VyOSI · Networking Foundations for Routing EngineersLayer 2 and Layer 3 foundations
DNS and DHCP — the services every routing change depends on
What you'll learn
- Configure `service dns forwarding` in both its forwarding and its recursive mode, with caching and per-domain overrides
- Run a VyOS DHCP server and DHCP relay across subnets
- Describe how DNS and DHCP interact with the routing plane and the firewall
- Identify the DNS and DHCP failure modes that surface as routing or application incidents
- Validate DNS and DHCP operation with dig, drill, dhclient, and tcpdump
Prerequisites
Verified against VyOS 1.5.x LTS (circinus) · VyOS 1.4.x (sagitta) — legacy · FRRouting 10.x (VyOS 1.5) · Linux kernel 6.6 LTS (VyOS 1.5 base) · strongSwan 5.9.x (IPsec) · WireGuard 1.0.x (kernel module + userspace tooling) · 2026-08-19
A routing engineer who treats DNS and DHCP as “someone else’s problem” is a routing engineer who will spend half of every incident chasing the wrong subsystem. The routing table tells the kernel where to send packets. DNS tells the application where to send packets. DHCP tells the host what IP address it has, which subnet it is on, which gateway to use, and which DNS server to ask.
This lesson is the operator’s grounding in DNS and DHCP on
VyOS 1.5. The platform wraps three separate services: a client
resolver under service dns forwarding, a DHCP server, and a
DHCP relay. The VyOS 1.5 documentation names the DHCP
implementation directly — “VyOS uses Kea DHCP server for both
IPv4 and IPv6 address assignment” — which matters because the
move to Kea is what changed the CLI shape you will see below.
The configuration is small; the operational implications are
large.
DNS on VyOS
There is one client-facing DNS service in the VyOS CLI, and
it is service dns forwarding. Underneath, it is PowerDNS
Recursor: VyOS renders the CLI into a generated recursor
configuration. There is no service dns recursive node and no
unbound tree — if you have seen either in older course
material, they were never valid VyOS commands.
The single service has two modes, and you select between them by what you configure, not by picking a different service:
- Forwarding — configure
name-server,system, ordhcpand queries go to those upstream resolvers, with the answers cached locally. - Recursion — configure none of those three and the same service resolves from the root servers itself. The VyOS documentation describes it as able to “operate as a full recursive DNS server without requiring upstream DNS servers”, and gives the privacy reason: “operating without upstream servers avoids exposing client queries to an upstream DNS operator.”
Two nodes are mandatory for a successful commit: at least one
listen-address and at least one allow-from prefix. A
forwarder with neither will not commit, which is deliberate —
an unrestricted resolver on a routed interface is an open
reflector.
configure
set service dns forwarding listen-address 10.0.0.1
set service dns forwarding allow-from 10.0.0.0/24
set service dns forwarding name-server 192.0.2.53
set service dns forwarding name-server 198.51.100.53
set service dns forwarding cache-size 10000
set service dns forwarding negative-ttl 60
commit
save
The listen-address is the address the forwarder binds to, and
the documentation is explicit that it “must already be assigned
to a local interface” — so add the address before you configure
the resolver on it, not after.
The allow-from prefix is the ACL of source networks permitted
to query. This is not optional politeness: an unrestricted
recursive resolver on a routed interface is a DDoS amplifier,
which is why the CLI refuses to commit without at least one
prefix. The behaviour when a client falls outside every prefix
is worth knowing, because it is the difference between two
diagnoses — the recursor answers REFUSED rather than dropping
the packet, so an out-of-ACL client gets an immediate error and
a firewalled client gets a timeout.
name-server names each upstream resolver, optionally with a
non-default port (name-server 192.0.2.53 port 853).
cache-size is a count of cache entries, default 10000, and 0
disables caching entirely.
Two upstream shorthands are worth knowing because they are what production configurations actually use:
set service dns forwarding system— forward to whatever is configured undersystem name-server. Note the direction: this points the forwarder at the system resolvers. It does not point the router’s own resolver at the forwarder.set service dns forwarding dhcp eth0— forward to the resolvers learned by the DHCP client oneth0. This is the standard edge-router pattern, where the ISP’s resolvers arrive with the WAN lease and are never typed into the config.
flowchart LR
C["LAN client\n10.0.0.50"] -->|"DNS query for host.example.com"| V["service dns forwarding\n10.0.0.1:53"]
V -->|"forward mode: name-server / system / dhcp"| U1["Upstream resolver\n192.0.2.53"]
V -->|"recursion mode: no upstream configured"| R["Root / TLD / authoritative"]
U1 -->|"recursive walk"| R
R -->|"answer"| U1
U1 -->|"answer"| V
R -->|"answer"| V
V -->|"cached answer"| C
To move a router from forwarding to recursion, remove every upstream — there is no mode switch to set:
configure
delete service dns forwarding name-server
delete service dns forwarding system
delete service dns forwarding dhcp
set service dns forwarding dnssec validate
commit
save
dnssec takes one of off, process-no-validate (the
default), process, log-fail, or validate. Only validate
checks every answer and returns SERVFAIL on bogus data;
process validates only when the client asks. Turning on
validate while forwarding to an upstream that strips DNSSEC
records will break resolution wholesale, so this is a change to
make deliberately and with a rollback plan.
Split-horizon
Per-domain forwarding is the domain node, not a “forward
zone”:
configure
set service dns forwarding domain corp.example.com name-server 10.0.0.53
set service dns forwarding domain corp.example.com addnta
commit
save
Queries for corp.example.com and everything under it go to the
internal authoritative resolver at 10.0.0.53; everything else
follows the global upstream. addnta marks the domain as a
Negative Trust Anchor, disabling DNSSEC validation for it — the
option exists because internal zones are usually unsigned, and
a validating forwarder would otherwise SERVFAIL every internal
name.
DNS and the firewall
DNS runs on UDP and TCP port 53. A LAN client querying the router’s own resolver is traffic terminating on the router, so in VyOS 1.5 it is matched by the input chain — not by the forward chain, and not by a rule set bolted onto an interface.
This is the single change that trips up operators coming from
1.3. There is no set interfaces ethernet eth1 firewall in name LAN-INBOUND any more. A named rule set is still written with
set firewall ipv4 name <name>, but it is reached by a jump
from one of the three base chains, and the interface is matched
inside the rule with inbound-interface name.
configure
set firewall ipv4 name LAN-TO-ROUTER default-action drop
set firewall ipv4 name LAN-TO-ROUTER rule 10 action accept
set firewall ipv4 name LAN-TO-ROUTER rule 10 protocol tcp_udp
set firewall ipv4 name LAN-TO-ROUTER rule 10 destination port 53
set firewall ipv4 name LAN-TO-ROUTER rule 10 source address 10.0.0.0/24
set firewall ipv4 input filter default-action drop
set firewall ipv4 input filter rule 10 action accept
set firewall ipv4 input filter rule 10 state established
set firewall ipv4 input filter rule 10 state related
set firewall ipv4 input filter rule 20 action jump
set firewall ipv4 input filter rule 20 jump-target LAN-TO-ROUTER
set firewall ipv4 input filter rule 20 inbound-interface name eth1
commit
save
Four details in that block are 1.4/1.5-specific and each one is
a commit failure if you write the 1.3 form:
- The tree is
firewall ipv4 name, notfirewall name. IPv6 lives underfirewall ipv6 nameas a separate tree, so a dual-stack service needs both. - The port match is
destination port 53, notdst-port 53. stateis a bare multi-value leaf:state establishedandstate relatedon the same rule. The 1.3 formstate established enableno longer parses.- Interfaces are matched with
inbound-interface name eth1inside the rule. Wildcards (eth2*) and negation (!eth1) are supported there, which the old per-interface binding could not express at all.
A common production error is to permit DNS only on UDP. The fallback to TCP happens whenever a UDP response is truncated (large DNSSEC records) or when a client requests AXFR (zone transfer). The client’s TCP retry is then dropped and the resolution fails, usually looking like an intermittent DNS outage that only affects some names.
DHCP on VyOS
VyOS 1.5 runs Kea. The documentation states it plainly — “VyOS uses Kea DHCP server for both IPv4 and IPv6 address assignment” — and the Kea data model is why several nodes you may remember have moved or been renamed.
configure
set service dhcp-server shared-network-name LAN authoritative
set service dhcp-server shared-network-name LAN subnet 10.0.0.0/24 subnet-id 1
set service dhcp-server shared-network-name LAN subnet 10.0.0.0/24 option default-router 10.0.0.1
set service dhcp-server shared-network-name LAN subnet 10.0.0.0/24 option name-server 10.0.0.1
set service dhcp-server shared-network-name LAN subnet 10.0.0.0/24 option domain-name corp.example.com
set service dhcp-server shared-network-name LAN subnet 10.0.0.0/24 lease 86400
set service dhcp-server shared-network-name LAN subnet 10.0.0.0/24 range 0 start 10.0.0.100
set service dhcp-server shared-network-name LAN subnet 10.0.0.0/24 range 0 stop 10.0.0.200
# Static mapping by MAC
set service dhcp-server shared-network-name LAN subnet 10.0.0.0/24 static-mapping printer-1 ip-address 10.0.0.50
set service dhcp-server shared-network-name LAN subnet 10.0.0.0/24 static-mapping printer-1 mac aa:bb:cc:11:22:33
commit
save
Read that block against a 1.3-era one and five things differ:
subnet-idis mandatory. The documentation says it “is required and must be unique to each subnet. It is required to map subnets to lease file entries.” Omit it and the commit fails; reuse a value across two subnets and you have corrupted the mapping between subnets and leases.- Client options live under
option.default-routerandname-serverareoption default-routerandoption name-server. Note also the rename: the DHCP option that used to be spelleddns-serverisoption name-serverhere, matching the DNS side of the CLI. static-mapping ... mac, notmac-address. The node isstatic-mapping <name> mac <address>paired withstatic-mapping <name> ip-address <address>.- There is no
option subnet-mask. The netmask is derived from the subnet prefix. Writing it is not “belt and braces”; it is a node that does not exist. - There is no per-shared-network
interfacenode on the IPv4 server. The documentation’s rule is that “each subnet must be present on an interface” — the server serves a subnet because the router has an address in it, not because you named an interface.set service dhcp-server listen-address <address>narrows which local address it answers on; it is the closest thing to the old binding and it is global, not per network.
authoritative is worth setting deliberately rather than by
habit. It declares this router the only DHCP server for the
network, and the documented consequence is that it will “send
‘DHCPNAK’ to any device trying to request an IP address that is
not valid for this network”. On a network that genuinely has one
server, that turns a slow client timeout into an immediate
re-DISCOVER. On a network that has two, it turns a quiet
misconfiguration into a loud one — which is usually what you
want, but it is a behaviour change, not a no-op.
The DHCP server listens on UDP port 67; clients respond on UDP port 68. Because DHCPDISCOVER arrives as a broadcast terminating on the router, it is input-chain traffic in the 1.5 firewall model, exactly like the DNS query above.
sequenceDiagram
autonumber
participant C as Client
participant V as VyOS DHCP server
participant N as LAN DNS / upstream
C->>V: DHCPDISCOVER (broadcast)
V->>C: DHCPOFFER (10.0.0.100)
C->>V: DHCPREQUEST (10.0.0.100)
V->>C: DHCPACK (10.0.0.100, lease 86400)
C->>C: Configure interface with 10.0.0.100/24, GW 10.0.0.1, DNS 10.0.0.1
Note over C,N: DNS queries via 10.0.0.1 (the router)
A subtle production detail: the DHCP server’s response is delivered via broadcast unless the client explicitly requests unicast. A network with multiple DHCP servers (a common production mistake) results in race conditions and unpredictable leases. The operator must verify there is exactly one DHCP server per subnet.
DHCP relay
When the DHCP server is on a different subnet than the client, the broadcast DHCP request cannot cross the router. The router must run a DHCP relay that intercepts the broadcast on the client’s subnet and forwards the request to the server via unicast.
Take the topology in the diagram below: clients on
192.168.1.0/24 behind eth2, and the central DHCP server at
10.0.0.50, reachable out of eth1.
configure
set service dhcp-relay listen-interface eth2
set service dhcp-relay upstream-interface eth1
set service dhcp-relay server 10.0.0.50
set service dhcp-relay relay-options hop-count 4
set service dhcp-relay relay-options relay-agents-packets discard
commit
save
The relay adds a giaddr (gateway IP address) field to the
DHCP request, identifying the subnet the request came from. The
server uses the giaddr to select the right subnet’s lease pool.
The two interface nodes are the part to get right, and the
1.4/1.5 CLI makes the distinction explicit where the older one
did not. set service dhcp-relay interface <if> still appears
in the tree but is documented as deprecated, and the reason is
operational rather than cosmetic: one flat list could not say
which side was which. listen-interface is where client
broadcasts are picked up — the client-facing side, eth2 here.
upstream-interface is where the unicast relay traffic leaves
toward the server — eth1. Swap them and the relay waits for
broadcasts on the server side, hears nothing, and reports no
error at all.
relay-options hop-count (1-255) replaces the older hops
spelling. relay-agents-packets decides what happens to a
request that already carries an option-82 relay-agent block —
discard is the safe default on an edge relay, because a
request arriving from a client with option 82 already attached
is either a second relay you did not know about or something
forged.
sequenceDiagram
autonumber
participant C as Client\n192.168.1.0/24
participant R as VyOS relay
participant S as DHCP server\n10.0.0.50
C->>R: DHCPDISCOVER (broadcast on 192.168.1.0/24)
Note over R: Relay intercepts broadcast
R->>S: DHCPDISCOVER (unicast, giaddr=192.168.1.1)
S->>R: DHCPOFFER (192.168.1.100)
R->>C: DHCPOFFER (broadcast or unicast)
C->>R: DHCPREQUEST
R->>S: DHCPREQUEST
S->>R: DHCPACK (192.168.1.100)
R->>C: DHCPACK
The relay must have an IP address on each subnet it serves as a relay for. If the VyOS router is the gateway for the client’s subnet, it already has the address. If the relay is dedicated hardware, configure the address explicitly.
DHCPv6
IPv6 has two configuration mechanisms:
- SLAAC — the host picks its own address from the advertised prefix. No state to track. Simple. But the host does not learn DNS servers automatically; RDNSS or DHCPv6 is needed for that.
- DHCPv6 — stateful. The server hands out addresses. The host also receives DNS server information.
VyOS supports both. The common pattern is SLAAC for addressing
with the resolver advertised in the router advertisement, which
is configured entirely under service router-advert on the
LAN-facing interface:
configure
set service router-advert interface eth1 prefix 2001:db8:abcd:1::/64
set service router-advert interface eth1 name-server 2001:db8:abcd:1::1
set service router-advert interface eth1 other-config-flag
commit
save
The node is name-server, not dns-server. It emits an RDNSS
option (RFC 8106) in the router advertisement, so the client
takes its address from the advertised prefix by SLAAC and its
resolver from the same advertisement — no DHCPv6 involved at
all. other-config-flag sets the O bit, telling hosts that
there is non-address configuration available over DHCPv6 if they
want it; managed-flag sets the M bit and tells them to get
their address over DHCPv6 instead of by SLAAC.
Note that this is the LAN interface. set interfaces ethernet eth0 address dhcpv6 is the opposite thing — it makes the router
a DHCPv6 client on that interface, which is what you configure
on the WAN side to accept an address from the ISP. Do not
confuse the two: a router advertising a prefix on the interface
where it is also asking for one is a topology error, not a
configuration style.
For full stateful DHCPv6, configure the DHCPv6 server as well and set the M bit so hosts actually use it:
configure
set service dhcpv6-server shared-network-name LAN6 interface eth1
set service dhcpv6-server shared-network-name LAN6 subnet 2001:db8:abcd:1::/64 subnet-id 1
set service dhcpv6-server shared-network-name LAN6 subnet 2001:db8:abcd:1::/64 range 0 start 2001:db8:abcd:1::100
set service dhcpv6-server shared-network-name LAN6 subnet 2001:db8:abcd:1::/64 range 0 stop 2001:db8:abcd:1::200
set service dhcpv6-server shared-network-name LAN6 subnet 2001:db8:abcd:1::/64 option name-server 2001:db8:abcd:1::1
set service router-advert interface eth1 managed-flag
commit
save
Two asymmetries with the IPv4 server are worth memorising
because they are easy to get backwards. The DHCPv6 server
does take an explicit interface under shared-network-name,
while the IPv4 server does not. And DHCPv6 identifies clients by
DUID rather than MAC, so its static mappings are
static-mapping <name> duid <identifier> with
static-mapping <name> ipv6-address <address> — a MAC address
will not commit there.
Operational commands
VyOS has its own operational-mode verbs for both services. Use these first — they read the running state rather than inferring it:
show dns forwarding statistics
show log dns forwarding
reset dns forwarding all
reset dns forwarding domain corp.example.com
show dhcp server leases
show dhcp server leases pool LAN
show dhcp server statistics
show dhcp server statistics pool LAN
show log dhcp server
reset dns forwarding domain <name> flushes one domain out of
the cache instead of the whole thing, which is the difference
between re-testing a single record after a zone change and
giving every client on the LAN a cold cache during business
hours.
Below that sits the standard Linux toolkit, which is what you reach for when the VyOS view says everything is fine and the client still cannot resolve:
# DNS
dig @10.0.0.1 host.example.com
dig @10.0.0.1 -x 192.0.2.10
drill -S host.example.com
tcpdump -ni eth1 udp port 53
# DHCP
tcpdump -ni eth1 udp port 67 or udp port 68
tcpdump -ni eth1 -vvv -n port 67
# DHCPv6 (from a client, not the router)
tcpdump -ni eth1 udp port 546 or udp port 547
The dig command is the canonical DNS debugging tool; drill -S chases and displays the DNSSEC signature chain, which is the
one thing dig will not draw for you. The tcpdump filters
matter more than they look: seeing the client’s DHCPDISCOVER
arrive but no DHCPOFFER leave separates a firewall problem from
a pool or subnet-id problem in one capture.
Validation
The validation sequence for “DNS is broken” / “DHCP is broken”:
- The forwarder is running and answering:
show dns forwarding statisticsreturns counters rather than an error, and the query count moves when you test. - DNS works from the router:
dig @10.0.0.1 host.example.comon the router itself returns the expected answer. Query the configuredlisten-address—@127.0.0.1only works if loopback is also a listen-address, and by default it is not. - DNS works from the LAN: the same query from a LAN client returns the same answer. A
REFUSEDhere meansallow-fromdoes not cover the client’s source prefix; a timeout means the input-chain rule is not matching. - Split-horizon works:
dig @10.0.0.1 intranet.corp.example.comreturns the internal answer whiledig @10.0.0.1 www.example.comreturns the public one. - DHCP works: a new client on the LAN receives an address inside the configured range, with the expected default router and resolver.
- The lease is recorded:
show dhcp server leases pool LANshows the new lease with the expected MAC. - The relay works: a client on the relayed subnet receives an address from that subnet’s pool on the central server, and the capture shows the relayed request carrying the relay’s
giaddr.
Cross-course references
- The Linux course’s
XXII-Linux-NetTroubleshootcoversdig,drill, andtcpdumpfrom the host perspective. - The OPNsense course covers DNS and DHCP through the FreeBSD
unboundanddnsmasqpackages, with the OPNsense GUI as the configuration surface. - The Observability course covers DNS as a monitoring target (blackbox exporter DNS probe).
Quiz
Knowledge check · 4 questions
Q1. A LAN client cannot resolve names but the VyOS router itself can. What is the most likely cause?
VyOS-R1 runs service dns forwarding with listen-address 10.0.0.1 and allow-from 10.0.0.0/24. On the router, `dig @10.0.0.1 host.example.com` answers. From the LAN client at 10.0.0.50 the same query times out. The input chain has default-action drop.
Q2. A DHCP client on subnet 192.168.1.0/24 broadcasts DHCPDISCOVER. The DHCP server is on subnet 10.0.0.0/24. What component must be present between them?
Q3. DNS uses TCP port 53 as well as UDP port 53.
Q4. A DHCP server with a /24 subnet and a 100-address pool (10.0.0.100-200) starts handing out addresses to a new client. The client gets an address, but other clients report "no lease available". What is the most likely cause?
VyOS-R1 runs the LAN DHCP server with the pool 10.0.0.100-10.0.0.200. The pool is 101 addresses. Some addresses are reserved via static-mapping. The dynamic leases count appears to be near the pool limit.
Passing score: 75%. Answers are checked in this browser.
Production discipline
DNS and DHCP are the two services that turn a routed network into a network hosts can actually use. A routing table is correct but the hosts cannot get an address, or they have an address but cannot resolve names — either failure makes the network unusable.
Plan the DNS and DHCP topology once. Document the splits. Document the pools. Validate the firewall rules permit the necessary traffic. Then the network either works or fails predictably.