Ramblings of an aging IT geek
← Ramblings of an aging IT geek
networking

the day i stopped trusting someone else's resolver

Why I built a recursive DNS resolver on the homelab with Unbound, how I wired up DNSSEC and prefetching, and what it actually bought me.

Network patch panel with bundled cables

For years my home network pointed at whatever DNS my ISP handed out on the DHCP lease, and on a good day I never thought about it. Then there were the other days. A resolver that returned SERVFAIL for ten minutes at a time. A "helpful" wildcard page when a domain didn't exist, which broke half my scripts that relied on NXDOMAIN actually meaning the name doesn't exist. And the growing suspicion that every lookup I made was being logged, aggregated, and sold by someone whose name I'd never know.

So I did the thing every homelab eventually does. I stopped forwarding to someone else and started resolving for myself.

forwarding versus resolving

This is the bit people gloss over, so it's worth being precise. Most home setups, including Pi-hole out of the box, are forwarders. They take your query and hand it on to an upstream like 8.8.8.8 or 1.1.1.1, then cache the answer. You've moved the problem one hop, not removed it. Google or Cloudflare still see everything.

A recursive resolver does the legwork itself. It starts at the root servers, asks who's authoritative for .pm, then who's authoritative for i0.pm, and walks the delegation chain down to the answer. No single upstream sees the full picture of what you're looking up, and you're not trusting anyone else's cache or politics.

Datacenter aisle with racks of equipment

The tool for this is Unbound. It's small, it's been audited to within an inch of its life, and it does one job properly. NLnet Labs write the sort of software I trust by default, which is rare praise from me.

the config

Here's the core of /etc/unbound/unbound.conf on the box. I've trimmed the comments.

server:
    interface: 0.0.0.0
    port: 53
    access-control: 10.0.0.0/8 allow
    access-control: 127.0.0.0/8 allow

    do-ip6: no
    hide-identity: yes
    hide-version: yes
    harden-glue: yes
    harden-dnssec-stripped: yes
    use-caps-for-id: yes

    # cache tuning for a small but chatty network
    cache-min-ttl: 120
    cache-max-ttl: 86400
    prefetch: yes
    prefetch-key: yes

    msg-cache-size: 128m
    rrset-cache-size: 256m
    num-threads: 2

    # validate against the root trust anchor
    auto-trust-anchor-file: "/var/lib/unbound/root.key"

    private-address: 10.0.0.0/8
    private-address: 192.168.0.0/16

Two settings earn their keep here. prefetch: yes means Unbound refreshes a popular record before its TTL expires, so the hot names in my house (the usual suspects) are almost always served warm. And auto-trust-anchor-file plus the harden-* lines turn on DNSSEC validation, so a forged answer for a signed zone gets thrown out rather than handed to me.

The first cold start is genuinely slow. Every name is a fresh recursion from the root, and you feel it. Give it a day of normal use and the cache fills with the things you actually visit. After that, p50 lookup latency on my LAN sits around 1ms for cached names, and the misses are bounded by however far away the authoritative server happens to be.

wiring it into the network

I run Unbound on the same small box as everything else, and the router hands it out as the only resolver over DHCP. The one rule I'd give anyone doing this: do not point clients at a public resolver "as a backup". The moment Unbound hiccups, every device fails over to 8.8.8.8, your DNSSEC validation quietly stops happening, and you've defeated the entire exercise without noticing. If your resolver is down, I'd rather DNS be down so I go and fix it.

Datacenter cabinet with structured cabling

I do keep Pi-hole in front for blocklists, because the two jobs are different. Pi-hole decides whether I'm allowed to resolve a name. Unbound does the actual recursion behind it. Pi-hole's upstream is set to 127.0.0.1#5335 and Unbound listens there. Belt and braces, each doing the thing it's good at.

was it worth it

Honestly, yes, and not for the reasons I expected. The privacy argument is real but abstract. What I actually notice day to day is that DNS just behaves. NXDOMAIN means the name doesn't exist. Signed zones are validated. Nothing injects a search page. When something breaks, it breaks on a box I own and can tail -f the logs of, rather than in a black hole two ISPs away.

The total cost was an afternoon and about 60MB of RAM. The total saving is never having to wonder, when a lookup goes strange, whether it's me or them. It's always me now, and that's oddly comforting.