DHCP and DNS are the two services nobody thinks about until they break. And when they break, everything breaks — no leases means no connectivity, no resolution means “the internet is down” even though routing is perfectly fine. Half the emergency calls I’ve taken over the years traced back to one of these two services being configured once, badly, and never touched again.
RouterOS ships a capable DHCP server and a caching DNS resolver. Both work well. Both also have defaults and failure modes you need to understand — especially the DNS one, which has earned MikroTik routers a starring role in DNS amplification attacks worldwide. Not because RouterOS is broken, but because people check “Allow Remote Requests” and never add the firewall rule that’s supposed to go with it.
DHCP Server: The Right Way
The setup wizard (/ip dhcp-server setup) works, but doing it manually teaches you what the pieces are. You need three things: an address pool, a DHCP server instance bound to an interface, and a network definition.
# Address pool — deliberately not the whole subnet/ip pool add name=lan-pool ranges=10.10.0.100-10.10.0.199
# Server instance on the LAN bridge/ip dhcp-server add name=lan-dhcp interface=bridge-lan address-pool=lan-pool \ lease-time=1h disabled=no
# Network definition — what clients are told/ip dhcp-server network add address=10.10.0.0/24 gateway=10.10.0.1 \ dns-server=10.10.0.1 ntp-server=10.10.0.1 domain=lan.example.comNotes that matter:
The pool starts at .100. Everything below is reserved for infrastructure and static leases. Mixing dynamic range and static assignments in the same block is how you get duplicate-IP incidents.
Lease time of 1 hour, not 10 minutes and not 3 days. Short leases hammer the server and fill logs with renewals; long leases mean a misbehaving client holds an address for days. One hour (or 4 for stable office networks) is a sane default. For guest networks, drop to 15–30 minutes.
The DHCP server binds to an interface, not an IP. If your LAN is a bridge with VLANs, the server goes on the VLAN interface, not the bridge itself — one DHCP server instance per VLAN interface, each with its own pool and network.
Static Leases as Inventory
Every device that matters gets a static lease. Not a manually configured IP on the device — a DHCP reservation on the router:
/ip dhcp-server lease add address=10.10.0.10 mac-address=AA:BB:CC:DD:EE:01 \ comment="NAS - storage" server=lan-dhcp/ip dhcp-server lease add address=10.10.0.11 mac-address=AA:BB:CC:DD:EE:02 \ comment="Proxmox node 1" server=lan-dhcpThis gives you central control (device config stays default-DHCP everywhere), and the lease table becomes documentation. /ip dhcp-server lease print with comments is an inventory that can’t drift from reality, because reality is generated from it.
A useful trick: connect the device, let it grab a dynamic address, then convert it:
/ip dhcp-server lease make-static [find mac-address="AA:BB:CC:DD:EE:03"]# then edit the address to the static blockDHCP Options: Beyond Gateway and DNS
The network entry covers the common options, but PXE boot, VoIP phones, and unifi-style controllers need custom options:
# Option 66/67 for PXE/ip dhcp-server option add name=tftp-server code=66 value="s'10.10.0.20'"/ip dhcp-server option add name=bootfile code=67 value="s'pxelinux.0'"/ip dhcp-server option sets add name=pxe options=tftp-server,bootfile/ip dhcp-server network set [find address="10.10.0.0/24"] dhcp-option-set=pxeThe quoting is deliberate and the prefix decides the encoding: s'text' sends a raw string, '10.10.0.20' (no prefix) encodes an IP as 4 binary bytes, and 0x sends hex. Option 66 formally expects a string, hence s'...' — check what your option actually landed as in print (the raw-value column shows the final hex). Test with a packet capture the first time — DHCP option encoding mistakes fail silently on the client side.
DHCP Client on WAN
The WAN side is usually a DHCP client:
/ip dhcp-client add interface=ether1 disabled=no use-peer-dns=no use-peer-ntp=no \ add-default-route=yes default-route-distance=1use-peer-dns=no is intentional. If you accept the ISP’s DNS servers, they get injected into your resolver config and clients end up using whatever your ISP runs — including their NXDOMAIN-hijacking ad servers. You decide your upstream DNS, not your ISP. Same logic for NTP.
default-route-distance matters when you have two WANs — give the backup a higher distance and failover becomes automatic when the primary DHCP lease disappears. I covered the full multi-WAN pattern earlier in this series.
DNS: Caching Resolver Done Right
/ip dns set servers=1.1.1.1,9.9.9.9 allow-remote-requests=yes \ cache-size=4096KiB max-concurrent-queries=200allow-remote-requests=yes is required for the router to answer client queries — and it is also the single most dangerous checkbox in RouterOS. It makes the router answer DNS on all interfaces, including WAN. An open resolver on a public IP will be found within hours and drafted into amplification attacks. The load will be yours, the abuse reports will be yours.
The firewall rule from the firewall article in this series is not optional:
/ip firewall filter add chain=input in-interface-list=WAN protocol=udp dst-port=53 \ action=drop comment="No DNS from WAN"/ip firewall filter add chain=input in-interface-list=WAN protocol=tcp dst-port=53 \ action=drop comment="No DNS-TCP from WAN"If your input chain already drops by default (it should), these are redundant — but explicit drop rules with counters let you see the scanning attempts, and they protect you when someone later “temporarily” relaxes the default policy.
DoH Upstream
RouterOS 7 can forward queries over DNS-over-HTTPS, which encrypts your resolver traffic to the upstream:
# Import a CA bundle first so the certificate verifies/tool fetch url=https://curl.se/ca/cacert.pem/certificate import file-name=cacert.pem passphrase=""
/ip dns set use-doh-server=https://cloudflare-dns.com/dns-query verify-doh-cert=yesTwo caveats. First, keep regular servers= configured as bootstrap — the router needs classic DNS to resolve the DoH hostname itself (or add a static entry for it). Second, verify-doh-cert=yes is the whole point; running DoH without certificate verification is theater.
Static Entries and Local Names
The resolver doubles as your local zone:
/ip dns static add name=nas.lan.example.com address=10.10.0.10/ip dns static add name=proxmox.lan.example.com address=10.10.0.11# Wildcard for a lab ingress/ip dns static add regexp=".*\\.apps\\.lan\\.example\\.com" address=10.10.0.30Regexp entries are powerful and expensive — every query is matched against every regexp, so keep the list short. Note that regexp entries are checked before static name entries, and matching is done against the full query string.
Verification
# Who holds leases right now/ip dhcp-server lease print where status=bound
# Watch DHCP conversation live when a client won't get an address/ip dhcp-server print stats/log print where topics~"dhcp"
# Resolver health/ip dns print/ip dns cache print where type="A"
# Resolve through the router explicitly/put [:resolve nas.lan.example.com server=127.0.0.1]For lease problems, enable DHCP debug logging temporarily: /system logging add topics=dhcp action=memory. It shows every DISCOVER/OFFER/REQUEST/ACK, which turns “it doesn’t work” into “the client requests its old address from a different subnet and we NAK it” in about thirty seconds.
Closing Thoughts
DHCP and DNS on RouterOS are mature and boring, which is exactly what you want. The rules that keep them boring: static leases for everything with a name, dynamic pool that doesn’t overlap infrastructure space, your own upstream DNS instead of the ISP’s, and DNS service reachable from LAN only — verified by an explicit firewall rule, not by hope. Set it up once like this and the only time you’ll touch it is to add another static lease to the inventory.