Most home networks send all DNS queries to an ISP resolver or a third-party service like Cloudflare or Google. Running your own recursive resolver eliminates that dependency: your queries go directly to the authoritative name servers for each domain, no intermediary logs them, and you can validate responses with DNSSEC. Layering Pi-hole on top gives you network-wide ad and tracker blocking without per-device configuration.
Architecture
The setup has two layers:
- Unbound runs as a recursive, caching resolver on
127.0.0.1:5335. It communicates directly with root servers and authoritative name servers, validates DNSSEC, and caches results. - Pi-hole listens on port 53, applies blocklists, and forwards all non-blocked queries to Unbound on port 5335.
This is preferable to Pi-hole forwarding to an upstream public resolver because your DNS traffic never leaves your network for resolution.
1. Install Unbound
apt update && apt install -y unbound
Download the root hints file (the list of root DNS servers):
curl -o /var/lib/unbound/root.hints https://www.internic.net/domain/named.root # Schedule this for periodic refresh (quarterly is fine) systemctl enable --now unbound
2. Configure Unbound
Create /etc/unbound/unbound.conf.d/pi-hole.conf:
server: verbosity: 1 interface: 127.0.0.1 port: 5335 do-ip4: yes do-udp: yes do-tcp: yes do-ip6: no # Root hints root-hints: /var/lib/unbound/root.hints # Security harden-glue: yes harden-dnssec-stripped: yes harden-algo-downgrade: yes use-caps-for-id: yes # DNSSEC validation auto-trust-anchor-file: /var/lib/unbound/root.key # Cache cache-min-ttl: 3600 cache-max-ttl: 86400 prefetch: yes prefetch-key: yes num-threads: 2 # Privacy hide-identity: yes hide-version: yes qname-minimisation: yes # Access access-control: 127.0.0.1/32 allow
Fetch and initialise the DNSSEC trust anchor:
unbound-anchor -a /var/lib/unbound/root.key systemctl restart unbound
Test that Unbound resolves and validates correctly:
# Should return SERVFAIL (DNSSEC validation failure on a deliberately broken domain) dig dnssec-failed.org @127.0.0.1 -p 5335 # Should return a valid A record with AD flag set dig kernel.org @127.0.0.1 -p 5335 | grep -E "status:|flags:"
The AD flag in the dig output means “Authenticated
Data” — Unbound has validated the DNSSEC chain for this
response.
3. Install Pi-hole
Pi-hole has a one-liner installer. Audit it before running:
curl -sSL https://install.pi-hole.net -o pihole-install.sh less pihole-install.sh # Review the script bash pihole-install.sh
During the installer:
- Select your network interface.
- Set any upstream DNS for now — you will change this to Unbound immediately after.
- Enable the web interface if you want the admin dashboard.
4. Point Pi-hole at Unbound
Edit /etc/pihole/setupVars.conf and set:
PIHOLE_DNS_1=127.0.0.1#5335 PIHOLE_DNS_2=
Then restart the Pi-hole DNS service:
pihole restartdns pihole status
Verify the chain works end to end:
dig kernel.org @127.0.0.1
5. Configure clients to use Pi-hole
The simplest approach is to set your router to hand out the Pi-hole’s IP as the DNS server via DHCP. On an OPNsense or pfSense router:
- Go to Services → DHCP Server for your LAN interface.
- Set DNS servers to the static IP of your Pi-hole host.
Alternatively, for a subset of machines, configure DNS statically:
# On Debian/Ubuntu with systemd-resolved sudo mkdir -p /etc/systemd/resolved.conf.d/ cat <<EOF | sudo tee /etc/systemd/resolved.conf.d/custom-dns.conf [Resolve] DNS=192.168.10.53 FallbackDNS= DNSStubListener=no EOF sudo systemctl restart systemd-resolved
6. Updating blocklists
Pi-hole fetches blocklists during install and when you run
pihole -g. Schedule automatic updates:
# Add to crontab (crontab -e) 0 3 * * 0 pihole -g > /var/log/pihole-update.log 2>&1
Useful additional blocklists beyond the default:
https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts(Steven Black’s consolidated list)https://blocklistproject.github.io/Lists/tracking.txt
Add them via the Pi-hole web interface under Adlists, or directly in the database:
sqlite3 /etc/pihole/gravity.db "INSERT INTO adlist (address, enabled) VALUES ('https://...', 1);"
pihole -g
7. Monitoring resolution failures
Pi-hole’s dashboard shows query statistics. For Unbound, enable logging to watch resolution behaviour:
# In /etc/unbound/unbound.conf.d/pi-hole.conf, add: server: verbosity: 2 log-queries: yes logfile: /var/log/unbound/unbound.log
mkdir -p /var/log/unbound chown unbound:unbound /var/log/unbound systemctl restart unbound journalctl -u unbound -f