TTR Networks

Intermediate · DHCP and DNS on the Debian router · section 2 of 6

Configuring dnsmasq

✓ Lab-tested 5 November 2026 on Debian 13.7, dnsmasq 2.91, dhcpcd 10.1.0, nftables 1.1.3

Debian's dnsmasq package reads /etc/dnsmasq.conf and every file in /etc/dnsmasq.d/, so the whole office configuration can live in one file of its own, office.conf, leaving the package's file untouched.

What each part does

  • Where to listen. interface= names the three VLANs, and bind-dynamic makes dnsmasq bind only to their addresses (and follow them if they change). Nothing is offered on the WAN, so the router can't become a DNS server for the whole internet by mistake.
  • DNS forwarding. no-resolv ignores /etc/resolv.conf, and server= names the upstream resolver explicitly. domain-needed and bogus-priv keep single names and reverse lookups for private addresses from leaking upstream.
  • Local names. domain=office.lan is handed to clients by DHCP; expand-hosts turns each client's hostname into <name>.office.lan, and local=/office.lan/ says the router alone is authoritative for that domain: names it doesn't know there get "no such name", not a trip to the internet. .lan isn't a real top-level domain; a domain you own (such as office.example.com) is better if you have one.
  • DHCP ranges. One dhcp-range per VLAN, .100 to .199, leaving the low addresses for fixed devices. dnsmasq picks the range that matches the subnet of the interface a request arrives on. Guests get 2-hour leases, so addresses from visitors' phones are freed quickly. Unless told otherwise, dnsmasq hands out itself as the gateway and DNS server, which is what every VLAN here needs.
  • A reservation. dhcp-host ties a MAC address to an IP address and a name: the file server always gets 10.10.30.10 and the name fileserver.
  • Blocking. address=/ads.example.com/ with no address makes dnsmasq answer every name in that domain itself, with "no such name". It's the same idea that DNS-based ad blockers use with long lists.

Starting it

dnsmasq --test checks the syntax; systemctl enable --now dnsmasq starts it now and at boot. ss shows it listening for DNS on the router's three VLAN addresses (and loopback), and for DHCP on port 67. Its log confirms the three ranges and the upstream server, and that it answers ads.example.com and office.lan from local knowledge only.

/etc/dnsmasq.d/office.conf
# /etc/dnsmasq.d/office.conf: DHCP and DNS for the office VLANs.

# Serve the VLANs only; never answer on the WAN.
interface=staff
interface=guest
interface=servers
bind-dynamic

# DNS: forward to the ISP's resolver and cache the answers.
no-resolv
server=198.51.100.53
cache-size=1000
domain-needed
bogus-priv

# Local names: <hostname>.office.lan for every DHCP client, answered here only.
domain=office.lan
local=/office.lan/
expand-hosts

# One address range per VLAN. Guests get short leases.
dhcp-range=set:staff,10.10.10.100,10.10.10.199,12h
dhcp-range=set:guest,10.10.20.100,10.10.20.199,2h
dhcp-range=set:servers,10.10.30.100,10.10.30.199,12h

# Reservations: the file server always gets .10, by its MAC address.
dhcp-host=02:00:00:00:30:10,10.10.30.10,fileserver

# Block a domain for everyone: answer with "no such name".
address=/ads.example.com/
  • Lines 4–7: Only the three VLANs. bind-dynamic binds to their addresses, never the WAN's.
  • Lines 10–14: Forward to the ISP's resolver, cache up to 1000 answers, and keep private lookups at home.
  • Lines 17–19: Clients become <hostname>.office.lan, answered only by the router.
  • Lines 22–24: One range per VLAN; dnsmasq matches each request to the subnet of the interface it arrived on. Guests: 2-hour leases.
  • Line 27: MAC address → fixed IP and name.
  • Line 30: Answer the whole domain locally with "no such name".
Starting dnsmasq (real output)
root@router:~# dnsmasq --test && systemctl enable --now dnsmasq
dnsmasq: syntax check OK.
Synchronizing state of dnsmasq.service with SysV service script with /usr/lib/systemd/systemd-sysv-install.
Executing: /usr/lib/systemd/systemd-sysv-install enable dnsmasq
Created symlink '/etc/systemd/system/multi-user.target.wants/dnsmasq.service' → '/usr/lib/systemd/system/dnsmasq.service'.

root@router:~# ss -4lunp | grep dnsmasq; journalctl -u dnsmasq --no-pager -o cat -n 8
UNCONN 0      0          127.0.0.1:53         0.0.0.0:*    users:(("dnsmasq",pid=558,fd=12))
UNCONN 0      0         10.10.10.1:53         0.0.0.0:*    users:(("dnsmasq",pid=558,fd=10))
UNCONN 0      0         10.10.20.1:53         0.0.0.0:*    users:(("dnsmasq",pid=558,fd=8)) 
UNCONN 0      0         10.10.30.1:53         0.0.0.0:*    users:(("dnsmasq",pid=558,fd=6)) 
UNCONN 0      0            0.0.0.0:67         0.0.0.0:*    users:(("dnsmasq",pid=558,fd=4)) 
DHCP, IP range 10.10.30.100 -- 10.10.30.199, lease time 12h
DHCP, IP range 10.10.20.100 -- 10.10.20.199, lease time 2h
DHCP, IP range 10.10.10.100 -- 10.10.10.199, lease time 12h
using nameserver 198.51.100.53#53
using only locally-known addresses for ads.example.com
using only locally-known addresses for office.lan
read /etc/hosts - 20 names
Started dnsmasq.service - dnsmasq - A lightweight DHCP and caching DNS server.
  • Lines 1–5: Syntax checked, then started now and at every boot.
  • Lines 8–11: DNS on loopback and the router's address on each VLAN; nothing on the WAN.
  • Line 12: DHCP on port 67. dnsmasq still only answers on the configured interfaces.
  • Lines 13–16: The three ranges and the upstream resolver, as configured.
  • Lines 17–18: The blocked domain and office.lan are answered locally.

Check your understanding

Why does the configuration list the VLAN interfaces with interface= instead of letting dnsmasq listen everywhere?

  • So it never answers DHCP or DNS on the WAN, where it would serve the ISP's network or the whole internet.
  • Because dnsmasq can only serve one interface without it.
  • To make it faster.
  • Because VLAN interfaces don't receive broadcasts otherwise.