Pi-hole + Unbound Behind Traefik with a Clean /admin Redirect

How this homelab publishes Pi-hole admin via Traefik while keeping DNS local, with practical hardening steps for the risky defaults.

HomeLab

DNS is the most overlooked piece of homelab security. You can harden every container, lock down every reverse proxy route, and still be leaking every domain your network touches to a third-party resolver. Running your own recursive DNS with Pi-hole and Unbound keeps that data local and gives you a policy enforcement point that most homelab setups skip entirely.

TL;DR

  • This stack runs Pi-hole + Unbound in one container (mpgirro/pihole-unbound:2025.11.1) and exposes only the admin UI through Traefik.
  • A Traefik redirect middleware sends / to /admin/, so users land in the right place without app-side rewrite logic.
  • DNS still binds to host port 53 (tcp and udp), so firewall boundaries matter more than reverse-proxy polish.
  • Security posture is workable but unfinished: listeningMode = "ALL", query logging retention, and broad Linux capabilities need tighter control.

What is Pi-hole?

Pi-hole is a network-level ad blocker that acts as your LAN’s DNS sinkhole. Point your clients or router at it, and it filters out ad and tracking domains before they ever resolve. It also gives you a clean admin dashboard for monitoring query traffic across your entire network. For a deeper introduction to both tools, see our post on Pi-hole + Unbound and why they replace your ISP’s DNS.

What is Unbound?

Unbound is a validating, recursive DNS resolver. Instead of forwarding your queries to Google or Cloudflare, Unbound resolves them directly against authoritative nameservers. Paired with Pi-hole, it means your DNS queries never leave your network until they hit the root servers — no third-party resolver sees your full query history.

Evidence used

This post is based on:

  • docker/pihole-unbound/compose.yaml
  • docker/pihole-unbound/etc-pihole/pihole.toml
  • docker/pihole-unbound/etc-pihole/dnsmasq.conf
  • docs/deployment-guide.md

What this design is doing

The container handles both ad-blocking DNS and recursive resolution:

services:
  pihole:
    image: mpgirro/pihole-unbound:2025.11.1
    ports:
      - '53:53/tcp'
      - '53:53/udp'
    cap_add:
      - NET_ADMIN
      - SYS_NICE
      - SYS_TIME

Two things matter here:

  1. DNS is intentionally bound on host port 53 for LAN clients.
  2. The admin UI is not published with a host port in Compose; it is routed through Traefik.

Admin UI through Traefik + redirect middleware

The Compose labels include both the main router and a root-path redirect:

labels:
  - traefik.http.routers.pihole.rule=Host(`pihole.subdepthtech.org`)
  - traefik.http.routers.pihole.entrypoints=https
  - traefik.http.routers.pihole.tls=true
  - traefik.http.services.pihole.loadbalancer.server.port=80
  - traefik.http.routers.pihole-root.rule=Host(`pihole.subdepthtech.org`) && Path(`/`)
  - traefik.http.routers.pihole-root.middlewares=pihole-admin-redirect
  - traefik.http.routers.pihole-root.priority=100
  - traefik.http.middlewares.pihole-admin-redirect.redirectregex.regex=^.*$
  - traefik.http.middlewares.pihole-admin-redirect.redirectregex.replacement=https://pihole.subdepthtech.org/admin/
  - traefik.http.middlewares.pihole-admin-redirect.redirectregex.permanent=true

This is practical and clean: users do not need to remember /admin/, and the redirect is explicit in edge routing.

DNS behavior from repo config

From pihole.toml and generated dnsmasq.conf, this instance is configured to recurse through local Unbound:

[dns]
upstreams = ["127.0.0.1#5335"]
listeningMode = "ALL"
queryLogging = true

And the generated dnsmasq config confirms:

no-resolv
server=127.0.0.1#5335
port=53
# Listen on all interfaces, permit all origins
except-interface=nonexisting

So the architecture is coherent: Pi-hole is the policy engine, Unbound is the upstream resolver, and no public upstream resolvers are used in this path.

Are you still forwarding to a public resolver like 1.1.1.1 or 8.8.8.8? If so, consider what you gain by switching to local recursion with Unbound — and what tradeoffs in latency and reliability you might need to plan for.

Deployment and verification commands

Using the repo’s deployment workflow:

./scripts/deploy.sh all --dry-run
./scripts/deploy.sh pihole-unbound

Quick checks after deploy:

# Container state

docker --context homelab-remote compose -f docker/pihole-unbound/compose.yaml ps

# DNS answer path

dig @<lan-dns-ip> example.com

# Admin route + redirect

curl -Ik https://pihole.subdepthtech.org/
curl -Ik https://pihole.subdepthtech.org/admin/

Lessons learned

  1. Router + middleware labels can simplify UX without adding app complexity.
  2. DNS security is mostly network security; TLS on the admin panel does not protect an exposed resolver.
  3. Pi-hole config drift is easy when generated files and source-of-truth files diverge. Treat pihole.toml as primary.
  4. Pinning an image tag is good; patch cadence still matters for infrastructure services.

What I’d do differently

  1. Restrict DNS ingress to trusted LAN segments only at firewall/router level instead of relying on container defaults.
  2. Revisit listeningMode = "ALL"; prefer the least permissive mode that still serves required clients.
  3. Add explicit access control in front of admin UI (for example, Authelia or IP allowlists), not just host-based routing.
  4. Keep capabilities minimal and validate whether all three (NET_ADMIN, SYS_NICE, SYS_TIME) are truly required in this environment.
  5. Add a periodic config audit that compares intended settings (pihole.toml) against effective runtime behavior.

Security notes

  • Exposing host port 53 is high impact; treat it as network-infrastructure exposure, not just another app port.
  • listeningMode = "ALL" can become an open-resolver risk if perimeter controls are weak.
  • Query logging is useful for troubleshooting but increases retention sensitivity; define a retention policy.
  • Traefik protects the admin UI transport path, but DNS traffic itself is still unauthenticated UDP/TCP unless you explicitly add encrypted DNS layers.
  • Avoid publishing private host/IP mappings in public docs; use sanitized examples (10.x.x.x) for write-ups.

Summary

  • Pi-hole + Unbound in a single container gives you ad-blocking and recursive DNS without relying on any third-party resolver.
  • Traefik redirect middleware cleanly routes the admin UI without app-side rewrites or exposing extra host ports.
  • Host port 53 is network-infrastructure exposure — treat it accordingly with firewall rules, not just container defaults.
  • listeningMode = "ALL" and broad Linux capabilities are workable starting points, but both need tightening for a hardened setup.
  • Config drift between pihole.toml and generated files is a real operational risk. Treat the TOML file as your source of truth.

Share Your DNS Stack

How are you handling DNS in your homelab? Running Pi-hole with Unbound, forwarding to a public resolver, or something else entirely? If you have found good solutions for the latency tradeoffs of local recursion or have tips on locking down listeningMode, share what has worked for you.