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.
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(tcpandudp), 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.
Evidence used
This post is based on:
docker/pihole-unbound/compose.yamldocker/pihole-unbound/etc-pihole/pihole.tomldocker/pihole-unbound/etc-pihole/dnsmasq.confdocs/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:
- DNS is intentionally bound on host port
53for LAN clients. - 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.
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
- Router + middleware labels can simplify UX without adding app complexity.
- DNS security is mostly network security; TLS on the admin panel does not protect an exposed resolver.
- Pi-hole config drift is easy when generated files and source-of-truth files diverge. Treat
pihole.tomlas primary. - Pinning an image tag is good; patch cadence still matters for infrastructure services.
What I’d do differently
- Restrict DNS ingress to trusted LAN segments only at firewall/router level instead of relying on container defaults.
- Revisit
listeningMode = "ALL"; prefer the least permissive mode that still serves required clients. - Add explicit access control in front of admin UI (for example, Authelia or IP allowlists), not just host-based routing.
- Keep capabilities minimal and validate whether all three (
NET_ADMIN,SYS_NICE,SYS_TIME) are truly required in this environment. - Add a periodic config audit that compares intended settings (
pihole.toml) against effective runtime behavior.
Security notes
- Exposing host port
53is 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.