# Tailscale vs. Managed DNS — Architecture Analysis > **Status:** analysis for review. No code decisions are final. Read the > "Known issues" section before acting on the managed-resolv.conf change. ## 1. Executive summary Every host in this build runs the Tailscale client, and Tailscale — by default — **manages `/etc/resolv.conf` itself**, pointing it at `100.100.100.100` (Tailscale's MagicDNS resolver). This directly conflicts with the managed `resolv.conf` (pointing at `192.168.3.252`/`192.168.3.253`) that `SetupNewSystem.sh` deploys: whichever runs last wins, and Tailscale's daemon re-wins on every `tailscale up` and on reboot. Worse, a probe of the live network shows that **knel.net device records only resolve through the Tailscale 100.100.100.100 path** — querying the LAN IPs of the DNS servers directly returns NXDOMAIN for current hostnames (the Technitium `knel.net` zone has the SOA but is stale/empty of actual records). So pointing `resolv.conf` at the LAN IPs would break resolution of the very names this project's modules depend on (`tsys-nsm.knel.net`, `tsys-cloudron.knel.net`, `tsys-librenms.knel.net`). This document lays out the options and a recommended path forward. ## 2. How name resolution actually works today (as measured) Probed from `sectestbed-sandbox` (192.168.3.50): | Query path | External name (`github.com`) | knel.net device name (`pfv-netinfra-01.knel.net`) | |---|---|---| | Via current resolver = `100.100.100.100` (Tailscale) | resolves | **resolves** → `100.70.181.72` (Tailscale CGNAT) | | Direct `dig @192.168.3.252` (Technitium, LAN) | resolves (recurses) | **NXDOMAIN** (SOA present, no record) | | Direct `dig @192.168.3.253` (Pi-hole, LAN) | resolves (recurses) | **NXDOMAIN** (SOA present, no record) | Other measured facts: - `dig @192.168.3.252 knel.net SOA` → `NOERROR`, returns `knel.net. 900 IN SOA dns.knel.net. hostadmin.knel.net. 2025062313 …` (serial dated **2025-06-23** — the zone exists but is stale). - NTP on both `.252` and `.253` answers time queries (stratum 2/3). - The live `/etc/resolv.conf` on a deployed host reads: ``` # resolv.conf(5) file generated by tailscale # DO NOT EDIT THIS FILE BY HAND -- CHANGES WILL BE OVERWRITTEN nameserver 100.100.100.100 nameserver fd7a:115c:a1e0::53 search knel.net ``` **Interpretation:** the `knel.net` device→Tailscale-IP mappings are synthesised by Tailscale's MagicDNS from the tailnet device registry (every device that joins the tailnet gets `hostname.knel.net` → its `100.x.x.x` address). The Technitium `knel.net` zone is a separate, manually-maintained zone that has fallen out of date. The two are not the same source of truth. ## 3. The core tension | Goal | Who provides it today | |---|---| | Resolve `*.knel.net` device names (→ Tailscale IPs) | Tailscale MagicDNS via `100.100.100.100` | | Resolve external names with ad-blocking | Pi-hole (`.253`), reachable via Tailscale → Technitium → Pi-hole chain | | Redundant, low-latency, tunnel-independent DNS | LAN resolvers `.252`/`.253` — **but these lack knel.net records** | | Authoritative time | NTP on `.252`/`.253` (works on either path) | The conflict: you cannot simply point `resolv.conf` at the LAN resolvers, because they do not know about the current `knel.net` device records, and several modules in this project resolve `knel.net` hostnames at runtime (wazuh manager, postfix relay, syslog target). You also cannot ignore Tailscale, because it is the only thing that resolves those names today. ## 4. Options ### Option A — Let Tailscale own DNS (status quo, `accept-dns=true`) Leave the default. Tailscale writes `100.100.100.100` to `resolv.conf`; the control-plane forwarding (`100.100.100.100` → Technitium → Pi-hole) handles external names and ad-blocking; MagicDNS handles `knel.net` device names. | Pros | Cons | |---|---| | Zero per-host config; new machines "just work" on `tailscale up` | **All DNS depends on the Tailscale daemon being up.** If `tailscaled` dies, every name lookup fails — including the ones you need to SSH in and fix it. | | MagicDNS + knel.net names resolve automatically | Latency: every query goes host→tailscaled→100.100.100.100→(tunnel)→Technitium→Pi-hole→upstream | | Ad-blocking preserved (via the Pi-hole hop) | Overwrites the managed `resolv.conf` — the `.252`/`.253` redundancy is lost | | Centralised in the Tailscale admin console | Single resolver in `resolv.conf` (`100.100.100.100`); no glibc-level failover | | | Boot-order risk: early-boot processes have no DNS until `tailscaled` is up | ### Option B — Pin resolv.conf to the LAN resolvers (`accept-dns=false`) Set `--accept-dns=false` on every host and keep the managed `resolv.conf` pointing at `.252`/`.253`. | Pros | Cons | |---|---| | DNS independent of Tailscale — survives `tailscaled` outages | **`*.knel.net` device names break (NXDOMAIN)** because the LAN resolvers' knel.net zone is stale. This breaks wazuh/postfix/syslog hostname resolution. | | Lowest latency, full glibc-level failover across two servers | MagicDNS names (`*.ts.net`) do not resolve | | Managed `resolv.conf` wins uncontested | Requires fixing the Technitium/Pi-hole `knel.net` zone to mirror the Tailscale device records before this is viable | | Boot-time DNS works immediately | Off-LAN hosts (laptops) can't reach `.252`/`.253` without the tunnel — back to needing Tailscale | > **Not recommended as-is.** Only viable **after** the `knel.net` zone on > `.252`/`.253` is repopulated with current device records (see §6). ### Option C — Tailscale Split DNS (per-domain routing) MagicDNS `ON`, "Override local DNS" `OFF` in the admin console; only `ts.net` (and explicitly split domains) route to `100.100.100.100`, everything else stays on the system resolver. | Pros | Cons | |---|---| | Best of both worlds: MagicDNS names resolve AND general queries go direct | Requires `systemd-resolved` (or NetworkManager `dns=dnsmasq`) for per-domain routing. These hosts use a **plain `/etc/resolv.conf`** — on which Tailscale **cannot** do per-domain split; it replaces the whole file. | | Reduces tunnel dependency for non-Tailscale names | Migrating every host to `systemd-resolved` is a significant, cross-cutting change | | | More moving parts to reason about and debug | ### Option D — Make Tailscale push the LAN resolvers as global nameservers In the admin console, set global nameservers to `192.168.3.252`/`192.168.3.253`, keep `accept-dns=true`. | Pros | Cons | |---|---| | Clients get the LAN resolvers via Tailscale config (consistent) | Tailscale still overwrites `resolv.conf` | | MagicDNS still works (100.100.100.100 added for `ts.net`/`knel.net`) | On-LAN hosts don't need Tailscale to find `.252`/`.253` — pure indirection | | Centralised management | Still depends on `tailscaled` for DNS | | | `knel.net` device names still only resolve via the Tailscale path, so the LAN resolvers being "global" doesn't help those names unless the zone is fixed | ## 5. Recommendation **Short term (unblock now): Option A — let Tailscale own DNS.** Revert/disable the managed-`resolv.conf` deployment so provisioning stops fighting Tailscale. Today, `knel.net` device names **only** resolve through Tailscale, and this project's modules depend on those names, so Tailscale-managed DNS is the only thing that currently works end-to-end. Keep the NTP change (LAN IPs, no DNS dependency) — that part is safe and beneficial regardless. **Medium term (the real fix): populate the `knel.net` zone on the LAN resolvers**, then choose B or C. Concretely: 1. Make Technitium (`.252`) authoritative for `knel.net` **with current records** (mirror the Tailscale device→IP mappings, or enable a zone-transfer/sync from the Tailscale device registry, or use Technitium's "Tailscale" DNS app if available). Confirm `dig @192.168.3.252 pfv-netinfra-01.knel.net` returns an answer, not NXDOMAIN. 2. Make Pi-hole (`.253`) forward `knel.net` to Technitium (or also serve the zone), so both resolvers in the pair can answer internal names — otherwise glibc failover to `.253` would silently break knel.net lookups. 3. *Then* pin `resolv.conf` to `.252`/`.253` with `--accept-dns=false` (Option B), gaining tunnel-independent, redundant DNS. **Long term (optional, if per-domain routing is wanted): Option C** — adopt `systemd-resolved` and configure Tailscale Split DNS so `ts.net`/`knel.net` go to MagicDNS and everything else goes direct. Only worth the migration cost if you specifically need `*.ts.net` short-name resolution alongside direct LAN DNS. ### Why not just force `.252`/`.253` today? Because it regresses name resolution for the hostnames this project already uses. Concretely, with `resolv.conf` pinned to the LAN resolvers the following would fail to resolve: - `ProjectCode/Modules/Security/secharden-wazuh.sh` → `WAZUH_MANAGER="tsys-nsm.knel.net"` - `ProjectCode/SetupNewSystem.sh` → `postconf -e "relayhost = tsys-cloudron.knel.net"` - `ProjectCode/ConfigFiles/Syslog/rsyslog.conf` → `*.* @tsys-librenms.knel.net:514` All three resolve cleanly via `100.100.100.100` today and return NXDOMAIN via `.252`/`.253`. Pinning the LAN resolvers before the zone is fixed would break wazuh, mail relay, and syslog. ## 6. Known issues / action items 1. **Technitium `knel.net` zone is stale.** SOA serial `2025062313` (2025-06-23); current device names return NXDOMAIN from the LAN interface. Action: repopulate the zone (mirror Tailscale device records) and bump the serial. 2. **Pi-hole (`.253`) has no `knel.net` device records either.** For the pair to be truly redundant for internal names, `.253` must either serve the same zone or conditional-forward `knel.net` to `.252`. Action: configure Pi-hole to forward `knel.net` to Technitium. 3. **The managed-`resolv.conf` change (commit f010fa9) conflicts with Tailscale.** As written, `SetupNewSystem.sh` writes `resolv.conf` with `.252`/`.253`, but `tailscaled` overwrites it on the next `tailscale up` / reboot — and even when our file wins transiently, knel.net names break. See §5 for the recommended handling. 4. **NTP change is safe and good.** `ntp.conf` now uses LAN IPs (`192.168.3.252`/`192.168.3.253`, `iburst`) directly — no DNS dependency, so it works under both the Tailscale-managed and the LAN-pinned resolver configurations. Keep this regardless of the DNS decision. 5. **Split-horizon possibility (unconfirmed).** It is possible Technitium serves a richer `knel.net` zone on its Tailscale interface (`100.x`) than on its LAN interface (`192.168.3.252`). If so, the fix is to make the LAN view match the Tailscale view. Worth confirming with `dig @ knel.net host`. ## 7. Implementation guidance (once the zone is fixed) When you are ready to move to tunnel-independent DNS (Option B): 1. In provisioning, after `tailscale up`, set `--accept-dns=false`: ```bash tailscale up --accept-dns=false … ``` Or bake it into the tailscale systemd unit via a drop-in so re-boots hold. 2. *Then* deploy the managed `resolv.conf` (`.252`/`.253`). Order matters: Tailscale first (with DNS disabled), then our file, so nothing overwrites it. 3. Add a watchdog (timer) that restores `resolv.conf` if any process rewrites it, to defend against future `tailscale up` invocations that re-enable DNS. 4. Validate with `Project-Tests/validation/dns-ntp-redundancy.sh` — and extend its probe to assert `*.knel.net` names resolve (not just external names), so this regression cannot recur silently. ## 8. TL;DR - **DNS**: don't fight Tailscale yet. Today `knel.net` names only resolve via Tailscale, and this project depends on them. Fix the Technitium/Pi-hole `knel.net` zone first, *then* pin the LAN resolvers. - **NTP**: the LAN-IP change is correct and safe; keep it. - **The managed `resolv.conf` (`.252`/`.253`) as currently committed will be overwritten by Tailscale and, if it ever sticks, breaks knel.net resolution — see §5/§6 before relying on it.**