4.2 KiB
4.2 KiB
| created | path | project | tags | type | |||||
|---|---|---|---|---|---|---|---|---|---|
| 2026-06-07 | Sources/Homelab | traefik-deployment |
|
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.devnames, wildcard cert. Unchanged. - Remote:
tailscale set --accept-dns=trueon the laptop → MagicDNS resolves.ts.netnames → reach services by their Tailscale node name.
Topics Covered
- Why MagicDNS short names worked via
tailscale ipbut.ts.netFQDNs didn't resolve incurl/system resolver. - The cert and
Host()-routing implications of using.ts.netnames instead ofherbylab.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 broketailscale cert's reach to Let's Encrypt when--accept-dns=truewas set on the Traefik box. Fix: either set--accept-dns=falseso the box uses its own recursing resolver, or add a Global Nameserver (e.g.1.1.1.1or 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-dnsis 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).resolvectlfailing with "unknown unit" = systemd-resolved not running. On the laptop, Tailscale writesresolv.confdirectly 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
cloudflareresolver (certificates: []is empty on purpose, Traefik fetches/renews via DNS-01). The.ts.netcert is a static hand-generated file pair declared explicitly undertls.certificates. Different mechanisms, both valid, separate dynamic files. 405to acurl -sI(HEAD) againstapi@internalis a success signal — TLS matched and routing worked; the dashboard just rejects HEAD. Confirm with a GET to/dashboard/.- The
.ts.netroute is per-service forever. One static cert covers the box; each service needs its ownHost(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:
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=truestay on without breaking public resolution on any box. - Optional: NetworkManager dispatcher script to auto-toggle
--accept-dnson home-network connect, removing the manual flip. - Renewal story for the static
.ts.netcert —tailscale certdoes not auto-renew like the Cloudflare resolver does; needs a timer or manual refresh before the ~90-day expiry. - Add
.ts.netrouters for any other service wanted remotely (per-serviceHost()matchers).