# Tailscale vs. Managed DNS — Architecture Analysis > **Status:** **RESOLVED.** The pfv-netinfra-01/02 pair now runs production > Technitium DNS with all `knel.net` records replicated from tailscale-router > via the DNS cluster setup. Both LAN IPs serve authoritative records for > `knel.net` and recurse externally. This document records the original > conflict, how it was resolved, and the recommended client configuration. ## 1. Executive summary Every host in this build runs the Tailscale client, and Tailscale's MagicDNS manages `/etc/resolv.conf` by default (pointing at `100.100.100.100`). This previously conflicted with a managed `resolv.conf` pointing at the LAN resolvers. The root cause was that the LAN Technitium instances did not have the `knel.net` zone populated — **that is now fixed.** The pfv-netinfra-01/02 pair now serves identical, authoritative `knel.net` records (replicated from production via [`dns-cluster-setup/`](../dns-cluster-setup/README.md)). Both LAN IPs resolve `knel.net` device names and recurse externally. The managed `resolv.conf` is now safe to deploy. **Recommendation:** Deploy the managed `resolv.conf` (`.252`/`.253`) on hosts where you want tunnel-independent DNS. Leave Tailscale managing DNS on hosts where MagicDNS device names must resolve without a LAN path (e.g. laptops off -network). See [§5](#5-recommendation) for details. ## 2. How name resolution works today (post-cluster-setup) Probed from `sectestbed-sandbox` (192.168.3.50) after the DNS cluster was deployed: | Query path | External name (`github.com`) | `knel.net` device name (`pfv-netinfra-01.knel.net`) | |---|---|---| | Via Tailscale resolver (`100.100.100.100`) | resolves | resolves → `100.70.181.72` (Tailscale CGNAT) | | Direct `dig @192.168.3.252` (Technitium primary, LAN) | resolves (recurses) | **resolves** → `100.70.181.72` | | Direct `dig @192.168.3.253` (Technitium secondary, LAN) | resolves (recurses) | **resolves** → `100.70.181.72` | **Both LAN resolvers now serve `knel.net` records identically.** The Technitium zone is no longer stale — it was replicated from production (tailscale-router) as part of the DNS cluster setup. ### What changed Previously (before the DNS cluster setup), querying the LAN IPs returned NXDOMAIN for `knel.net` device names because the Technitium `knel.net` zone was empty (SOA serial `2025062313`, dated 2025-06-23). After replicating production config to both netinfra hosts, all 124 zones — including `knel.net` with all current device records — are served authoritatively on both `.252` and `.253`. ## 3. The DNS server pair | Host | IP | Role | Services | |------|----|------|----------| | pfv-netinfra-01 | 192.168.3.252 | **Primary** | Technitium (authoritative, port 5300) + Pi-hole (recursive, port 53) | | pfv-netinfra-02 | 192.168.3.253 | **Secondary** | Technitium (replicated via rsync, port 5300) + Pi-hole (recursive, port 53) | Zone replication is rsync-based (every 60s via systemd timer) because Technitium's AXFR uses port 53, which is occupied by Pi-hole on these hosts. See [`dns-cluster-setup/README.md`](../dns-cluster-setup/README.md) for full details. ## 4. NTP (fully resolved) NTP is independent of DNS: `provisioning/ConfigFiles/NTP/ntp.conf` points directly at the LAN IPs with no DNS dependency: ``` server 192.168.3.252 iburst server 192.168.3.253 iburst ``` Both servers respond with stratum 2/3. The client config uses `restrict` rules (not `interface listen`) to avoid the loopback-binding bug that prevented sync. This is safe under both Tailscale-managed and LAN-pinned resolver configurations. ## 5. Recommendation ### On fixed servers (always on-LAN) **Deploy the managed `resolv.conf`** (`provisioning/ConfigFiles/Resolv/`): - Points at `.252`/`.253` with failover - `knel.net` records resolve on both servers - External names recurse on both servers - DNS survives `tailscaled` outages (unlike Tailscale-managed DNS) To prevent Tailscale from overwriting the managed file: ```bash tailscale up --accept-dns=false ``` ### On laptops / roaming hosts **Let Tailscale manage DNS** (default `accept-dns=true`): - MagicDNS resolves `knel.net` device names via the tunnel - No dependency on LAN reachability - Accept the `tailscaled` dependency (if the tunnel is down, you're off-network anyway) ## 6. Subnet router HA (redundant routing) > **Redmine:** [#377](https://projects.knownelement.com/issues/377) Both DNS nodes advertise the LAN subnet as Tailscale subnet routes AND serve as exit nodes, providing full HA for remote monitoring (Uptime Kuma on the Cloudron VPS), off-site access, and internet egress: | Node | Tailscale IP | Advertised routes | Role | |------|-------------|-------------------|------| | pfv-netinfra-01 | 100.70.181.72 | 192.168.0.0/22, 0.0.0.0/0, ::/0 | Primary subnet router + exit node | | pfv-netinfra-02 | 100.71.171.20 | 192.168.0.0/22, 0.0.0.0/0, ::/0 | Secondary subnet router + exit node (failover) | To configure on a node (both flags together): ```bash sudo tailscale set --advertise-routes=192.168.0.0/22 --advertise-exit-node ``` **All routes must be approved** in the Tailscale admin console (https://login.tailscale.com/admin/machines) for failover to activate. This includes the subnet route (`192.168.0.0/22`) AND the exit node routes (`0.0.0.0/0`, `::/0`) — they appear as separate approvals on each machine. Tailscale uses the primary by default and fails over to the secondary if the primary's tunnel drops. ### Known instability factors (2026-08-06, [#377]) - **DERP relay bouncing:** netinfra-01 was observed switching between DERP relays (#9, #12, #13) every ~15 minutes, causing brief tunnel disruptions. - **Docker LinkChange events:** container create/destroy adds/removes bridge interfaces, triggering `LinkChange: major, rebinding` in tailscaled and momentarily dropping all connections. - **resolv.conf immutable flag:** netinfra-01 had the `+i` (immutable) attribute on `/etc/resolv.conf`, preventing tailscaled from updating DNS config after link changes. Fixed by `chattr -i /etc/resolv.conf`. ## 7. Known items / future work 1. **Pi-hole upstream configuration.** Pi-hole on both hosts should forward to the local Technitium instance (port 5300) for `knel.net` and to an external resolver for everything else. Verify this is configured on both nodes. 2. **Zone transfer via AXFR.** Currently using rsync because Technitium's AXFR expects port 53. If Technitium's listen port can be changed, or Pi-hole can be configured to proxy AXFR, the rsync timer could be replaced with native DNS zone transfer. 3. **`accept-dns=false` automation.** The provisioning code should set `--accept-dns=false` on Tailscale during setup (after deploying the managed `resolv.conf`) so Tailscale doesn't overwrite it on reboot.