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.
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.
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.