Privacy Tools #Unbound#DNS#recursive resolver

Setting Up Unbound as a Local Recursive DNS Resolver

Install Unbound on Ubuntu or Debian as a local recursive DNS resolver with DNSSEC validation, root hints, and Pi-hole integration for full DNS privacy.

7 min read

Most DNS privacy guides stop at pointing your system to a privacy-respecting resolver like Cloudflare’s 1.1.1.1 or Quad9. That is an improvement over your ISP’s resolver, but you are still trusting a third party with your complete DNS query history. A recursive resolver eliminates that trust by doing the DNS resolution itself — starting from the root nameservers and traversing the DNS hierarchy directly to the authoritative nameserver for each domain. Unbound is the production-grade tool for this, trusted by ISPs, enterprises, and privacy-focused users alike.

What a Recursive Resolver Does

When you type hackingpc.com in your browser, a DNS lookup occurs:

  1. Your resolver asks a root nameserver: “Who handles .com?”
  2. The root nameserver returns the .com TLD nameservers.
  3. Your resolver asks a .com nameserver: “Who handles hackingpc.com?”
  4. The TLD nameserver returns Cloudflare (or wherever the domain is hosted).
  5. Your resolver asks Cloudflare: “What is the IP for hackingpc.com?”
  6. Cloudflare returns the IP address.

A forwarding resolver (1.1.1.1, 8.8.8.8) performs this process on your behalf — it handles steps 1–6 and returns the result. A recursive resolver performs steps 1–6 itself. No upstream provider sees your complete query history. The authoritative nameservers for each domain see individual queries, but no single entity aggregates all your lookups.

Installing Unbound on Ubuntu / Debian

sudo apt update
sudo apt install unbound

Stop the service temporarily while we configure it:

sudo systemctl stop unbound

If systemd-resolved is running on port 53, it will conflict with Unbound. Disable its DNS stub listener:

sudo sed -i 's/#DNSStubListener=yes/DNSStubListener=no/' /etc/systemd/resolved.conf
sudo systemctl restart systemd-resolved

Downloading Root Hints

Unbound needs the root hints file to know where to start the recursive lookup chain — the IP addresses of the 13 root nameserver clusters.

sudo curl -o /var/lib/unbound/root.hints https://www.internic.net/domain/named.root

Update this file every 6–12 months. The root nameserver IPs rarely change, but keeping the file current is good practice.

Configuring unbound.conf

The main configuration file is /etc/unbound/unbound.conf. Replace its contents (or create /etc/unbound/unbound.conf.d/privacy.conf) with:

server:
    # Network
    interface: 127.0.0.1
    port: 53
    do-ip4: yes
    do-ip6: no
    do-udp: yes
    do-tcp: yes

    # Access control
    access-control: 127.0.0.0/8 allow
    access-control: 0.0.0.0/0 refuse

    # Root hints
    root-hints: "/var/lib/unbound/root.hints"

    # DNSSEC validation
    auto-trust-anchor-file: "/var/lib/unbound/root.key"

    # Privacy settings
    hide-identity: yes
    hide-version: yes
    qname-minimisation: yes
    aggressive-nsec: yes

    # Performance
    prefetch: yes
    prefetch-key: yes
    num-threads: 2
    cache-min-ttl: 3600
    cache-max-ttl: 86400

    # Logging (minimal for privacy)
    verbosity: 1
    log-queries: no
    log-replies: no

Key settings explained:

  • qname-minimisation: yes — Instead of sending the full query (hackingpc.com) to root nameservers, sends only the TLD (.com). This reduces information leakage at each step of the recursive process.
  • aggressive-nsec: yes — Uses DNSSEC NSEC records to answer negative queries locally, reducing round trips.
  • hide-identity / hide-version — Prevents Unbound from revealing its version and hostname to queriers.
  • prefetch: yes — Prefetches popular cached records before they expire, reducing latency.

DNSSEC Validation

Unbound validates DNSSEC signatures by default when auto-trust-anchor-file is set. Initialize the trust anchor:

sudo unbound-anchor -a /var/lib/unbound/root.key
sudo chown unbound:unbound /var/lib/unbound/root.key

DNSSEC prevents DNS spoofing by verifying that DNS responses are signed by the authoritative nameserver’s private key. A tampered response fails validation and is rejected.

Starting Unbound

sudo systemctl enable unbound
sudo systemctl start unbound

Check status:

sudo systemctl status unbound

Look for active (running). If it fails, check logs:

sudo journalctl -u unbound -n 50

Testing with dig

Verify Unbound is responding on localhost:

dig @127.0.0.1 google.com

Expected output includes:

;; SERVER: 127.0.0.1#53(127.0.0.1)

Test DNSSEC validation:

dig @127.0.0.1 sigfail.verteiltesysteme.net

This domain is deliberately signed with an invalid DNSSEC signature. Unbound should return SERVFAIL, confirming validation is active.

Test a valid DNSSEC domain:

dig @127.0.0.1 dnssec-tools.org +dnssec

Look for the ad flag (Authenticated Data) in the response flags line:

;; flags: qr rd ra ad; QUERY: 1, ANSWER: 2

The ad flag confirms the response was DNSSEC-validated.

Pointing System DNS to Unbound

NetworkManager (most Linux desktops)

nmcli connection modify "Your Connection" ipv4.dns "127.0.0.1" ipv4.ignore-auto-dns yes
nmcli connection up "Your Connection"

Direct resolv.conf edit

echo "nameserver 127.0.0.1" | sudo tee /etc/resolv.conf

Prevent NetworkManager from overwriting it:

sudo chattr +i /etc/resolv.conf

Remove the immutable flag when you need to make changes: sudo chattr -i /etc/resolv.conf.

Combining Pi-hole with Unbound

Pi-hole blocks ads and trackers at the DNS level. Combined with Unbound, you get ad-blocking plus recursive resolution:

Client → Pi-hole (port 53) → Unbound (port 5335) → Root nameservers

Configure Unbound to listen on port 5335 instead of 53:

server:
    interface: 127.0.0.1
    port: 5335

Restart Unbound:

sudo systemctl restart unbound

In Pi-hole’s admin interface (http://pi.hole/admin):

  1. Go to Settings > DNS.
  2. Uncheck all upstream DNS providers.
  3. Under Custom 1 (IPv4), enter 127.0.0.1#5335.
  4. Save.

Pi-hole now forwards all non-blocked queries to Unbound, which resolves them recursively. Blocked domains return Pi-hole’s block page; clean domains are resolved recursively without passing through any third-party DNS provider.

Performance Comparison

Resolver TypeLatency (cold)Latency (cached)Privacy
ISP DNS (plain)5–20ms2–5msNone
Cloudflare 1.1.1.1 (DoH)10–30ms1–5msTrusts Cloudflare
Unbound (recursive)50–200ms0.1–1msNo upstream trust
Pi-hole + Unbound50–200ms0.1–1msNo upstream trust + ad blocking

The cold query latency for Unbound is higher because it performs the full DNS traversal rather than querying a pre-populated resolver cache. However, prefetch: yes and caching mean subsequent queries for popular domains return in under a millisecond from local cache.

For most users, the latency difference is imperceptible in normal browsing. DNS lookups are a small fraction of total page load time, and the cache quickly populates with frequently visited domains.

Maintenance

Update root hints periodically:

sudo curl -o /var/lib/unbound/root.hints https://www.internic.net/domain/named.root
sudo systemctl restart unbound

Update the DNSSEC trust anchor if it changes (Unbound handles this automatically via the built-in RFC 5011 rollover mechanism when auto-trust-anchor-file is configured).

Check Unbound’s cache statistics:

sudo unbound-control stats_noreset | grep total

Running your own recursive resolver removes the last significant third-party from your DNS traffic. Combined with Pi-hole’s filtering, it is the most complete local DNS privacy solution available without external service dependencies.

#Pi-hole #DNSSEC #recursive resolver #DNS #Unbound