
Move a site to a new host, watch a phone on mobile data reach the new server immediately, and then find the laptop in the next room still talking to the old one two days later. Flushing the browser cache does nothing. Flushing the system cache does nothing. Rebooting the router does nothing, because the stale answer is not on any machine in the building. It is sitting in an ISP’s resolver that decided the old record was good enough and ignored the expiry time you published. There is no button you get to press on somebody else’s cache. That is usually the point at which DNS stops looking like somebody else’s plumbing. Running a recursive resolver on your own network fixes that particular irritation, and it raises a second question worth sitting with: every domain every device in the house asks for is currently being handed, in plain text, to one company’s servers.
forwarding and resolving are not the same job


Almost every “use a better DNS server” guide is about forwarding. You type 1.1.1.1 or 9.9.9.9 into your router and your devices ask that company’s resolver for everything. It is fast, it is free, and it means one operator sees the full stream of names your household looks up.
Recursion is the other half of DNS, the part public resolvers do on your behalf. A recursive resolver starts from nothing and walks the tree. It asks a root server who is responsible for .com, then asks the com registry’s nameservers who is responsible for technobabu.com, then asks those for the address record. Nobody had to hand it an answer. Unbound does exactly this, on hardware you can put your hand on.
what changes in practice
Three things. Records you changed yourself expire when you said they should. You stop depending on one operator’s availability, which matters on Indian broadband, where an ISP resolver can go sideways while the link itself stays perfectly up. And the full list of what your house is curious about stops accumulating in one place outside your control.
the privacy argument, stated honestly

Self-hosted resolver enthusiasm often overstates the win. Resolving for yourself does not make lookups invisible, it redistributes who sees them. Instead of one operator holding your whole query history, each authoritative nameserver sees the queries for its own zone, arriving from your home address. You gain freedom from a single comprehensive observer and lose the cover of being one query among millions.
Plain DNS leaving your line is still unencrypted, too, so your ISP can observe the pattern of lookups your resolver makes. If hiding lookups from your ISP is the goal, a resolver at home is the wrong tool, as I explained in how DNS-level blocking actually works.
Pick the reason you are doing this before you start. “I want control over caching and validation” is a reason a recursive resolver serves well. “I want nobody to know what I browse” is not, and you will be disappointed.
where to run it, and how to install it



| Where you run it | Suits | Watch out for |
|---|---|---|
| Raspberry Pi 4 or 5 on the LAN | Most homes, alongside a filter | Needs a decent power supply and an SD card that is not three years old |
| Old laptop as a home server | Households that want more services on one box | Idle power draw, but the battery rides out short cuts |
| Router with OpenWrt | Minimal extra hardware | Flash and RAM are tight; cache stays small |
| Rented VPS | Nothing, for this purpose | Your lookups now originate from a datacentre, and an open resolver on the internet gets abused |
That last row matters. A resolver on a public IP that answers anyone is a reflection amplifier for somebody else’s attack traffic. Keep it on your LAN, or bound to localhost so only your filter can reach it. Sharing a Pi with Pi-hole is the arrangement I’d pick, and it is one of the setups in my Raspberry Pi 5 projects guide.
installing it next to pi-hole
The arrangement I recommend is Pi-hole on port 53 answering your devices, and Unbound on a private port answering only Pi-hole. Two processes, clean division of labour: one decides what to block, the other decides what the answer is.
sudo apt update
sudo apt install unbound
# Debian and Raspberry Pi OS ship a root hints file in a separate package
sudo apt install dns-root-data
# Config goes in its own file so package updates do not clobber it
sudo nano /etc/unbound/unbound.conf.d/pi-hole.conf
Using a drop-in file under unbound.conf.d rather than editing the main config means an upgrade can replace the packaged file and your settings survive untouched.
a minimal config, line by line

Here is a starting point adapted from Pi-hole’s Unbound guide, with each line explained below.
server:
verbosity: 0
interface: 127.0.0.1
port: 5335
do-ip4: yes
do-udp: yes
do-tcp: yes
do-ip6: no
prefer-ip6: no
harden-glue: yes
harden-dnssec-stripped: yes
use-caps-for-id: no
edns-buffer-size: 1232
root-hints: "/usr/share/dns/root.hints"
cache-max-ttl: 86400
prefetch: yes
serve-expired: yes
serve-expired-ttl: 86400
serve-expired-client-timeout: 1800
num-threads: 2
msg-cache-size: 64m
rrset-cache-size: 128m
access-control: 127.0.0.1/32 allow
access-control: 0.0.0.0/0 refuse
hide-identity: yes
hide-version: yes
private-address: 192.168.0.0/16
private-address: 169.254.0.0/16
private-address: 172.16.0.0/12
private-address: 10.0.0.0/8
private-address: fd00::/8
private-address: fe80::/10
- interface and port. Listening only on localhost, on a non-standard port, means nothing on your network can reach Unbound directly. Pi-hole is the only client it needs.
- do-ip6: no. Turn this on only if your ISP gives you working IPv6. Half-working IPv6 makes lookups stall before failing over, which is a common cause of a “slow DNS” complaint on a self-hosted resolver.
- harden-glue and harden-dnssec-stripped. Reject sloppy or tampered-looking responses instead of trusting them.
- edns-buffer-size. Large UDP responses get fragmented, and fragments get dropped by middleboxes. A conservative buffer avoids a whole class of intermittent failure.
- no cache-min-ttl. Unbound can impose a minimum cache time, but its manual warns that doing so keeps answers longer than the domain owner intended, which is the stale-record problem this guide set out to fix. Leave it at the default of 0. serve-expired-ttl and serve-expired-client-timeout limit stale answers to a day, and to cases where a fresh lookup is slow or failing.
- access-control. Explicitly refusing everything else is the line that stops you from accidentally running an open resolver.
- private-address. Refuses public answers that point at addresses inside your own network, a basic defence against DNS rebinding; these lines come from Pi-hole’s Unbound guide.
Check the file before you restart, because Unbound will refuse to start on a typo and you will have no DNS at all while you work out why:
sudo unbound-checkconf
sudo systemctl restart unbound
systemctl status unbound --no-pager
On Debian Bullseye or newer, including current Raspberry Pi OS, there is one more required step. The unbound package can install a helper that writes nameserver 127.0.0.1, without the 5335 port, into /etc/resolv.conf, which causes unexpected behaviour for Pi-hole and Unbound. Pi-hole’s Unbound guide has you disable it (skip a line if the service or file does not exist):
systemctl is-active unbound-resolvconf.service
sudo systemctl disable --now unbound-resolvconf.service
sudo sed -Ei 's/^unbound_conf=/#unbound_conf=/' /etc/resolvconf.conf
sudo rm /etc/unbound/unbound.conf.d/resolvconf_resolvers.conf
sudo systemctl restart unbound
dnssec validation, and proving it is on

This is the real payoff. DNSSEC lets a resolver check that an answer was signed by the zone that owns it and has not been altered in transit. A forwarding setup asks you to trust that the upstream validated properly. A validating resolver on your own LAN does the checking itself and refuses answers that fail.
The trust anchor is the root zone’s key. On Debian and Raspberry Pi OS the unbound package normally configures it already as an automatically updated anchor at /var/lib/unbound/root.key, which is why Pi-hole’s own Unbound config does not set it. Check with grep -r trust-anchor /etc/unbound/ before assuming validation is working, and do not add a second, static trust-anchor-file line for the same file.
# A signed zone. Look for the "ad" flag in the answer header.
dig @127.0.0.1 -p 5335 iana.org +dnssec | head -20
# flags: qr rd ra ad; <- "ad" means authenticated data
# Without validation you get no ad flag at all.
# A zone with a deliberately broken signature should fail, not resolve.
# A validating resolver returns SERVFAIL here, which is the correct answer.
dig @127.0.0.1 -p 5335 sigfail.verteiltesysteme.net
If the first shows ad and the second returns SERVFAIL, validation is running. If both resolve happily, something is forwarding around Unbound.
cache, prefetch, and the first-lookup trade-off
Be prepared for the cost. The first time your resolver meets a brand new domain it has to do the whole walk from the root, so there is a small one-time delay compared to a huge public resolver that almost certainly had that name cached already. You will notice it occasionally on a site nobody in your house has visited before.
Everything after that goes the other way. A cache hit is answered on your own LAN rather than across the country, and browsing feels noticeably snappier than going out to an ISP resolver every time. Two settings make the cost rarer: prefetch: yes refreshes popular entries just before they expire, and serve-expired: yes hands back a slightly stale answer at once while refreshing in the background.
pointing pi-hole at unbound
In the Pi-hole admin interface, open the DNS settings, clear every upstream provider checkbox, and set a custom upstream of 127.0.0.1#5335. Save. Do not tick “Use DNSSEC” in Pi-hole as well; Unbound is already validating, and doing it twice only adds work. If you have not set the filter up yet, my Pi-hole setup guide for Indian homes covers the router side, including the JioFiber boxes that will not let you change the DHCP DNS server.
Notice what this split does not change. Unbound decides what the correct answer is; Pi-hole decides which questions get a blocked answer instead. Your filtering policy stays exactly where it was, including the sites you have chosen to leave alone. Which lists to run, and how to tune them without breaking a payment page, is a whole subject of its own, covered in choosing and tuning DNS blocklists.
when it misbehaves, reach for dig

Almost every problem here is diagnosed by asking one component at a time and seeing which one lies to you.
# 1. Does Unbound itself answer?
dig @127.0.0.1 -p 5335 technobabu.com
# 2. Does Pi-hole answer, and with what?
dig @127.0.0.1 -p 53 technobabu.com
# 3. Watch the full delegation chain from the root
dig +trace technobabu.com
# 4. Is the cache doing any work?
sudo unbound-control stats_noreset | grep -E 'cache|num.queries'
# 5. What did the daemon complain about?
sudo journalctl -u unbound -n 40 --no-pager
unbound-control needs its keys generated once with sudo unbound-control-setup and a remote-control section enabling it on localhost. Worth the two minutes, because cache hit counts tell you quickly whether some device is going around your resolver entirely.
common mistakes
- Leaving a public resolver configured as a second upstream in Pi-hole “just in case”. Every answer that arrives through it skipped your validation, and you will never know which ones did.
- Running it on a single SD card with no plan for when the card wears out. Keep a copy of the config off the device.
- Forgetting the resolver is now a single point of failure. When it is down nothing resolves, and the complaints arrive within a minute.
FAQ
can i run unbound without pi-hole?
Yes. Set interface: 0.0.0.0 with an access-control line allowing only your LAN range, move it to port 53, and point your router’s DHCP at it. That gives you recursion, caching and validation with no filtering at all, which is a fine outcome if a better resolver was what you wanted. Unbound can hold a small blocklist itself through local-zone entries, but managing lists that way is far more manual.
does this survive a power cut?
The service comes back on boot, but the cache lives in memory and starts empty, so the first few minutes after power returns feel slower while it warms up. In a Patna summer with a few cuts a week, that is worth planning for. Nothing on the network resolves without this box, so it is the first thing I would put on an inverter.
should i add encrypted dns upstream as well?
Unbound can forward over TLS instead, but that converts it back into a forwarder: you trade your own recursion for encryption to a chosen upstream. Both are defensible, they are just different goals. Recursion at home and encrypted DNS on the devices that leave the house is the split I would pick.
Test validation properly before you declare victory. The payoff is not a dramatic speed number; it is that answers for your own network are now computed and checked on a machine you can unplug.
Update history: removed the minimum cache time and the static trust-anchor line from the config, bounded stale answers, and added the resolvconf step and private-address lines from Pi-hole’s Unbound guide.
Sources
- Unbound documentation, NLnet Labs — the authoritative reference for every configuration option used above, including cache, prefetch and hardening settings.
- Pi-hole guide to using Unbound as upstream — the current recommended port, drop-in config location and Pi-hole side settings.
- IANA root server list — where recursion begins, and the source of the root hints your resolver needs.