Files
serverAppConfigs/serverAppConfigs-docs/Infrastructure/LAN-DNS.md
T
Frank 93d1181fee docs: OKF v0.1 compliance pass + fact-check + 3 research briefs
- Migrate all 127 non-reserved docs to OKF v0.1 frontmatter (type/description/tags/timestamp); fix 13 YAML issues, 2 duplicate-FM blocks, strip reserved-page FM
- Restructure log.md to OKF (## YYYY-MM-DD, newest-first)
- Fact-check against scripts/AGENTS.md ground truth: fix 6 false 'Active' banners on retired runbooks, 96->114 GiB GTT, deployment.md vs deploy-plan.py/deploy-remote.sh, Deployment Flow frank-medium=Quadlet, Calibre 9.9.0->9.15.0 + CVE-2025-7404 conflation, Copyparty v1.20.24, RomM 5.3.0 breaking change
- Move *arr docs to new FrankBook-Pro/ section (git mv, link-safe)
- Write active runbook bodies from ground truth (frank-large, frank-small)
- Add 3 research briefs + HTML report: Local TTS and Voice Cloning, AI Job Search and Resume Tools, Most Popular Self-Hosted AI Apps (Jun-Sep 2026)
- Add upstream-version rows to ComfyUI/OpenWebUI/Hermes app docs
- All 336 tests pass; 136 pages, 0 unresolved wikilinks
2026-09-22 02:09:41 -07:00

9.1 KiB

type, description, tags, timestamp
type description tags timestamp
reference The homelab gives every service a name under `franksdank.cloud`.
infrastructure
dns
2026-09-02

LAN DNS

The homelab gives every service a name under franksdank.cloud. Users type a name; they never type a port number. This page covers how names reach users: the Pi-hole server, the G36 router hand-off, and the split between LAN and WAN resolution.

See Caddy for the reverse proxy that serves the names, and Network Architecture for the full topology.

Components

Component Where Role
Pi-hole Dankclaw quadlet (dankclaw/pihole/) Answers the flat service names for LAN clients
SURFboard G36 192.168.0.1 Hands dnsmasq to clients as the DHCP DNS server
Porkbun Registrar for franksdank.cloud Answers the public names for the WAN; internal-only names have no public record and resolve only on the LAN
Caddy Dankclaw (192.168.0.143:443) Serves every name over HTTPS

How a name reaches a client

sequenceDiagram
    participant C as LAN client (laptop, phone)
    participant G as G36 router (192.168.0.1)
    participant D as Pi-hole (192.168.0.143:53)
    participant P as Porkbun (WAN)
    participant X as Caddy (192.168.0.143:443)

    C->>G: DHCP request
    G-->>C: lease + DNS = 192.168.0.143
    C->>D: dig romm.franksdank.cloud
    D-->>C: 192.168.0.143
    Note over D: addn-hosts answer<br/>(dankclaw/pihole/addn-hosts)
    C->>X: https://romm.franksdank.cloud
    X-->>C: RomM (192.168.0.235:8085 upstream)
    C->>P: dig franksdank.cloud (unknown name)
    P-->>C: 71.227.227.172

Pi-hole answers only the names in addn-hosts. Every other query goes to the upstream resolvers 1.1.1.1 and 1.0.0.1. The zone apex and unknown subdomains fall through to Porkbun. This avoids a wildcard override.

Why split DNS

The G36 has no NAT loopback (hairpin). A LAN client that resolves a name to the WAN IP and then connects to it would time out. Split DNS removes the hairpin: LAN clients get the LAN IP, WAN clients get the public IP. Both use the same name and the same Let's Encrypt certificate. See Caddy for the TLS side.

Pi-hole quadlet

Pi-hole FTL is a dnsmasq fork. It serves the same addn-hosts file the retired dnsmasq quadlet used, mounted into /etc/pihole/hosts/ read-only. It retired the dnsmasq quadlet on 2026-08-24.

  • Location: dankclaw/pihole/ — quadlet/pihole.container, env.example, addn-hosts
  • Ports: 53/udp + 53/tcp (DNS) and 8083/tcp (web UI) on 192.168.0.143
  • Host prerequisite 1: sysctl net.ipv4.ip_unprivileged_port_start=53 (persisted in /etc/sysctl.d/99-dnsmasq.conf)
  • Host prerequisite 2: DNSStubListener=no in /etc/systemd/resolved.conf (the resolved stub would otherwise hold port 53 inside the container netns)
  • Env file: /home/dank/Containers/pihole/env (never committed) — ServerIP, TZ, DNS1 + DNS2 (pinned upstreams 1.1.1.1 + 1.0.0.1), WEBPASSWORD (the shared family password)
  • Web UI: pihole.franksdank.cloud (Caddy vhost → 8083), family login
  • Deploy + restart after a change: ./scripts/deploy-pihole-quadlet.sh dankclaw

Adding a new service name is a two-or-three step decision — decide the exposure first, because it decides whether the name gets a public record at all:

  1. dankclaw/pihole/addn-hosts — 192.168.0.143 <name>.franksdank.cloud (always).
  2. Public name only: Porkbun A record <name> 71.227.227.172, so WAN clients resolve it too. An internal-only name deliberately gets no public record: the query falls through to the upstream resolvers, which answer NXDOMAIN for franksdank.cloud subdomains, so off-LAN clients cannot reach the vhost at all.
  3. Add the vhost to Caddy and run ./scripts/deploy.sh dankclaw. See Pi-hole for the full configuration.

Internal-only names as of 2026-09-02: prometheus., loki., grafana. (observability) and seerr. (the *arr request front-end). Their public A records were deleted from the Porkbun zone that day; each is still served by Caddy on the same name, so LAN clients see no change and WAN clients get NXDOMAIN. DNS-01 issuance is unaffected — Caddy's challenge is a TXT record in the same zone and needs no A record at all, which is why an internal-only vhost still gets a real Let's Encrypt certificate.

G36 router DNS hand-off

The router must tell DHCP clients to use Pi-hole. Until that step is done, new clients get the router's own DNS relay and the flat names do not resolve for them. Existing clients that already use Pi-hole are unaffected.

Manual step (web GUI at https://192.168.0.1):

  1. Open Gateway → Connection → Local IP Network → IPv4.
  2. Uncheck DNS Proxy (disable the router DNS relay).
  3. Set DNS to Statically configured.
  4. Set Primary DNS to 192.168.0.143.
  5. Leave the secondary field empty or set 0.0.0.0.
  6. Apply, then re-lease DHCP on each client (sudo dhclient -r && sudo dhclient).

The router admin interface has no programmatic API in this firmware. The web GUI is the only control path. Keep the credentials out of this repo; the repo tracks no router secrets.

Verify after the change:

# on a LAN client
resolv.conf          # nameserver 192.168.0.143
dig romm.franksdank.cloud +short
# expected: 192.168.0.143

Dankclaw host DNS

The Dankclaw host itself resolves the flat names through Pi-hole:

  • /etc/systemd/resolved.conf: DNS=192.168.0.143 192.168.0.1 (Pi-hole first, router as fallback)

Without this, the host would resolve the names through the router to the WAN IP and time out (no hairpin).

Dankware host DNS

dankware is a LAN client, not a Pi-hole server. Its flat names resolve through Pi-hole (192.168.0.143), but its resolver setup differs from Dankclaw's because it carries two +DefaultRoute links.

  • Two default links: enp47s0 (ethernet, metric 100) and wlan0 (wifi, metric 600), each getting the same DHCP DNS list [192.168.0.143, 1.1.1.1, …].
  • The flap: systemd-resolved picks a different "current" resolver per link — enp47s0 settled on the Comcast v6 upstream (→ WAN IP, times out on the LAN), wlan0 on Pi-hole (→ LAN IP). Flat-name resolution flapped between the two on every query.
  • The fix: pin 192.168.0.143 as the static IPv4 resolver on both NetworkManager connections. resolved.conf's own DNS= is a per-link fallback, so the link itself must be pinned:
for c in "Wired connection 1" FRANKFI-5G; do
  nmcli connection modify "$c" ipv4.method auto \
    ipv4.dns 192.168.0.143 \
    ipv4.ignore-auto-dns yes \
    ipv4.ignore-auto-routes no
done
nmcli connection up "Wired connection 1"
nmcli connection up FRANKFI-5G
systemctl restart systemd-resolved

method:auto keeps DHCP for the address/gateway/route (dual-default traffic path preserved); ignore-auto-dns=yes uses only the static resolver; ignore-auto-routes=no keeps the DHCP routes. Verify with a cache-clear loop — romm.franksdank.cloud should return 192.168.0.143 on every run:

for i in $(seq 1 10); do resolvectl flush-caches; dig romm.franksdank.cloud +short; done

The apex and unknown subdomains still fall through to Porkbun (→ 71.227.227.172), so WAN clients and a bare dig franksdank.cloud keep working.

WAN records (Porkbun)

The Porkbun zone franksdank.cloud carries an A record for every vhost plus the apex, all pointing at 71.227.227.172. TTL is 300. The zone has no wildcard record: unknown subdomains return NXDOMAIN. Caddy's DNS-01 challenges add and remove _acme-challenge.* TXT records through the same API during issuance. See Caddy for the TLS flow.

franksdank.casa is not in this Porkbun account. The plex.franksdank.casa vhost stays plain HTTP for that reason. The canonical Plex name is plex.franksdank.cloud (HTTPS).

Failure modes

Symptom First check
Name does not resolve on a new client resolv.conf shows 192.168.0.1, not .143 — the G36 hand-off step is missing
Name resolves to 71.227.x and times out on the LAN Client used the router DNS — same root cause
Flat name flaps between 192.168.0.143 and 71.227.x on one client Two +DefaultRoute links; resolved picks a different "current" resolver per link. Pin 192.168.0.143 as static DNS on both NM connections — see #Dankware host DNS
dig @192.168.0.143 fails entirely Pi-hole quadlet down: systemctl --user status pihole
Single-label name (for example dankclaw) answers 127.0.1.1 The rootless container /etc/hosts carries the host entry (127.0.1.1 <host>). FTL serves it. Use the FQDN, or an /etc/hosts entry on the client
HTTPS works from the host but not the WAN No port forward on the G36 for that service (manual router step)
Cert renewal fails in Caddy logs Porkbun API credentials in the Dankclaw .env; test with the Porkbun API /ping endpoint

See Also