Files
homelab/assets/dns-chain.md
T
2026-04-26 22:26:14 -04:00

2.9 KiB

DNS Resolution

Two flows, one resolver chain. Splitting them apart because the interesting part of the design is what doesn't go to the upstream.

External resolution

What happens when a client asks for a public domain.

graph LR
  CLIENT[Client<br/>most VLANs] --> PIHOLE[Pi-hole<br/>filtering + cache]
  PIHOLE -->|miss| UNBOUND[Unbound on firewall<br/>recursive + DNSSEC]
  UNBOUND --> UPSTREAM[Cloudflare<br/>fallback only]

  PIHOLE -.->|blocked| BLOCKED[Ad/tracker<br/>domains]

  classDef client fill:#1f2f3a,stroke:#3a6b8b,color:#d0e0f0
  classDef resolver fill:#1f3a2f,stroke:#3a8b6b,color:#d0f0e0
  classDef upstream fill:#3a2f1f,stroke:#8b6b3a,color:#f0e0d0
  classDef blocked fill:#3a1f1f,stroke:#8b3a3a,color:#f0d0d0

  class CLIENT client
  class PIHOLE,UNBOUND resolver
  class UPSTREAM upstream
  class BLOCKED blocked

Local hostname resolution (split-horizon)

What happens when a client asks for an internal hostname. The query never leaves the LAN — Pi-hole answers from its local A records, and the client connects to the internal reverse proxy directly.

graph LR
  CLIENT[Client] -->|asks for<br/>app.lerkolabs.com| PIHOLE[Pi-hole<br/>local A records]
  PIHOLE -->|returns<br/>internal IP| CLIENT
  CLIENT -->|HTTPS<br/>valid public cert| CADDY[Internal Caddy<br/>reverse proxy]
  CADDY --> SVC[Internal service]

  classDef client fill:#1f2f3a,stroke:#3a6b8b,color:#d0e0f0
  classDef resolver fill:#1f3a2f,stroke:#3a8b6b,color:#d0f0e0
  classDef edge fill:#2f1f3a,stroke:#6b3a8b,color:#e0d0f0

  class CLIENT client
  class PIHOLE resolver
  class CADDY,SVC edge

Why this design

A few things are doing more work here than they look.

Pi-hole is the only authoritative source for internal names. One source of truth for hostname → IP, one place to update when something moves. It's also a documented SPOF — if it dies, internal hostnames stop resolving. I considered mirroring the records into Unbound on the firewall as a fallback and decided not to. I'd rather know Pi-hole is unhealthy than paper over it with a fallback that hides the problem.

Internal services get valid public certs without ever being exposed to the internet. Cloudflare DNS-01 ACME proves I control the domain via a TXT record; the cert never requires a publicly-reachable HTTP-01 challenge. Combined with split-horizon DNS, a VPN or LAN client browsing to app.lerkolabs.com gets a real cert chain on a connection that never leaves the network. The cert proves identity; segmentation handles confidentiality.

Bootstrap exception. The host running Pi-hole has to resolve through the firewall directly, not through itself, or nothing comes up at boot. Took a power outage to learn that one cleanly.

WFH and Management tiers don't use Pi-hole. Different reasons, both deliberate — see private repo for detail. The short version: the WFH laptop shouldn't see the local hostname inventory, and Management hosts can't depend on Pi-hole being up.