Pi-hole DNS

Installed April 19, 2026 on CT102 at 192.168.8.53. Serves DNS + ad blocking to the entire LAN and all NetBird peers.

Summary
Container CT102 pihole
IP 192.168.8.53
Version Pi-hole v6.4.1 (FTL 6.6, Web 6.5)
Upstream DNS Unbound on CT100 (192.168.8.100:5335, primary recursive resolver) + Cloudflare (1.1.1.1) + Quad9 (9.9.9.9) as fallbacks
Blocklists StevenBlack unified (87,771 domains)
Admin UI https://pihole.edmd.me (alias: dns.edmd.me)
Raw admin http://192.168.8.53/admin/
Container root password /root/pihole-root-password.txt on Proxmox
Web admin password /root/pihole-admin-password.txt on Proxmox
Who uses Pi-hole
  • All LAN devices โ€” router’s DHCP hands out 192.168.8.53 as primary DNS
  • All NetBird peers โ€” NetBird web UI โ†’ Networks โ†’ Nameservers โ†’ 192.168.8.53 registered

Any device connected to home wifi OR to the NetBird mesh gets ad blocking + short names automatically. No per-device config.

Local DNS records (short names)

Pi-hole resolves friendly short names for every major LAN service. Adds both bare and .home variants.

Infrastructure: hpve, proxmox, ct100, docker-host, ct101, immich, studio, mac-studio, pihole, vps, edge01

Services (all on CT100 at 192.168.8.100): portainer, plex, calibre, lidarr, sonarr, radarr, prowlarr, bookshelf, audiobookshelf, navidrome, n8n, kuma, uptime-kuma, gotify, freshrss, homepage, convertx, readarr, shelfmark, paperless, paperless-ai

Farm network: farm-pve (192.168.0.191), ha / home-assistant (192.168.0.10), slzb (192.168.0.16)

Bare names (no suffix) work from NetBird peers because they append .netbird.cloud as a search domain. For LAN devices where the router drops bare names, use .home suffix โ€” e.g. http://portainer.home:9443. For real HTTPS with clean URLs, use .edmd.me via Caddy on CT103 โ€” e.g. https://portainer.edmd.me.

DNS Architecture

Resolution chain: Client โ†’ Router (192.168.8.1) โ†’ Pi-hole (CT102, 192.168.8.53) โ†’ Unbound (CT100, 192.168.8.100:5335) โ†’ root servers

Unbound is a recursive resolver running as a Docker container on CT100 (mvance/unbound). It resolves queries directly from the DNS root servers rather than forwarding to Cloudflare or Quad9 โ€” this means no single upstream provider sees all your DNS queries. Pi-hole uses Unbound as its primary upstream with Cloudflare (1.1.1.1) and Quad9 (9.9.9.9) configured as fallbacks.

Config: Custom unbound.conf mounted at /opt/unbound/conf/unbound.conf (overrides the image’s auto-generated config). Key settings: msg-cache-size: 50m, rrset-cache-size: 100m, num-threads: 2, log-servfail: yes, verbosity: 1. DNSSEC validation enabled.

May 10 2026 fix: The mvance/unbound Docker image auto-calculates cache sizes from the host’s total RAM. Running inside a memory-limited LXC container (CT100), it was allocating ~33GB for DNS caching, causing periodic OOM and SERVFAIL responses. Pi-hole cached those SERVFAILs instead of falling back to working upstreams. The old health check (drill @127.0.0.1 google.com) was useless because SERVFAIL counted as a valid response. Fixed by:

  1. Mounting a custom unbound.conf with sane cache sizes
  2. Updating the Docker health check to verify NOERROR status: drill @127.0.0.1 google.com | grep -q 'NOERROR'
  3. Enabling log-servfail: yes and verbosity: 1 for future debugging
  4. Flushing Pi-hole’s cached SERVFAILs via systemctl restart pihole-FTL
Managing Pi-hole

Add a new short-name DNS record โ€” use the web UI (Settings โ†’ Local DNS โ†’ DNS Records) or the API:

# From Proxmox host
ADMIN_PW=$(cat /root/pihole-admin-password.txt)
SID=$(curl -sk -X POST http://192.168.8.53/api/auth \
  -H "Content-Type: application/json" \
  -d "{\"password\":\"$ADMIN_PW\"}" | grep -oE '"sid":"[^"]+"' | cut -d'"' -f4)

curl -sk -X PUT "http://192.168.8.53/api/config/dns/hosts/192.168.8.100%20newservice?sid=$SID"

Update blocklists โ€” web UI โ†’ Gravity โ†’ “Update”
Or CLI from within the container:

pct exec 102 -- pihole -g

View query log โ€” web UI โ†’ Query Log
Or from container: tail -f /var/log/pihole/pihole.log

Reload after config changes โ€” pct exec 102 -- pihole reloaddns

Gotchas
  • Pi-hole v6 uses embedded FTL as web server (NOT lighttpd โ€” unlike v5). Port 80 and 443 handled by pihole-FTL.
  • v6 migrated from setupVars.conf to /etc/pihole/pihole.toml.
  • Custom DNS records live at /etc/pihole/hosts/custom.list but Pi-hole v6 regenerates this file on some config changes. Use the admin UI or API to add records โ€” do not edit the file directly.
  • Loading custom dnsmasq directives โ€” set misc.etc_dnsmasq_d = true in pihole.toml, then drop config into /etc/dnsmasq.d/. The *.edmd.me wildcard (/etc/dnsmasq.d/03-edmd-wildcard.conf โ†’ address=/edmd.me/192.168.8.54) is loaded this way. NOTE: when etc_dnsmasq_d=false (the default), files in /etc/dnsmasq.d/ are silently ignored. We’ve changed our default to true after the Apr 29 cutover.
  • The bogusPriv=true setting in pihole.toml rejects RFC1918 answers from upstream โ€” but does NOT affect locally-configured wildcards in /etc/dnsmasq.d/. So the wildcard works even with bogusPriv on.
  • .netbird.cloud names are NOT resolved by Pi-hole โ€” NetBird client intercepts those on each peer locally. Don’t try to forward .netbird.cloud from Pi-hole (CT102 can’t reach NetBird’s internal DNS at 100.123.31.199 because CT102 isn’t a NetBird peer).