Recursive, Filtered DNS at Home
Run Unbound as your own recursive resolver behind Pi-hole, so your DNS queries go straight to the root servers instead of through a third party's resolver.
What you'll build and why
Pi-hole (the prerequisite guide) blocks ads and trackers at the DNS level, but it still has to ask someone for the actual answer to a non-blocked query -- by default, a third-party resolver like Cloudflare or Google. Unbound removes that third party: it's a validating, recursive, caching resolver that talks directly to the DNS root servers and walks the delegation chain itself, so no single upstream provider ever sees your full query history.
Don't build this if: you're satisfied with Pi-hole's default upstream (a reputable provider like Cloudflare or Quad9) and don't have a specific reason to remove that party from the picture -- this guide is a real but incremental privacy improvement, not a fix for a broken setup.
How it works
Without Unbound (Pi-hole's default):
Client -> Pi-hole -> third-party resolver (e.g. 1.1.1.1)
-> that resolver sees every
non-blocked query you make
With Unbound:
Client -> Pi-hole -> Unbound -> root servers, then the
relevant TLD server, then
the relevant authoritative
server -- directly, no
single party sees the
whole picture
Recursion means Unbound itself walks the chain: ask a root server who's authoritative for .com, ask that server who's authoritative for example.com, ask that server for the actual record. Each step only reveals part of the query to any one party, unlike sending the full query to one upstream resolver that sees everything.
Before you start
Decision: DNSSEC validation, on by default here. Unbound validates DNSSEC signatures where they exist, catching certain classes of DNS spoofing. This is a real security improvement with essentially no downside for a home setup -- this guide leaves it on.
Steps
Step 1: Install Unbound alongside Pi-hole
If running Pi-hole via Docker (per the prerequisite guide), add Unbound as a second service in the same Compose file:
unbound:
image: mvance/unbound:latest
container_name: unbound
ports:
- "5335:53/tcp"
- "5335:53/udp"
volumes:
- "./unbound-config:/opt/unbound/etc/unbound"
restart: unless-stopped
A widely used community Docker image for Unbound -- exposed on port 5335 to avoid conflicting with Pi-hole's own port 53 on the same host.
$ docker compose up -d
Step 2: Confirm Unbound resolves on its own
$ dig @127.0.0.1 -p 5335 example.com
Expected output: a real answer, confirming Unbound itself can resolve recursively before you point Pi-hole at it.
Step 3: Point Pi-hole at Unbound as its upstream
In Pi-hole's admin UI: Settings → DNS, under Upstream DNS Servers, uncheck any third-party provider and add a Custom upstream: 127.0.0.1#5335 (or Unbound's container address and port 5335 if running as a separate container without shared networking).
Verify it works
-
dig @127.0.0.1 -p 5335 example.comreturns a real answer directly from Unbound - Pi-hole's Query Log shows queries being resolved (confirm via Pi-hole's dashboard that DNS is still working end-to-end for a client device)
- DNSSEC validation is active:
dig @127.0.0.1 -p 5335 sigfail.verteiltesysteme.net(a deliberately DNSSEC-broken test domain) should fail to resolve, while a normal domain succeeds -- confirms validation is actually happening, not just configured - Failure test: stop Unbound (
docker stop unbound). Confirm Pi-hole (and therefore your whole network's DNS) stops resolving -- this is expected with a single Unbound instance and exactly why Go further covers adding a second one
Secure it
- What's exposed: Unbound's port 5335 should only be reachable from Pi-hole itself (or your LAN if you have another reason to query it directly) -- not the internet. This guide assumes Docker's default networking keeps it LAN-scoped; confirm that's actually true for your setup.
- The failure that hurts most: a single point of failure for your entire network's DNS resolution (the failure test above demonstrates this directly) -- the fix is redundancy (Go further), not avoiding this setup.
- Update strategy: Unbound ships security advisories on its own project page -- worth checking periodically given DNS resolvers are a meaningful piece of security-sensitive infrastructure.
Back it up and maintain it
What matters: Unbound's config directory (./unbound-config) -- typically small and simple enough that re-creating it from this guide is nearly as fast as restoring a backup, but back it up anyway if you've made custom changes beyond the defaults.
What to monitor: Unbound's own logs for resolution failures distinct from Pi-hole's blocking (a domain failing to resolve because Unbound can't reach it is a different problem than Pi-hole blocking it on purpose).
Troubleshooting
Logs: docker logs unbound; Pi-hole's own Query Log for the Pi-hole side of the chain.
| Symptom | Likely cause | Diagnostic | Fix |
|---|---|---|---|
| Unbound doesn't resolve anything, even directly | Port conflict, or the container can't reach the internet | docker logs unbound; confirm the container has network access |
Fix port mapping conflicts (5335 vs Pi-hole's own 53); confirm Docker networking allows outbound access |
| Pi-hole shows queries failing after switching to Unbound | Pi-hole pointed at the wrong address/port for Unbound | Re-check Settings → DNS → Upstream in Pi-hole against Unbound's actual address:port | Fix the upstream entry |
| Some domains resolve, others consistently fail | A domain-specific DNSSEC validation failure (the domain's own DNSSEC is misconfigured, not yours) | dig @127.0.0.1 -p 5335 <domain> -- check for a SERVFAIL specifically tied to DNSSEC |
This is the other domain's problem, not yours -- rare, but a small number of real domains do have broken DNSSEC |
| Resolution is noticeably slower than before | Expected on a cold cache -- recursive resolution walking the full chain is inherently slower than asking an upstream that already has it cached, until Unbound's own cache warms up | Compare repeated queries for the same domain (should get faster) vs. first-time queries (expected to be slower) | This is normal; if it never improves, check Unbound's cache configuration |
| DNSSEC validation test doesn't behave as expected | Unbound's DNSSEC validation isn't actually enabled (config issue) | Check Unbound's config for val-permissive-mode or missing trust anchor setup |
Fix per Unbound's own configuration documentation for DNSSEC |
Undo
Point Pi-hole's upstream back to a third-party provider (Settings → DNS), then docker compose down (or remove just the unbound service) to remove Unbound.
Go further
- Add a second Unbound instance and configure Pi-hole with both as upstreams for redundancy -- directly addresses the failure test above
- Enable DNS-over-TLS or DNS-over-HTTPS between Pi-hole and Unbound if they're on separate hosts, encrypting that hop too
- Query Name Minimisation (a privacy feature Unbound supports) limits how much of a query is revealed to each server in the resolution chain -- worth understanding once the basics work
Resources
Official documentation:
- Unbound project page -- overview and current version, verified this session
- Unbound documentation
Source and releases:
Community:
- Unbound mailing lists -- linked from the official project page
Related DaemonPress projects:
- Network-Wide Ad Blocking with Pi-hole -- the prerequisite this guide adds to
Last verified: 2026-09-21, checked against official Unbound project documentation.