Pi-hole + Unbound: Local Recursive DNS

Block ads network-wide, run your own recursive resolver, and stop leaking your DNS history to upstream providers.

What This Buys You

  • Network-wide ad and tracker blocking for every device — phones, TVs, IoT junk that has no other way to block.
  • Local DNS for your homelab services, so you can use proxmox.lab instead of memorizing IPs.
  • Recursive resolution via Unbound — queries go directly to root and authoritative servers, not through Cloudflare/Google/your ISP.
  • Query visibility: see what every device on your network is asking for. Surprising and educational.

Architecture

The flow is straightforward but worth seeing once:

client device  →  Pi-hole (filter + cache)  →  Unbound (recursive)  →  root + TLD + authoritative servers

Pi-hole handles blocklists, local DNS records, and per-client logging. Unbound does the actual recursion. They run on the same host and talk over loopback.

Deployment Options

  • LXC container on Proxmox: Lightweight, easy to back up, survives host reboots cleanly. Recommended.
  • Dedicated Pi or thin client: Simple, but a single point of failure. If you go this route, plan for HA.
  • Docker compose: Works but DNS in containers has edge cases (especially with systemd-resolved on the host). Bind it to a specific IP, not all interfaces.

Install: Pi-hole + Unbound on Debian/Ubuntu

# Pi-hole — uses the official one-step script
curl -sSL https://install.pi-hole.net | bash

# Unbound
sudo apt install unbound dns-root-data

During Pi-hole setup, when asked for an upstream DNS provider, pick anything — you will override it to point at Unbound in a minute.

Configure Unbound

Drop this into /etc/unbound/unbound.conf.d/pi-hole.conf:

server:
    verbosity: 0
    interface: 127.0.0.1
    port: 5335
    do-ip4: yes
    do-udp: yes
    do-tcp: yes
    do-ip6: no

    root-hints: "/usr/share/dns/root.hints"

    harden-glue: yes
    harden-dnssec-stripped: yes
    use-caps-for-id: no

    edns-buffer-size: 1232
    prefetch: yes
    num-threads: 1
    so-rcvbuf: 1m

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

Restart and verify:

sudo systemctl restart unbound
dig pi-hole.net @127.0.0.1 -p 5335
# Look for status: NOERROR and a sensible answer.

Point Pi-hole at Unbound

In the Pi-hole admin UI → Settings → DNS:

  • Uncheck every upstream provider.
  • Under Custom 1 (IPv4), enter 127.0.0.1#5335.
  • Save.

Test from a client: dig pi-hole.net @192.168.1.5 (your Pi-hole IP). The first query for a domain will take ~100ms; subsequent ones return from cache in single-digit ms.

Point Your Network at Pi-hole

Two options, in order of preference:

  1. DHCP option: On your router/firewall, set the DNS server pushed via DHCP to your Pi-hole IP. Every device gets it on next lease renewal.
  2. Run DHCP on Pi-hole itself if your router is too dumb to push custom DNS. Disable DHCP on the router first to avoid conflicts.

Avoid setting upstream DNS at the router level only. Some devices (Chromecast, Android, smart TVs) hardcode 8.8.8.8 and ignore DHCP. Block outbound port 53 to anything except your Pi-hole at the firewall to force them.

Local DNS Records for Lab Services

In Local DNS → DNS Records, add entries for your services:

  • proxmox.lab → 192.168.1.100
  • nas.lab → 192.168.1.110
  • npm.lab → 192.168.1.120

Pick a TLD that does not exist on the public internet. .lab, .home.arpa, or your own subdomain (home.example.com) are all fine. Do not use .local — it conflicts with mDNS and will cause weird intermittent failures on Apple devices.

Blocklists: Less Is More

Pi-hole ships with a sensible default blocklist (Steven Black's unified list). Resist the urge to pile on a dozen more — over-blocking breaks legitimate sites and you end up disabling Pi-hole entirely to debug it.

  • Start with defaults. Add more only when you have a specific tracker you want gone.
  • Use group management to exempt specific clients (a media TV that breaks with strict blocking) instead of disabling globally.
  • Whitelist proactively: common false positives include payment processors, telemetry endpoints needed by Steam/Discord, and CDN paths used by login flows.

High Availability

DNS down means your entire network is "broken" to most users. Plan for it:

  • Two Pi-holes on different hosts. Push both via DHCP as primary and secondary. Use gravity-sync or the newer Pi-hole v6 native sync to keep blocklists and local records aligned.
  • keepalived with a VIP if you want clients to see a single DNS IP and fail over automatically. More complex but cleaner.
  • Backup the /etc/pihole directory. Settings, whitelists, and local DNS records all live there. Restoring is a tarball restore + reinstall.

Privacy and DNS-over-HTTPS

With Unbound doing recursion you are already not sending queries to a centralized provider. If you want to be reachable from outside your network without exposing port 53 to the world, run a DoH proxy (cloudflared in DoH client mode pointing at Pi-hole, or doh-server in front of Unbound). Pair this with the WireGuard hub guide so remote clients can hit Pi-hole over the tunnel and get the same blocking protection on the go.

Common Pitfalls

  • Forgetting IPv6: If your network advertises IPv6, devices will use it for DNS and bypass Pi-hole. Either disable IPv6 RA or run Pi-hole on IPv6 too.
  • systemd-resolved conflicts on the Pi-hole host: It binds port 53. Disable it before installing Pi-hole or the install script will refuse.
  • Logs growing unbounded: Set query log retention under Settings → Privacy. 24-hour rolling log is usually plenty.
  • Blocking your own services: Telemetry domains for software you actually use (printer drivers, smart home apps). Check the query log when something breaks suddenly.

Validation Checklist

  • dig pi-hole.net @127.0.0.1 -p 5335 resolves via Unbound
  • dig flurry.com @<pi-hole-ip> returns 0.0.0.0 (blocked)
  • dig proxmox.lab @<pi-hole-ip> returns your local IP
  • Test client gets Pi-hole as DNS via DHCP (nslookup shows it as server)
  • Pi-hole admin UI shows queries from multiple clients
  • Backup of /etc/pihole exists and is part of your scheduled job

- Crafted by Axiom|Spectre