DNS Resolver
The source material this page replaces covers installing Unbound directly on an OpenWrt router. For this network, the DNS resolver does not run on the router. It runs on the homelab server, which is always on, has more memory, and can handle the full Unbound configuration without the constraints of an embedded device.
The router’s role in DNS is simpler: distribute the homelab server’s address as the DNS server via DHCP, so every device on every VLAN uses the correct resolver automatically. This page covers both: the DHCP DNS distribution on the router, and the Unbound configuration on the server.
The desktop Unbound configuration was covered in the desktop section of this series. The server Unbound configuration is covered here, since it serves the whole network rather than just one machine.
Architecture
The DNS architecture for this three-site network:
Device on any VLAN
↓ DNS query
Unbound on homelab server (10.1.0.x)
↓ Internal domains
Authoritative DNS server (covered in the server section)
↓ External domains
Root hints / recursive resolution or upstream DoT resolver
Every device on every VLAN at every site, when connected via the inter-site VPN, uses the same Unbound instance at the primary site. Each site could run its own resolver for resilience, but a single well-maintained resolver is simpler to manage and consistent across the estate.
Router: DHCP DNS distribution
The router distributes the DNS server address to every device that gets a DHCP lease. In UniFi, set this per network.
Navigate to Settings > Networks. Edit each network and under DHCP, set:
DNS Server 1: 10.1.0.x (the homelab server’s address on the Core VLAN)
DNS Server 2: 1.1.1.1 (fallback if the homelab server is unavailable)
The fallback is important: if the homelab server is down for maintenance, devices should still be able to resolve external names. The fallback should not be used for internal names, since it does not know about your private zones.
For the Visitor VLAN, set the DNS server to a public resolver rather than the internal one. Visitor devices should not have access to internal DNS records:
- Visitor VLAN DNS Server 1:
1.1.1.1 - Visitor VLAN DNS Server 2:
9.9.9.9
Server: Unbound installation
Install Unbound on the homelab server:
sudo apt install unbound dns-root-data
Create the drop-in configuration directory:
sudo mkdir -p /etc/unbound/unbound.conf.d
Server: main configuration
Create /etc/unbound/unbound.conf.d/server.conf:
server:
#--------------------------------------
# Interface and access control
#--------------------------------------
# Listen on all interfaces so all three sites can reach it via VPN
interface: 0.0.0.0
interface: ::0
port: 53
# Allow queries from all three site subnets and localhost
access-control: 127.0.0.0/8 allow
access-control: ::1 allow
access-control: 10.0.0.0/8 allow
access-control: 192.168.0.0/16 allow
access-control: 172.16.0.0/12 allow
access-control: 0.0.0.0/0 refuse
access-control: ::0/0 refuse
#--------------------------------------
# DNSSEC
#--------------------------------------
auto-trust-anchor-file: "/var/lib/unbound/root.key"
root-hints: "/usr/share/dns/root.hints"
#--------------------------------------
# Privacy and security
#--------------------------------------
hide-identity: yes
hide-version: yes
hide-trustanchor: yes
identity: ""
version: ""
qname-minimisation: yes
aggressive-nsec: yes
use-caps-for-id: yes
harden-glue: yes
harden-dnssec-stripped: yes
harden-algo-downgrade: yes
harden-referral-path: yes
harden-below-nxdomain: yes
harden-short-bufsize: yes
harden-large-queries: yes
# DNS rebinding protection
private-address: 10.0.0.0/8
private-address: 172.16.0.0/12
private-address: 192.168.0.0/16
private-address: 169.254.0.0/16
private-address: fc00::/7
private-address: fe80::/10
private-address: ::ffff:0:0/96
# Allow internal domains to contain private addresses
private-domain: "yourdomain.net"
#--------------------------------------
# Performance
#--------------------------------------
num-threads: 2
so-reuseport: yes
msg-cache-size: 50m
rrset-cache-size: 100m
cache-min-ttl: 300
cache-max-ttl: 86400
prefetch: yes
prefetch-key: yes
serve-expired: yes
minimal-responses: yes
rrset-roundrobin: yes
#--------------------------------------
# EDNS
#--------------------------------------
edns-buffer-size: 1232
#--------------------------------------
# Logging
#--------------------------------------
use-syslog: yes
verbosity: 0
# Outgoing port ranges - avoid ports used by other services
outgoing-port-avoid: 1194 # OpenVPN
outgoing-port-avoid: 5060 # SIP
outgoing-port-avoid: 51820 # WireGuard
#--------------------------------------
# Local zones: RFC 1918 reverse zones
#--------------------------------------
# Prevent RFC 1918 reverse lookups leaking to the internet
local-zone: "10.in-addr.arpa." nodefault
local-zone: "168.192.in-addr.arpa." nodefault
local-zone: "16.172.in-addr.arpa." nodefault
local-zone: "17.172.in-addr.arpa." nodefault
local-zone: "18.172.in-addr.arpa." nodefault
local-zone: "19.172.in-addr.arpa." nodefault
local-zone: "20.172.in-addr.arpa." nodefault
local-zone: "21.172.in-addr.arpa." nodefault
local-zone: "22.172.in-addr.arpa." nodefault
local-zone: "23.172.in-addr.arpa." nodefault
local-zone: "24.172.in-addr.arpa." nodefault
local-zone: "25.172.in-addr.arpa." nodefault
local-zone: "26.172.in-addr.arpa." nodefault
local-zone: "27.172.in-addr.arpa." nodefault
local-zone: "28.172.in-addr.arpa." nodefault
local-zone: "29.172.in-addr.arpa." nodefault
local-zone: "30.172.in-addr.arpa." nodefault
local-zone: "31.172.in-addr.arpa." nodefault
Server: forward zones for internal domains
Create /etc/unbound/unbound.conf.d/forward-zones.conf to forward internal domain queries to the authoritative DNS server:
# Forward internal domains to the authoritative DNS server
# Update these addresses once the DNS server is running
forward-zone:
name: "yourdomain.net"
forward-addr: 10.1.0.x # authoritative DNS server address
forward-first: no
# Reverse zones for all three sites
forward-zone:
name: "1.10.in-addr.arpa."
forward-addr: 10.1.0.x
forward-first: no
forward-zone:
name: "2.10.in-addr.arpa."
forward-addr: 10.1.0.x
forward-first: no
forward-zone:
name: "3.10.in-addr.arpa."
forward-addr: 10.1.0.x
forward-first: no
Server: upstream resolvers with DNS-over-TLS
For external domain resolution, forward to a DNS-over-TLS resolver rather than doing full recursion from root hints. This encrypts DNS queries leaving the network.
Create /etc/unbound/unbound.conf.d/upstream.conf:
# Forward all external queries to DNS-over-TLS resolvers
# Leave internal forward zones in forward-zones.conf to take precedence
forward-zone:
name: "."
forward-tls-upstream: yes
# Cloudflare DNS
forward-addr: 1.1.1.1@853#cloudflare-dns.com
forward-addr: 1.0.0.1@853#cloudflare-dns.com
forward-addr: 2606:4700:4700::1111@853#cloudflare-dns.com
# Quad9 (DNSSEC-validating, malware-blocking)
forward-addr: 9.9.9.9@853#dns.quad9.net
forward-addr: 149.112.112.112@853#dns.quad9.net
forward-first: yes
Server: remote control
Create /etc/unbound/unbound.conf.d/remote-control.conf:
remote-control:
control-enable: yes
control-interface: 127.0.0.1
control-interface: ::1
control-port: 8953
server-key-file: "/etc/unbound/unbound_server.key"
server-cert-file: "/etc/unbound/unbound_server.pem"
control-key-file: "/etc/unbound/unbound_control.key"
control-cert-file: "/etc/unbound/unbound_control.pem"
Generate the remote control certificates:
sudo unbound-control-setup
Firewall rules
The homelab server needs to accept DNS queries from all three site subnets. Add rules to allow UDP and TCP port 53 from the internal network:
sudo ufw allow from 10.0.0.0/8 to any port 53
sudo ufw allow from 192.168.0.0/16 to any port 53
On the UniFi firewall, ensure traffic from the site 2 and site 3 VPN subnets to the homelab server on port 53 is permitted. Since inter-site traffic flows through the WireGuard tunnel and the tunnel permits 10.0.0.0/8 routing, this should work without additional rules.
Root hints update
The dns-root-data package installs root hints at /usr/share/dns/root.hints. These are updated by package updates. Additionally, update them manually quarterly:
cat > ~/.anacron/cron.weekly/update-root-hints << 'EOF'
#!/usr/bin/env bash
# Update DNS root hints from IANA
REMOTE_URL='https://www.internic.net/domain/named.cache'
LOCAL_FILE='/usr/share/dns/root.hints'
TEMP_FILE=$(mktemp)
if curl --fail --silent --show-error --location \
--time-cond "$LOCAL_FILE" \
--remote-time \
--output "$TEMP_FILE" \
"$REMOTE_URL"; then
if [ -s "$TEMP_FILE" ]; then
sudo cp "$TEMP_FILE" "$LOCAL_FILE"
sudo chmod 644 "$LOCAL_FILE"
sudo unbound-control reload
echo "Root hints updated"
fi
fi
rm -f "$TEMP_FILE"
EOF
chmod 0755 ~/.anacron/cron.weekly/update-root-hints
Start and enable Unbound
Validate the configuration:
sudo unbound-checkconf
Enable and start the service:
sudo systemctl enable --now unbound
Verification
Test from the homelab server:
# Basic resolution
dig google.com @127.0.0.1
# DNSSEC validation
dig +dnssec sigok.verteiltesysteme.net @127.0.0.1
# DNSSEC failure (should return SERVFAIL)
dig sigfail.verteiltesysteme.net @127.0.0.1
# Internal domain (once authoritative DNS is running)
dig server.yourdomain.net @127.0.0.1
Test from the desktop:
# Should resolve via the homelab server
resolvectl query google.com
resolvectl query server.yourdomain.net
Check unbound statistics:
sudo unbound-control stats_noreset | grep -E "total\.|cache\."
Check the service status:
sudo systemctl status unbound
sudo journalctl -u unbound -f
Per-site resolvers
For resilience, Vernal and Estival can each run their own Unbound instance forwarding to the primary resolver. This means each site continues to resolve internal names even if the inter-site VPN is down.
Install Unbound on each site’s server with the same configuration, but point the forward zones at the local authoritative DNS server for that site’s zones, with the primary site resolver as a fallback.
This is an optional improvement. For a homelab where occasional connectivity interruptions are acceptable, a single resolver is sufficient.
The DNS resolver is the most critical piece of infrastructure after the router itself. Every connection the network makes starts with a DNS lookup. If the resolver is down, the network effectively stops working for most users. Run it on the most reliable server available and monitor it via the alerting setup in the server section.