Skip to content

AdGuard Home + Unbound

DNS filtering and recursive resolution on infra-services. Replaces PiHole (LXC 104 blocktopus, 192.168.6.80).

Key Value
Compose services/adguard/compose.yml
Admin UI https://adguard.infra.realemail.app
DNS 192.168.6.17:53 (UDP/TCP)
Upstream Unbound at static compose IP 172.21.0.3
Backup tier 2

Full operator README (import script, upstream UI fields, compose notes): services/adguard/README.md in the repo.

Architecture

Clients → AdGuard Home (:53) → Unbound (:53) → root servers
                └─ DNS rewrites (*.lab.local, *.infra.realemail.app, *.realemail.app)

AdGuard handles filtering and local rewrites; Unbound recurses so the lab does not depend on a public resolver.

Do not use dynamic Docker IP discovery for the upstream

The 2026-07-08 infra-services reboot reassigned 172.21.0.2 to AdGuard while AdGuard still forwarded to that old Unbound IP, creating a DNS self-loop. The compose stack now reserves 172.21.0.2 for AdGuard and 172.21.0.3 for Unbound; the live AdGuard upstream should stay 172.21.0.3.

Cutover status (2026-06-19)

Item Status
Stack deployed on infra-services Done
Traefik UI + wildcard *.infra.realemail.app rewrite Done
Inventory rewrites imported (~29 + infra wildcard) Done
UFW allow LAN → :53 on infra-services Done
Tailscale prefer-main fix (Servers VLAN same-L2 DNS) Done (Ansible + systemd)
UDM WAN DNS → 192.168.6.17 Done
All VLAN DHCP DNS → 192.168.6.17 Done
IPv6 DNS on UDM Deferred (AdGuard IPv4 only today)
PiHole parallel soak + LXC 104 decom Done (2026-06-17)

See Phase 7 Owner Actions — decommission PiHole.

Servers VLAN caveat (Tailscale)

infra-services advertises 192.168.6.0/24 on Tailscale. Without a prefer-main ip rule, DNS replies to same-subnet clients (e.g. saltierpoop) leave via tailscale0 instead of eth0 and clients time out.

Managed by Ansible (tailscale_prefer_main_routes) and tailscale-local-subnet-routes.service on the host.

Verify

dig @192.168.6.17 google.com +short
dig @192.168.6.17 infra-services.lab.local +short
dig @192.168.6.17 adguard.infra.realemail.app +short
cd /opt/homelab/services/adguard && bash scripts/smoke-test.sh

From a Servers VLAN host, all three must succeed before relying on AdGuard alone.

Proxmox discovery LXCs

LXCs without an explicit Proxmox -nameserver inherit prox resolvers (often Tailscale MagicDNS 100.100.100.100). Non-Tailscale guests cannot use that and will fail DNS (apt update, registry pulls). On prox:

pct set <vmid> -nameserver 192.168.6.17
pct reboot <vmid>

Example: harbor-registry LXC 119 — harbor-registry.

Saltbox hostnames (*.realemail.app)

Same cutover gap as infra: Saltbox Traefik on saltierpoop (192.168.6.243) serves homeass.realemail.app, plex.realemail.app, etc. Without a rewrite, AdGuard returns Cloudflare public IPs and internal browsers miss local Traefik.

Domain Answer
*.realemail.app 192.168.6.243

Defined in services/adguard/saltbox-dns-rewrites.yaml; import via ./import-rewrites.sh on infra-services (see repo README).

dig @192.168.6.17 homeass.realemail.app +short
# 192.168.6.243