Logo

Unbound: validating recursive DNS resolver

Unbound: validating recursive DNS resolver

Unbound is a modern recursive DNS resolver: native DNSSEC validation, outbound DNS-over-TLS, performant cache, light footprint. The best alternative to forwarding to Cloudflare/Quad9 if you want end-to-end private DNS.

Introduction

Unbound:

  • Recursive resolver (queries root servers directly)
  • Native DNSSEC validation
  • Outbound DNS-over-TLS (DoT) support
  • Performant cache, very low memory footprint
  • Written in C, audited, security-focused (NLnet Labs)
  • Multi-thread, configurable up to 100k qps

Use Unbound when:

  • You want a local resolver with no external dependency (privacy)
  • You manage enterprise/datacenter LAN
  • You want to block ads/malware (with lists)
  • You want encrypted DNS to upstream

Prerequisites

  • Linux VPS Debian / Ubuntu
  • Root access
  • Port 53 UDP/TCP free

Step 1: Installation

sudo apt update
sudo apt install -y unbound
unbound -V
sudo systemctl enable --now unbound

Step 2: Minimal config (LAN resolver)

sudo nano /etc/unbound/unbound.conf.d/lan.conf
server:
    verbosity: 1
    interface: 0.0.0.0
    port: 53
    
    access-control: 127.0.0.0/8 allow
    access-control: 192.168.0.0/16 allow
    access-control: 10.0.0.0/8 allow
    access-control: 172.16.0.0/12 allow
    
    do-ip4: yes
    do-ip6: yes
    do-udp: yes
    do-tcp: yes
    
    hide-identity: yes
    hide-version: yes
    
    harden-glue: yes
    harden-dnssec-stripped: yes
    
    use-caps-for-id: yes
    
    cache-min-ttl: 300
    cache-max-ttl: 86400
    prefetch: yes
    prefetch-key: yes
    
    num-threads: 2
    msg-cache-size: 64m
    rrset-cache-size: 128m

Step 3: Download root hints

sudo wget https://www.internic.net/domain/named.root -O /var/lib/unbound/root.hints
server:
    root-hints: "/var/lib/unbound/root.hints"

For DNSSEC:

sudo unbound-anchor -a /var/lib/unbound/root.key
server:
    auto-trust-anchor-file: "/var/lib/unbound/root.key"

Step 4: Test

sudo unbound-checkconf
sudo systemctl restart unbound

dig @127.0.0.1 google.com
dig @127.0.0.1 example.com

# DNSSEC test
dig @127.0.0.1 +dnssec dnssec-failed.org   # should SERVFAIL
dig @127.0.0.1 +dnssec cloudflare.com       # should have `ad` flag

Step 5: Outbound DNS-over-TLS (DoT)

server:
    tls-cert-bundle: "/etc/ssl/certs/ca-certificates.crt"
    
forward-zone:
    name: "."
    forward-tls-upstream: yes
    forward-addr: 9.9.9.9@853#dns.quad9.net
    forward-addr: 149.112.112.112@853#dns.quad9.net
    forward-addr: 1.1.1.1@853#cloudflare-dns.com
    forward-addr: 1.0.0.1@853#cloudflare-dns.com

⚠️ With this config, Unbound is no longer recursive (forwards). Trade-off: less granular privacy, but upstream traffic encrypted.

sudo systemctl restart unbound

Step 6: Hybrid recursive + DoT

Recursive by default, forward sensitive zones:

forward-zone:
    name: "google.com"
    forward-tls-upstream: yes
    forward-addr: 1.1.1.1@853#cloudflare-dns.com

Step 7: Ad / malware blocking

sudo wget https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts -O /tmp/hosts
sudo grep "^0.0.0.0" /tmp/hosts | awk '{print "local-zone: \""$2"\" always_nxdomain"}' > /etc/unbound/unbound.conf.d/blocklist.conf
sudo systemctl restart unbound

Test:

dig @127.0.0.1 doubleclick.net

Should return NXDOMAIN.

Automate refresh:

sudo nano /usr/local/bin/update-blocklist.sh
#!/bin/bash
wget -q https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts -O /tmp/hosts
grep "^0.0.0.0" /tmp/hosts | awk '{print "local-zone: \""$2"\" always_nxdomain"}' > /etc/unbound/unbound.conf.d/blocklist.conf
systemctl reload unbound
sudo chmod +x /usr/local/bin/update-blocklist.sh
echo "0 4 * * 0 root /usr/local/bin/update-blocklist.sh" | sudo tee -a /etc/crontab

Step 8: Local zones (internal resolution)

server:
    local-zone: "example.lan." static
    local-data: "server1.example.lan. IN A 192.168.50.10"
    local-data: "server2.example.lan. IN A 192.168.50.11"
    local-data-ptr: "192.168.50.10 server1.example.lan"
    local-data-ptr: "192.168.50.11 server2.example.lan"

Step 9: Performance tuning

For high loads:

server:
    num-threads: 4
    msg-cache-slabs: 8
    rrset-cache-slabs: 8
    infra-cache-slabs: 8
    key-cache-slabs: 8
    
    msg-cache-size: 256m
    rrset-cache-size: 512m
    
    so-rcvbuf: 4m
    so-sndbuf: 4m
    so-reuseport: yes
    
    outgoing-range: 8192
    num-queries-per-thread: 4096

Step 10: Monitoring

remote-control:
    control-enable: yes
    control-interface: 127.0.0.1
    control-use-cert: no
sudo unbound-control stats

For Prometheus, use unbound_exporter.

Step 11: Outbound DNS over HTTPS (DoH)

Unbound 1.20+ supports outbound DoH via doq. For legacy DoH, use dnscrypt-proxy in front:

client → unbound (53) → dnscrypt-proxy (5353) → DoH upstream

Step 12: Anti-DDoS and rate limiting

server:
    ratelimit: 1000              # max qps per source IP
    ip-ratelimit: 500
    edns-buffer-size: 1232       # avoid UDP amplification
    do-not-query-localhost: yes

Troubleshooting

"SERVFAIL" on all domains

sudo unbound-checkconf
sudo unbound -dd

Slow resolution

Cache cold. Check:

sudo unbound-control stats | grep cachehits

Will be faster after a few min.

DNSSEC fails everywhere

sudo unbound-anchor -a /var/lib/unbound/root.key -v

Logs too verbose

server:
    verbosity: 0       # 0=silent, 1=normal, 4=detailed
    log-queries: no
    log-replies: no
    log-tag-queryreply: no

High memory consumption

Reduce caches:

server:
    msg-cache-size: 32m
    rrset-cache-size: 64m

Useful commands

sudo systemctl status unbound
sudo systemctl reload unbound
sudo systemctl restart unbound

sudo unbound-checkconf

sudo unbound-control stats
sudo unbound-control reset_cache
sudo unbound-control flush google.com

sudo journalctl -u unbound -f
sudo unbound -dd

dig @127.0.0.1 google.com
dig @127.0.0.1 +dnssec cloudflare.com
dig @127.0.0.1 +trace example.com

sudo unbound-anchor

Conclusion

Unbound gives you:

  • Private, recursive, DNSSEC-validated resolution
  • Outbound DoT for encryption
  • Ad/malware blocking
  • Performance and scalability

Going further:

  • Combine with dnscrypt-proxy for DoH
  • Use Pi-hole + Unbound (Pi-hole in front, Unbound behind)
  • For HA, deploy 2-3 Unbound + Keepalived

Resources

Join our Discord community server

For any questions, suggestions, or just to chat with the community, join us on Discord!

900+Members