Redundant DHCP and DNS Resolver using OpenBSD
888 words, 5 minutes
One of my OpenBSD server provides DHCP and DNS resolving for my home LAN. But it sometimes has to go into maintenance mode. And if an IoT or phone requires an IP address or an FQDN at the precise moment, I hear screaming throughout the whole house.
So I decided to have fully redundant network services using two OpenBSD servers.
The installation is really simple:
- Two OpenBSD servers on a LAN are configured with a static IP.
- Both run the stock
unbound(8)program to provide DNS cache / resolver. - Both run the stock
dhcpd(8)program to provide DHCP address allocation.
Unbound DNS validating resolver
OpenBSD 7.7 ships with unbound 1.22.0.
Its configuration is really simple given that a
/var/unbound/etc/unbound.conf file already exists.
Both servers share the exact same configuration. I only changed a few things from the default configuration.
--- unbound.conf.orig Sun Apr 13 16:07:40 2025
+++ unbound.conf Wed Jul 16 18:09:40 2025
@@ -3,4 +3,5 @@
server:
interface: 127.0.0.1
+ interface: 192.0.2.666
#interface: 127.0.0.1@5353 # listen on alternative port
interface: ::1
@@ -12,6 +13,10 @@
#outgoing-interface: 2001:db8::53
+ num-threads: 2
+
access-control: 0.0.0.0/0 refuse
access-control: 127.0.0.0/8 allow
+ access-control: 192.0.2.0/24 allow
access-control: ::0/0 refuse
access-control: ::1 allow
@@ -20,4 +25,14 @@
hide-version: yes
+ log-queries: no
+ log-replies: yes
+ log-tag-queryreply: yes
+ log-destaddr: yes
+ log-local-actions: yes
+ log-servfail: yes
+
+ prefetch: yes
+ prefetch-key: yes
+
# Perform DNSSEC validation.
#
@@ -25,4 +40,6 @@
val-log-level: 2
+ qname-minimisation: yes
+
# Synthesize NXDOMAINs from DNSSEC NSEC chains.
# https://tools.ietf.org/html/rfc8198
@@ -38,4 +55,6 @@
#local-data-ptr: "192.0.2.51 mycomputer.local"
+ include: "/var/unbound/etc/home.arpa.conf"
+
# Use TCP for "forward-zone" requests. Useful if you are making
# DNS requests over an SSH port forwarding.
What this diff means is:
- I added the public servers interface so that LAN clients can actually query the DNS service.
- I told
unbound(8)to use two threads. (OPTIONAL) - I added an
access-controlto allowunbound(8)to reply to LAN clients queries. - I added some more logging for stats and debugging. (OPTIONAL)
- I configured
prefetchso that the DNS caches always have the latest entries; and queries get answered faster. (OPTIONAL) - I added
qname-minimisationso thatunbound(8)sends the minimum amount of information to upstream servers to enhance privacy. (OPTIONAL) - I included an external zone file to resolve internal names on the LAN. (OPTIONAL)
Once both servers are configured properly, enabling the service is as simple as:
# rcctl enable unbound
# rcctl start unbound
To make sure the root trust anchor for DNSSEC validation is always up-to-date, I added an entry in the crontab:
# update the root trust anchor for DNSSEC validation
1-30 0-3 * * * -ns /usr/sbin/unbound-anchor -v
When an update is required on the configuration or the local zone file,
I edit the file on one server, reload it, rsync the configuration to
the second server and reload it.
Having two DNS resolvers on the same LAN is no problem; since they have the same local zone content. So the LAN clients can query any one of those, any time.
Dynamic Host Configuration Protocol (DHCP) daemon
Should you not want to start from scratch after reading dhcpd(8),
dhcpd.conf(5) and dhcpd.leases(5), there is a configuration example in
/etc/examples/dhcpd.conf that can be copied to /etc/dhcpd.conf.
Not much has to be modified to have a working DHCPd configuration:
--- /etc/examples/dhcpd.conf Sun Apr 13 16:07:40 2025
+++ /etc/dhcpd.conf Thu Sep 11 01:43:26 2025
@@ -11,21 +11,115 @@
# Addresses: 192.168.1.32 - 192.168.1.127
#
-option domain-name "my.domain";
-option domain-name-servers 192.168.1.3, 192.168.1.5;
+option domain-name "home.arpa";
+option domain-name-servers 192.0.2.666, 192.0.2.999;
-subnet 192.168.1.0 netmask 255.255.255.0 {
- option routers 192.168.1.1;
+subnet 192.0.2.0 netmask 255.255.255.0 {
+ option domain-search "home.arpa";
+ option routers 192.0.2.1;
- range 192.168.1.32 192.168.1.127;
+ range 192.0.2.100 192.0.2.200;
- host static-client {
- hardware ethernet 22:33:44:55:66:77;
- fixed-address 192.168.1.200;
+ host smart-stove-top {
+ hardware ethernet fe:ed:ba:be:ca:fe;
+ fixed-address 192.0.2.404;
}
}
What this diff implies is:
- A domain name is offered to the local LAN toys; the domain same that
unbound(8)might be able to resolve. - Both previously configured
unbound(8)servers may be queried by LAN clients. - LAN clients will get IPs with the ( 192.0.2.100 - 192.0.2.200 ) range.
- Some hardware have static IP allocation. (OPTIONAL)
DHCP leases synchronization
Having two DHCP servers on the same LAN can lead to allocating the same
IP to two LAN clients - as soon as both DHCP servers have the exact same
configuration. To prevent such issue, both servers have to be kept aware
of the allocation the other one already did. Hopefully, dhcpd(8) has
options for this.
I want to use an Active/Active configuration. So I need to have my
DHCP servers both broadcasting their IP allocations and also listen to
others allocations. Those behaviour are achieved using the -Y and -y
arguments. Read dhcpd(8) for more details.
# rcctl enable dhcpd
# rcctl set dhcpd flags -y vio0 -Y vio0
# rcctl start dhcpd
Both DHCP servers are configured the same way and share the same configuration file. When a modification is required, it is copied from one server to the other; and services are reloaded.
Depending on your topology, you also may be interested by the fact that
dhcpd(8) can interact with pf(4). Just read the man page for more
details.
Until then, have a nice IP allocation and DNS resolving day!