Skip to content

DNS — the complete spec

The single source of truth for how name resolution works across the homelab: the resolution model, every authoritative record, the operational runbook, the known gotchas, and the resilience/migration plan. Complements docs/architecture.md (network model) and docs/host-port-map.md (ports). Last verified end-to-end: 2026-07-19.

Domain: fmm.house · Host: av at LAN 192.168.86.49, tailnet 100.98.53.7 / fd7a:115c:a1e0::4001:3507 · Registrar/authoritative NS: Porkbun.

2026-07-16 — model change. We retired split-horizon in favor of one public answer, reachable two ways. Public DNS now returns A 192.168.86.49 and AAAA <tailnet> for every *.fmm.house name. Resolution no longer depends on AdGuard or the Nest forwarding to it — the answer is authoritative in public DNS. AdGuard's rewrite stays only as a local fallback. Why: the old split-horizon made all *.fmm.house resolution hinge on AdGuard being up on a rebooting box; a single unclean reboot took it down. See §7 for the reasoning and the rebind-safety test.

2026-07-18 — the container-resolution culprit was the Nest, not the host. Bridge containers were getting only the tailnet AAAA for *.fmm.house (this doc used to blame systemd-resolved). Direct testing showed the truth: the Nest's DNS-rebinding protection was refusing the private A (dig @192.168.86.1 … Astatus: REFUSED) for names that also carry an AAAA, and systemd-resolved merely relayed that. The 2026-07-16 "rebind-safe" test gave a false green because it used an A-only name (a name with only a private A isn't refused; one that also has an AAAA is). Rebind protection was disabled on the Nest, so it now serves the A everywhere — host and containers resolve *.fmm.house → .49 unaided, and every per-container dns: / extra_hosts workaround was removed (§6.1).

2026-07-19 — the AAAA now carries both the LAN IPv6 and the native tailnet IPv6. We added av's LAN ULA fd00:6176::49 as an AAAA, advertised as an approved Tailscale subnet route fd00:6176::49/128 (the IPv6 analog of 192.168.86.49/32), alongside av's native Tailscale AAAA fd7a:115c:a1e0::4001:3507. Why both: the LAN ULA + /128 route gives LAN and route-accepting tailnet devices a native-v6 path, but subnet routes don't cross a node share and aren't used by tailnet peers that don't accept routes — those clients can only reach av's native address, so fd7a must stay published. Happy Eyeballs picks the reachable one per client (LAN / on-tailnet → fd00:6176; shared-in or route-less → fd7a). AdGuard's rewrite serves AAAA fd00:6176 only (its wildcard rewrites return one answer per type; the public zone carries both). Sharing detail: §7 + docs/runbooks/tailscale.md.

2026-08-02 — the ULA changed, and the published AAAA rotted silently. The old value fd6e:88e0:2ffe:ea43::49 was a SLAAC address derived from the Nest's auto-generated ULA prefix. The router regenerated that prefix (it now advertises fde8:5432:a881:1::/64), the address vanished with it, and every *.fmm.house AAAA — AdGuard's rewrite and the public Porkbun zone — kept pointing at an address av no longer held.

The failure mode is worse than "no answer". ip -6 route get sent traffic for the dead address out the RA default route to be dropped, so connections hung rather than failing fast. Happy Eyeballs sometimes fell back to the working A record and sometimes did not, which reads as a random ERR_SSL_PROTOCOL_ERROR on any *.fmm.house name. The stale approved Tailscale /128 made it worse: route-accepting tailnet peers had that traffic pulled into av, which had no such address — a second blackhole on the same ghost prefix.

Fix and the rule that follows from it: av's ULA is now fd00:6176::49, pinned as a manual address in NetworkManager (ipv6.addresses, valid_lft forever) rather than inherited from RA. Never publish an AAAA for a SLAAC-derived address — a router firmware update or factory reset silently invalidates it. If the ULA must change again, all three locations move together: AdGuard rewrites, the Porkbun zone, and the Tailscale /128 (§below and tailscale.md).


1. Mental model — one answer, reachable two ways

Public DNS is authoritative and identical for everyone — it can't tell where you're asking from, so it hands out three private addresses per name (a LAN IPv4, a LAN IPv6, and av's native Tailscale IPv6) and lets the network use whichever it can route to. The two LAN addresses are reached on-LAN directly or off-LAN via an approved Tailscale subnet route; the native Tailscale address is the fallback for peers those routes don't reach (route-less peers, shared-in users):

*.fmm.house (and the apex)  ->  A    192.168.86.49                 (LAN IPv4)
                                AAAA fd00:6176::49       (LAN IPv6 / ULA)
                                AAAA fd7a:115c:a1e0::4001:3507     (native tailnet — shares / route-less peers)
You are asking from… you get (same everywhere) you reach av via…
A LAN device (v4 or dual) A .49 + AAAA fd00:6176 + fd7a .49 or fd00:6176, direct on the LAN
A LAN device, IPv6-only same fd00:6176, direct on the LAN
On-tailnet, off-LAN, accepts routes same .49/fd00:6176 via the …/32 + …/128 routes (or fd7a native)
On-tailnet, off-LAN, doesn't accept routes same fd7a (native — subnet routes need acceptance)
Shared-in user (another tailnet) same fd7a only — subnet routes don't cross a node share (§7 / tailscale.md)
Off-tailnet / off-LAN same neither — all three are private (LAN or tailnet)

Everything funnels to av, which terminates TLS at Traefik (:443) and routes on. The privacy model is intact: both published addresses are only reachable on your LAN or your tailnet — the wider internet resolves the names but can route to nothing, and no ports are forwarded.

The two LAN addresses (.49, fd00:6176) are reached on-LAN directly or off-LAN via an approved Tailscale subnet route (192.168.86.49/32 and fd00:6176::49/128); the native tailnet fd7a covers peers those routes don't reach (route-less peers, shared-in users). This replaced split-horizon: instead of one resolver (AdGuard) handing different answers to different networks, one public record set carries all three, and each client uses the one it can route to.


2. Authoritative records

2a. Public zone — Porkbun (api.porkbun.com, creds in stacks/traefik/.env)

A      *.fmm.house       -> 192.168.86.49                 # wildcard: every name → av's LAN IPv4
A      fmm.house         -> 192.168.86.49                 # apex (wildcards don't cover the apex)
AAAA   *.fmm.house       -> fd00:6176::49       # av's LAN IPv6 (ULA) — LAN + /128 route
AAAA   *.fmm.house       -> fd7a:115c:a1e0::4001:3507     # av's native tailnet IPv6 — shares / route-less peers
AAAA   fmm.house         -> fd00:6176::49       # apex (both AAAAs)
AAAA   fmm.house         -> fd7a:115c:a1e0::4001:3507
MX     fmm.house         -> mail.fmm.house   (prio 10)    # see §4 (mail)
NS     fmm.house         -> curitiba/fortaleza/maceio/salvador.porkbun.com
  • All are wildcards — every *.fmm.house name gets the LAN A and both AAAAs (LAN fd00:6176
  • native tailnet fd7a), so adding a new service needs zero public-DNS work (§5a).
  • The A records point at a private (RFC1918) address in a public zone. Cloudflare and Google pass it through fine; the Nest's DNS-rebinding protection refused it (for dual A/AAAA names) until we disabled that on 2026-07-18 — it now passes too (§7). It does mean 192.168.86.49 is public; it's a LAN IP, low sensitivity, but note it for the eventual public repo.
  • There used to be explicit AAAA ha.fmm.house / AAAA mail.fmm.house (pre-wildcard leftovers). They were deleted — an explicit record suppresses the wildcard for that name, which would have starved ha/mail of the new A. The wildcard now governs both.
  • Porkbun is also the ACME DNS-01 provider: Traefik proves *.fmm.house ownership by writing a TXT record via the Porkbun API to issue the wildcard cert. That TXT is transient.

2b. Internal resolver — AdGuard Home (stacks/adguard)

Runs as a container on av, published on the host's real IPs (see §5c for why specific IPs):

DNS listeners : 192.168.86.49:53 , 100.98.53.7:53 (tailnet) , [::]:53   (udp+tcp)
Admin UI/API  : http://192.168.86.49:2003   (login-gated)

DNS Rewrites (now a redundant LOCAL fallback, not load-bearing — public DNS is authoritative):
    *.fmm.house   -> 192.168.86.49                # A
    *.fmm.house   -> fd00:6176::49      # AAAA — the ULA ONLY (public zone also carries fd7a; see note)
    fmm.house     -> 192.168.86.49  /  fd00:6176::49    # A + AAAA

Upstream  : https://dns.quad9.net/dns-query   (DoH)
            tls://dns.quad9.net               (DoT)
Bootstrap : 9.9.9.9 , 149.112.112.112         (plain IPs — resolve the DoH/DoT hostname)
Fallback  : (none)
Filtering : 2 blocklists · user_rules: (empty) · ratelimit 20

AdGuard's rewrite returns A .49 + AAAA fd00:6176 (it previously answered AAAA as NODATA). It serves only fd00:6176, not the public zone's second AAAA fd7a — AdGuard wildcard rewrites return a single answer per type. That's fine: every AdGuard-resolving client here is on the LAN (reaches fd00:6176 directly) or accepts routes; the clients that need fd7a (node shares, route-less peers) resolve via the public zone, which carries both. Since public DNS now carries these itself, the rewrite is no longer required for resolution — it only helps in the narrow case where a LAN client can reach AdGuard but not the public internet (e.g. WAN down, LAN up), where it answers *.fmm.house locally. Its NODATA-for-AAAA (and MX/TXT shadowing) still applies — see §6.

2c. The router — Nest Wifi Pro (192.168.86.1)

Hands out itself (192.168.86.1) as the DNS server via DHCP and forwards to a public upstream (and should forward to AdGuard for ad-blocking, though it currently doesn't — see below).

No longer load-bearing for resolution. Since 2026-07-16, *.fmm.house has public A .49 records, so LAN devices resolve .49 through the Nest regardless of whether it forwards to AdGuard or straight to public — either path returns .49 (the Nest serves the private A now that rebind protection is off, §7). The only thing the Nest bypassing AdGuard costs you now is ad-blocking, not service access. To restore ad-blocking: Google Home → Wifi → Advanced networking → DNS → Custom = 192.168.86.49. The durable fix for both is still the router migration (§7).


3. Resolution flow (worked)

LAN laptop  ── im.fmm.house? ──►  Nest 192.168.86.1  ──►  (public or AdGuard) ──►  A 192.168.86.49
                                                                                     │  (same LAN → direct)
                                   Traefik :443 (SNI im.fmm.house) ──► file route ──► av:2002 (immich)

Tailnet phone (off-LAN) ── im.fmm.house? ──► public/MagicDNS ──►  A .49 + AAAA fd00:6176(LAN) + fd7a(native)
                                          ──► fd00:6176 via /128 route (or fd7a native for shares/route-less) ──► Traefik :443 ──► immich

The host av resolves via systemd-resolved, which forwards *.fmm.house to the Nest and now returns the A .49 and both AAAAs (fd00:6176 + fd7a). (Until 2026-07-18 the Nest's rebind protection refused the A, so the host — and every bridge container forwarding to it — saw only the then-tailnet AAAA; that's fixed, §6.1.) A bridge container uses the A (its v4-only bridge can't route the v6, so the AAAA fast-fails and it falls back to .49); a dual-stack LAN device can use either.


4. Mail / MX

  • Public: MX fmm.house → mail.fmm.house, and mail.fmm.house now resolves (via the wildcards) to A .49 + AAAA fd00:6176/fd7a. A sender delivering to <mailbox>@fmm.house does the MX lookup and reaches :25 (Postmoogle) on the LAN directly, or off-LAN via the approved Tailscale routes. No public inbound mail (both addresses are private; no port-forward).
  • Internal (implicit MX): AdGuard's rewrite shadows MX (returns NODATA), so a LAN MTA gets no MX record and falls back per RFC 5321 §5.1 to the apex A (fmm.house → 192.168.86.49) → :25. This is why we never needed to hand-craft an internal MX record.
  • Full mail path + both well-known gotchas: docs/runbooks/email-to-matrix.md.

5. Operational runbook

5a. Add a new service hostname

Nothing to do in DNS. The public wildcards (A .49 + AAAA fd00:6176 + fd7a) already cover every name. Just give Traefik a route (container label or stacks/traefik/dynamic/<svc>.yaml) and the wildcard TLS cert already matches. Only add an explicit record for a non-av target — and if you do, add both the A and AAAA for it, because one explicit record suppresses the wildcard for that name entirely (that's what bit ha/mail, §2a).

5b. Verify DNS end-to-end

# Public zone is authoritative — every name should return BOTH the LAN A and the LAN AAAA
dig @1.1.1.1 im.fmm.house A +short            # → 192.168.86.49
dig @1.1.1.1 im.fmm.house AAAA +short          # → fd00:6176::49  AND  fd7a:115c:a1e0::4001:3507
dig @192.168.86.1 im.fmm.house A +short        # via the Nest → 192.168.86.49 (rebind protection off; A served)

# AdGuard fallback still answers on every interface it binds
dig @192.168.86.49 im.fmm.house +short         # → 192.168.86.49 (rewrite)
dig @100.98.53.7   fmm.house +short            # → 192.168.86.49 (tailnet listener)
dig @192.168.86.49 google.com +short           # → upstream works (Quad9)

# Tailnet reachability of the LAN A/AAAA depends on the approved subnet routes
tailscale status --json | python3 -c "import json,sys;print(json.load(sys.stdin)['Self']['PrimaryRoutes'])"
#   → must contain 192.168.86.49/32 AND fd00:6176::49/128

5c. AdGuard :53 bind — why specific IPs

stacks/adguard/compose.yml publishes :53 on 192.168.86.49, 100.98.53.7, and [::]not 0.0.0.0. This is deliberate: av runs systemd-resolved on 127.0.0.53:53, and a 0.0.0.0:53 publish would collide with it. Binding the real IPs sidesteps the conflict. Do not change these to 0.0.0.0.

5d. Reboot-recovery playbook ← learned the hard way, 2026-07-16

av has a LUKS root (no auto-boot) and an unclean power-loss reboot leaves Docker's per-container network state corrupt. The nasty part: containers come back looking "healthy" (their healthcheck only probes an internal port) while their host networking is dead. Symptoms and fixes seen this incident:

Symptom Root cause Fix
dig @192.168.86.49connection refused, but docker ps says healthy Container restored with no network attacheddocker port <c> empty, NetworkSettings.Networks empty; AdGuard's own :53 was up inside the container docker compose … up -d --force-recreate <svc> (a plain start/up does not rebuild the network)
App crash-loops on getaddrinfo EAI_AGAIN <db> while its DB container is up Project network's embedded DNS (127.0.0.11) left stale; recreating only the app onto it doesn't fix it docker compose … down && … up -d (rebuilds the network object, not just containers) — did this for immich + peertube + ocis
Bridge container dies on dial <fd7a:…>:443: network is unreachable It resolved a *.fmm.house name to only the tailnet IPv6 — the Nest was refusing the private A (rebind protection), so there was no A to fall back to Fixed at the source 2026-07-18 (rebind protection off → the Nest serves the A, containers fall back to .49). The old extra_hosts: …:host-gateway pins are now removed (§6.1)

Fast triage after any reboot:

# any running container with NO network attached? (the "healthy but dead" trap)
for c in $(docker ps --format '{{.Names}}'); do
  n=$(docker inspect "$c" --format '{{range $k,$v := .NetworkSettings.Networks}}{{$k}} {{end}}')
  m=$(docker inspect "$c" --format '{{.HostConfig.NetworkMode}}')
  [ -z "$(echo $n|tr -d ' ')" ] && [ "$m" != host ] && [ "$m" != none ] && echo "⚠ $c has no network"
done
# services that exited ~boot time and didn't come back
docker ps -a --format '{{.Names}}\t{{.Status}}' | grep -iE 'exited.*(255|1) .*(minute|hour) ago'

5e. DNS-over-TLS for Android "Private DNS" (ag.fmm.house)

Android's Private DNS speaks DoT on :853 with a valid cert only (no DoH, no plain DNS — that's why plain ag.fmm.house was rejected). We terminate DoT at Traefik, reusing the *.fmm.house wildcard cert, so no cert lives inside AdGuard: - traefik.yaml — a dot entrypoint on :853. - traefik compose — publishes :853 on 192.168.86.49 + 100.98.53.7 + [::] (specific IPs + [::], not 0.0.0.0+[::] together — those collide, the v6 bind also grabs v4). - dynamic/adguard-dot.yaml — a TCP router HostSNI(ag.fmm.house) that TLS-terminates and forwards the plaintext DNS-over-TCP to adguard:53. AdGuard sees normal DNS-over-TCP. - ag.fmm.house needs a dedicated A → 192.168.86.49-only record in Porkbun (not the dual-stack wildcard). Android's Private DNS validator probes every resolved address; the wildcard's tailnet AAAA is unreachable from a Wi-Fi phone → a persistent "DNS server cannot be accessed" warning even while DNS works over the A. The explicit A suppresses the wildcard AAAA for this name (Porkbun honors RFC 4592 — allow ~1 min to propagate). Remote reach is then the LAN A via the approved 192.168.86.49/32 route (mobile Tailscale uses it automatically).

On the phone: Settings → Network & internet → Private DNS → provider hostnameag.fmm.house. After a DNS change, toggle Private DNS off→on (or re-enter the hostname) to force Android to re-resolve + re-validate — it caches both the resolution and the validation result.

⚠️ Strict Private DNS has no fallback. If ag.fmm.house:853 is unreachable, the phone has no DNS at all. It works only on the LAN or with Tailscale connected (the phone resolves ag.fmm.house via the underlying network, then reaches :853 on .49/tailnet). Off-LAN without Tailscale → turn Private DNS off. (This also means the phone now gets AdGuard's ad-blocking + *.fmm.house rewrites wherever it is on the tailnet.)

Verify: kdig +tls @192.168.86.49 -H ag.fmm.house fmm.house, or a Python TLS-to-:853 + DNS probe.


6. Known gotchas (consolidated)

  1. [RESOLVED 2026-07-18] Bridge containers once got ONLY the tailnet IPv6 for *.fmm.house. Symptom: a container resolving a *.fmm.house URL got only the unroutable tailnet AAAA (network is unreachable / a false "down"), with no A to fall back to. The cause was not systemd-resolved (an earlier version of this note said so — wrong): the Nest's DNS-rebinding protection was refusing the private A for names that also carry an AAAA, and the host resolver relayed that refusal. Disabling rebind protection on the Nest made it serve the A again; host and containers now resolve *.fmm.house → .49 unaided (verified end-to-end via getent/create_connection from a bridge — v6 fast-fails, .49 answers in ~90 ms). The per-container workarounds were therefore removed:
  2. Removed dns: [192.168.86.49] from Uptime Kuma.
  3. Removed extra_hosts: ["…:host-gateway"] from Postmoogle (matrix.fmm.house) and the app mailers beszel / forgejo / ocis / immich (mail.fmm.house).
  4. The fleet-wide /etc/docker/daemon.json { "dns": ["192.168.86.49"] } fix is no longer needed, and lk-jwt (Element Call federation to matrix.fmm.house) resolves correctly too.
  5. Still the best practice for container-to-container traffic: prefer internal service names (http://continuwuity:8008) over the public hostname where possible.

    If the Nest is ever factory-reset or replaced, rebind protection may come back — re-check with dig @192.168.86.1 <dual-stack-name>.fmm.house A (a REFUSED/empty answer is the tell) and either disable it again or apply the daemon.json dns fix above.

  6. The Nest silently failing over off AdGuard now only costs ad-blocking, not resolution. Because *.fmm.house has public A .49, LAN devices resolve correctly whether the Nest forwards to AdGuard or to public. Re-assert custom DNS = 192.168.86.49 (§2c) to get ad-blocking back, but it is no longer an outage when it drifts.
  7. AdGuard "DNS Rewrites" only do A/AAAA/CNAME and NODATA-shadow every other type (MX, TXT, SRV). Never rely on AdGuard to serve an MX/TXT for fmm.house; use the implicit-MX apex-A fallback (§4) or a $dnsrewrite filter rule. Full write-up: memory adguard-ui-rewrites-shadow-mx.
  8. "Healthy" ≠ "reachable" after an unclean reboot. AdGuard's healthcheck pings only its web UI (:8083), so LAN DNS can be dead while docker ps is green. External monitoring is the only reliable signal — done: Uptime Kuma monitor #19 "AdGuard DNS (fmm.house :53)" now queries fmm.house A against 192.168.86.49:53 every 60 s (a real DNS check, unlike the HTTP web-UI monitor #2), and alerts to the Matrix "Homelab Updates" room (all monitors now notify there — see docs/runbooks/monitoring.md). So a dead resolver both goes red and pages.

7. Resilience & the router-native migration

What the public-A change fixed (2026-07-16). The old fragility was that all *.fmm.house resolution hinged on AdGuard being reachable on a rebooting box — and general LAN DNS hinged on the Nest forwarding to it. A single unclean reboot took AdGuard's networking down and the Nest silently failed over, so the whole house lost DNS in a way monitoring couldn't see. Publishing the LAN A in authoritative public DNS decouples resolution from av entirely: every device resolves *.fmm.house from public DNS regardless of AdGuard or the Nest. av going down now only means the services are unreachable (they're on av anyway) — it no longer breaks name resolution or general LAN internet.

The rebind story (2026-07-16 → 2026-07-18). Putting a private (RFC1918) A in a public zone can be refused by resolvers with DNS-rebinding protection. The 2026-07-16 test (rbtest49.fmm.house resolving to .49 via Cloudflare 1.1.1.1, Google 8.8.8.8, and the Nest 192.168.86.1) looked clean — but it used an A-only name, and that was the flaw: the Nest refuses the private A only for names that also carry an AAAA (i.e. every real service). Public resolvers (Cloudflare/Quad9/Google) never refuse it; dig @192.168.86.1 git.fmm.house A returned status: REFUSED while the AAAA passed. Fix: rebind protection disabled on the Nest (2026-07-18) — it now serves the A for all names. Re-test with dig @192.168.86.1 <dual-stack-name> A if the router is ever reset or replaced.

What's left for the migration. With resolution handled, the router migration is now about ad-blocking robustness and cleanliness, not a DNS outage risk — a nice-to-have, not urgent.

Options

Option What Pros Cons
A. Router-native AdGuard (your lean) A router/appliance that runs AdGuard Home itself (e.g. GL.iNet Flint, or OPNsense/OpenWrt + AdGuard) replaces the Nest DNS decoupled from av; survives av reboots; the LAN's default gateway is the resolver so no failover gap new hardware; re-create rewrites + upstreams there; migrate the "custom DNS" trick away
B. Second AdGuard on an always-on low-power device (Pi/mini-PC), handed out as secondary Cheap; redundancy without replacing the router secondary-DNS behavior is non-deterministic on the Nest (it can prefer either); two configs to keep in sync
C. Harden in place TPM2 LUKS auto-unlock + fix the Nest custom-DNS + real healthcheck/monitor + daemon.json dns no new hardware DNS still lives on av; auto-unlock weakens the LUKS threat model

Recommendation: now that resolution is decoupled, this is no longer urgent. A is still the tidiest long-term home for ad-blocking + LAN DNS, but there's no outage pressure. Cheap independent wins whenever: re-assert the Nest custom DNS (ad-blocking), [done] the Uptime Kuma DNS monitor on 192.168.86.49:53 (catches the "healthy but dead" AdGuard state, §6.4 — now alerts to the Matrix "Homelab Updates" room), and give AdGuard a real query-based healthcheck. (The daemon.json dns fix is no longer needed — the Nest serves the A directly, §6.1.) The KVM already covers the LUKS-unlock console, so TPM2 auto-unlock stays optional.

Replication checklist for a new router running AdGuard

Everything needed to rebuild the resolver elsewhere: 1. Rewrites: *.fmm.house → 192.168.86.49 and fmm.house → 192.168.86.49 (A). (Update the IP if av's LAN address ever changes.) 2. Upstream: DoH https://dns.quad9.net/dns-query + DoT tls://dns.quad9.net; bootstrap 9.9.9.9, 149.112.112.112. 3. Blocklists: the 2 currently enabled (export from the current AdGuard UI → Filters). 4. Serve DNS to the LAN as the DHCP-advertised resolver (no more "router forwards to a separate AdGuard" hop — the router is AdGuard). 5. Keep the tailnet listener reachable (or point tailnet clients at MagicDNS + the public zone). 6. Public zone (Porkbun) is independent of the LAN resolver and already carries the authoritative A .49 + AAAA fd00:6176/fd7a wildcards — a new router inherits correct resolution for free; it only adds ad-blocking + the local AdGuard fallback (which should serve A .49 + AAAA fd00:6176 to match). 7. Keep av's subnet routes advertised + approved — 192.168.86.49/32 and fd00:6176::49/128 — they're what make the public A .49 / AAAA fd00:6176 reachable for off-LAN tailnet devices. 8. Re-run the §5b verification from a LAN client, a tailnet client, and a container.


8. Cross-references

  • docs/architecture.md — overall network model and layers
  • docs/host-port-map.md — every published port (:53, :2003 AdGuard; :443 Traefik; …)
  • docs/runbooks/email-to-matrix.md — mail path + well-known gotchas
  • Memory: adguard-ui-rewrites-shadow-mx, email-into-matrix-postmoogle