From 09745b33bddb1481efcbe83f107b33168b5f57f0 Mon Sep 17 00:00:00 2001 From: Lovebug MCP Date: Sun, 7 Jun 2026 01:19:50 +0000 Subject: [PATCH] =?UTF-8?q?mcp:=20session-notes=20=E2=80=94=20Tailscale=20?= =?UTF-8?q?.ts.net=20access=20to=20Traefik=20=E2=80=94=20remote=20DNS=20+?= =?UTF-8?q?=20static=20cert?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...26-06-07-tailscale-tsnet-traefik-access.md | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 Sources/Homelab/2026-06-07-tailscale-tsnet-traefik-access.md diff --git a/Sources/Homelab/2026-06-07-tailscale-tsnet-traefik-access.md b/Sources/Homelab/2026-06-07-tailscale-tsnet-traefik-access.md new file mode 100644 index 0000000..7ec01e1 --- /dev/null +++ b/Sources/Homelab/2026-06-07-tailscale-tsnet-traefik-access.md @@ -0,0 +1,63 @@ +--- +created: '2026-06-07' +path: Sources/Homelab +project: traefik-deployment +tags: +- tailscale +- traefik +- tls +- dns +- knot +type: session-notes +--- + +## Outcome + +Laptop (`truckslaptop`) can now reach the Traefik dashboard remotely over the tailnet at `https://traefik.taila7f44e.ts.net/dashboard/` with a valid cert and clean padlock — HTTP 200 confirmed. This is a parallel access path that does not touch the existing on-LAN `herbylab.dev` + wildcard-cert setup. + +Access model is two-path, toggled by hand: +- **On LAN:** `tailscale set --accept-dns=false` → normal resolver → Pi-hole → Knot → `*.herbylab.dev` names, wildcard cert. Unchanged. +- **Remote:** `tailscale set --accept-dns=true` on the laptop → MagicDNS resolves `.ts.net` names → reach services by their Tailscale node name. + +## Topics Covered + +- Why MagicDNS short names worked via `tailscale ip` but `.ts.net` FQDNs didn't resolve in `curl`/system resolver. +- The cert and `Host()`-routing implications of using `.ts.net` names instead of `herbylab.dev`. +- Generating a static Tailscale cert on the Traefik box and wiring it into the file provider alongside the Cloudflare DNS-01 auto-generated wildcard. +- Decision to keep subnet routing / split-horizon Knot off the table; accept two naming schemes by location. + +## Key Learnings + +- **MagicDNS needs a global nameserver to recurse.** With the tailnet's Global Nameservers empty, MagicDNS (`100.100.100.100` / `fd7a:115c:a1e0::53`) answers authoritatively for the tailnet but returns SERVFAIL ("recursion requested but not available") for public names. This broke `tailscale cert`'s reach to Let's Encrypt when `--accept-dns=true` was set on the Traefik box. Fix: either set `--accept-dns=false` so the box uses its own recursing resolver, or add a Global Nameserver (e.g. `1.1.1.1` or Pi-hole's tailnet IP) in the admin console. The cert command does not need MagicDNS — it only needs the box to reach Let's Encrypt over normal DNS. +- **`tailscale set --accept-dns` is global, not per-network.** No native per-SSID switching; the two-path model is a manual toggle (acceptable here, can be automated later via a NetworkManager dispatcher hook). +- **`resolvectl` failing with "unknown unit" = systemd-resolved not running.** On the laptop, Tailscale writes `resolv.conf` directly instead. Works fine; just know that's the mechanism rather than the resolved stub. +- **Two cert mechanisms coexist in the file provider.** The wildcard is auto-generated by the `cloudflare` resolver (`certificates: []` is empty on purpose, Traefik fetches/renews via DNS-01). The `.ts.net` cert is a static hand-generated file pair declared explicitly under `tls.certificates`. Different mechanisms, both valid, separate dynamic files. +- **`405` to a `curl -sI` (HEAD) against `api@internal` is a success signal** — TLS matched and routing worked; the dashboard just rejects HEAD. Confirm with a GET to `/dashboard/`. +- **The `.ts.net` route is per-service forever.** One static cert covers the box; each service needs its own `Host(`x.taila7f44e.ts.net`)` router. The trade for skipping subnet routing / split-horizon Knot. + +## Config landed + +Cert files: `/opt/traefik/dynamic/certs/traefik.taila7f44e.ts.net.{crt,key}` (in-container: `/etc/traefik/dynamic/certs/`). + +New dynamic file `/opt/traefik/dynamic/tailscale.yml`: +```yaml +tls: + certificates: + - certFile: /etc/traefik/dynamic/certs/traefik.taila7f44e.ts.net.crt + keyFile: /etc/traefik/dynamic/certs/traefik.taila7f44e.ts.net.key +http: + routers: + tailscale-dashboard: + rule: "Host(`traefik.taila7f44e.ts.net`)" + service: api@internal + tls: {} + entryPoints: + - websecure +``` + +## Follow-ons + +- [ ] Decide whether to add a tailnet Global Nameserver (Pi-hole tailnet IP or `1.1.1.1`) so MagicDNS recurses — would let `--accept-dns=true` stay on without breaking public resolution on any box. +- [ ] Optional: NetworkManager dispatcher script to auto-toggle `--accept-dns` on home-network connect, removing the manual flip. +- [ ] Renewal story for the static `.ts.net` cert — `tailscale cert` does not auto-renew like the Cloudflare resolver does; needs a timer or manual refresh before the ~90-day expiry. +- [ ] Add `.ts.net` routers for any other service wanted remotely (per-service `Host()` matchers). \ No newline at end of file