DNS Resolver

Posted on 5 2026

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.