Tailscale homelab reference: overlay as second path and management plane
How Tailscale runs in this homelab: as a parallel overlay alongside the site-to-site IPsec/GRE tunnels (covered in Magic WAN interop), with its own job - out-of-band management, a third path when LAN and tunnel routes are both unavailable, and an identity layer for SSH. Covers the NixOS authkey pattern, the Unraid plugin, the multi-path SSH alias scheme, and every failure mode observed in production. Hostnames, addresses, and paths are anonymized; the patterns are exactly as built.
TL;DR: Tailscale earns its place for OOB access and per-device identity, not for bulk site-to-site traffic (IPsec/GRE wins there). The two configurations to copy are the NixOS authKeyFile pattern (a rebuilt node rejoins unattended) and SSH Host pattern lists (one identity block, three addresses). The failures to avoid: placeholder authkeys, stale tailnet entries in a WSL-generated /etc/hosts, and an overlay node that was never authenticated in the first place.
What the tailnet is for
Section titled “What the tailnet is for”Three jobs, in order of how much they matter day to day:
- Out-of-band management. When the edge router, the IPsec/GRE tunnel, or local DNS is broken, the tailnet is the path that still works - it shares no fate with any of them. PiKVMs and the second-site Pi stay reachable even when the primary network does not.
- A third path for SSH. Every host alias carries a
10.0.xaddress (LAN or site-to-site), a10.68.xaddress (cross-tunnel), and the MagicDNS name. The tailnet path works from arbitrary networks - a hotel line, a phone hotspot - where neither RFC1918 path exists. - Identity for SSH. Tailscale SSH on the Unraid box replaces
authorized_keysdistribution with tailnet policy, and check mode adds browser re-authentication for high-risk connections.1
What it is not for: bulk traffic between sites. The IPsec/GRE tunnels move more data at lower latency because they are routed, not relayed, and do not depend on DERP fallback when NAT traversal fails.
| Decision | Payoff | Cost |
|---|---|---|
| Tailscale alongside IPsec/GRE, not instead of | Independent failure domain for OOB | Two overlays to reason about |
| Box-level tailscale on the router (subnet routing deferred) | Management plane without touching production routing | Devices without tailscaled stay unreachable from the tailnet for now |
| Tailscale SSH on Unraid only | Kills key-distribution toil where it hurt most | Check-mode re-auth URLs interrupt automation |
| Authkey file on the NixOS appliance | Rebuilt node rejoins unattended | Key material on disk; 90-day max key lifetime2 |
Topology
Section titled “Topology”Every node is box-level today: tailnet devices reach tailnet devices, nothing more. Subnet-router advertising from the router (the site VLAN ranges) is a planned cutover step, deliberately not enabled - the router’s Tailscale role is management plane, not production routing. Its firewall treats tailscale0 as a management interface: SSH, the management UI port, and ping, nothing else.
Node classes
Section titled “Node classes”| Class | Examples | How it joins | Verified state |
|---|---|---|---|
| NixOS router | edge router | Interactive tailscale up once; node key persists under /var/lib/tailscale | BackendState: Running, home DERP relay #3 |
| NixOS appliance | IoT Pi | authKeyFile + tailscaled-autoconnect unit | Running; autoconnect unit inert after registration |
| Unraid | media/automation server | Plugin manager, interactive login once | Running, Tailscale 1.102.2 |
| Windows + WSL2 | dev box | Windows GUI client; WSL2 has no tailscaled | - |
| OOB | PiKVMs | Stock tailscaled on the PiKVM OS | - |
Two consequences of the split. First, the authkey-joined node can rebuild and rejoin without a browser; the interactively-joined nodes cannot, but their node keys survive reboots under /var/lib/tailscale. Second, anything on the Windows box resolves MagicDNS through the Windows client - WSL2 inherits that DNS but runs no daemon of its own, which produces two of the failure modes below.
Declarative tailscaled on NixOS
Section titled “Declarative tailscaled on NixOS”The two variants
Section titled “The two variants”The router gets the minimal form - enable the service, open the management ports on tailscale0, authenticate interactively once:
# router: box-level management plane. First auth is interactive:# `sudo tailscale up` + browser URL (one-time; node key persists# under /var/lib/tailscale).services.tailscale.enable = true;
# nftables: management plane from the tailnet# iifname "tailscale0" tcp dport { 22, <mgmt-ui-port> } accept# iifname "tailscale0" icmp type echo-request acceptThe IoT Pi gets the unattended form. The nixpkgs services.tailscale module generates a tailscaled-autoconnect systemd unit when authKeyFile is set; the unit polls tailscale status --json and, on NeedsLogin, runs tailscale up --auth-key "$(cat <keyfile>)" plus any extraUpFlags, then exits once the state reaches Running:3
# IoT Pi: Tailscale is the second way in when the LAN path breaks.# The auth key is placed by hand, out-of-repo like the other secrets;# tailscaled consumes it on first start and ignores it once the node# is registered:# printf %s '<key from the admin console>' > /etc/tailscale-authkey# chmod 600 /etc/tailscale-authkeyservices.tailscale = { enable = true; authKeyFile = "/etc/tailscale-authkey";};Verified live after provisioning: BackendState: Running, and tailscaled-autoconnect is inactive - the unit is a one-shot that did its job on first boot and now sits inert. The key file on disk is ignored from then on; the node’s identity lives in /var/lib/tailscale.
Gotchas, all observed
Section titled “Gotchas, all observed”Authkeys expire after 1-90 days (90 default), so the key on disk is a consumable: if the node ever needs to re-register after the key lapses, generate a new one and drop it in place. An already-registered node stays authorized until its node key expires.2 Note that re-registration also means a new tailnet IP - when the Pi was re-registered, its old 100.x address was gone and anything pinned to it (including a stale /etc/hosts entry, below) dialed a dead node.
Tailscale SSH on Unraid
Section titled “Tailscale SSH on Unraid”Unraid gets Tailscale from the plugin manager (the tailscale plugin plus unraid-tailscale-utils), not from a container. Enabling Tailscale SSH is one command on the host:
tailscale set --sshThat claims port 22 on the tailnet IP only - the Unraid SSH daemon, sshd_config, and authorized_keys are untouched, and SSH over the LAN IP keeps working exactly as before.1
The default tailnet policy puts SSH to your own devices in check mode: the first connection of the day returns a login.tailscale.com/a/... URL and waits for browser re-authentication, then stays authorized for the 12-hour check period.1 Two operational notes:
- Deploy scripts that SSH to the box (mine takes the tailscale IP as an argument) fail on the first run of the day until someone opens the re-auth URL. Run one manual
sshfirst, then let automation ride the 12-hour window. - Check rules win over accept rules when both match, so adding a permissive
acceptrule does not bypass check mode - you must scope theacceptrule more precisely than thecheckrule.1
Multi-path SSH aliases
Section titled “Multi-path SSH aliases”The SSH config gives each host one block, many addresses. The Host line is a pattern list covering every way the host is addressed; the block below it carries the user and identity file. You pick the path by what you type:
Host unraid 10.0.x.2 10.68.x.2 unraid.<tailnet>.ts.net User root IdentityFile ~/.ssh/id_unraid IdentitiesOnly yesssh unraid resolves through local DNS (LAN) or MagicDNS; ssh 10.0.x.2 forces the primary path; ssh 10.68.x.2 forces the cross-tunnel backup. Same identity either way, no duplicate blocks to drift.
The hard rules that keep this survivable:
IdentitiesOnly yeson every block. Without it, ssh-agent offers every key in turn and locks accounts (Unraidrootafter a handful of offers) before the right key is tried.- MagicDNS names are patterns, never
HostName. PinHostNameto the LAN address. The tailnet name stays an alias you can type, not a resolution the config depends on. - Tailnet names do not override local DNS. The router’s own zones win for short names; the tailnet name is always the FQDN form.
Access control notes
Section titled “Access control notes”The idioms in use, all from the policy-file syntax reference:5
autogroup:internetas adstis what permits exit-node use. A device with no grant toautogroup:internetcannot use any exit node, and ACLs cannot restrict which exit node - that needs grants withvia.autoApprovers.routeswill matter at the subnet-router cutover: it lets advertised subnets skip the admin-console approval click. Auto-approval only applies when the route is first advertised - editing the policy file does not retroactively approve a pending route; remove and re-advertise it.- Tags beat users for servers. A tagged node authenticates with an authkey, gets key expiry disabled by default,2 and its access is governed by the tag rather than by whoever happened to log it in.
tests. Adenytest on a sensitive subnet fails the policy-file save if a future edit accidentally opens it.
Failure modes observed
Section titled “Failure modes observed”| Failure | Observation | Root cause | Fix | Evidence |
|---|---|---|---|---|
| Placeholder authkey | nixos-rebuild switch fails; NeedsLogin | Verbatim paste of the README line wrote literal placeholder text into two of three secret files | printf %s the real key; verify byte count | install README |
| Newline in key file | tailscale up rejects the key | echo appends \n | printf %s | install README |
| No second path during LAN incident | NIC naming trap broke LAN; tailnet unreachable too | Tailscale on the box had never been authenticated | authkey provisioning so a rebuild can never leave it un-authenticated | config comment |
| Alias dialed a dead node | ssh iot-pi hung while the LAN IP worked | WSL-generated /etc/hosts held a stale tailnet entry for the previous OS install | Pin HostName to the LAN address | ssh config comment |
| Dead tailnet IP after re-registration | Old 100.x address gone | Re-registering a node allocates a new tailnet IP | Never pin configs to tailnet IPs; use MagicDNS names | ssh config comment |
ping shows tx 1404 rx 0 to dev box | Tailnet healthy, all other peers fine | Windows-side Tailscale broken, not the tailnet | Check tailscale status --json BackendState, not just ping | session, 2026-07 |
| WSL2: ping fails, SSH works | ping <alias> no replies, ssh <alias> fine | Windows does not answer tailnet ICMP on the dev box | Test with the protocol you actually use | session, 2026-07 |
| Monitoring pipeline died mid-run | Postgres at 100.x:5433 unreachable; 8300-row run lost | tailscaled restarted on the DB host; the tailnet had silently become a production dependency | Treat tailnet IPs in connection strings as dependencies to monitor, not conveniences | session, 2026-08 |
Verification checklist
Section titled “Verification checklist”After touching anything Tailscale-related on a node:
| Check | Command | Expected |
|---|---|---|
| Daemon state | tailscale status --json | jq -r .BackendState | Running |
| Authkey file is real | wc -c /etc/tailscale-authkey | Key length, no trailing newline; not 15-ish placeholder bytes |
| Autoconnect consumed | systemctl is-active tailscaled-autoconnect | inactive after first registration (one-shot, done) |
| Router management plane | ssh router from a tailnet device | Connects over the tailnet IP |
| Tailscale SSH | ssh unraid from a tailnet device | Connects, or returns a check-mode URL (re-auth, then retry) |
| Path selection | ssh 10.0.x.2 vs ssh 10.68.x.2 | Same identity, both land on the same host |
| MagicDNS | getent hosts <host>.<tailnet>.ts.net | Current 100.x address (compare against the admin console after any re-registration) |
| DERP vs direct | tailscale netcheck; tailscale ping <peer> | Nearest relay region; direct after a few tries |
References
Section titled “References”-
Tailscale, “Tailscale SSH.” https://tailscale.com/kb/1193/tailscale-ssh ↩ ↩2 ↩3 ↩4
-
Tailscale, “Auth keys.” https://tailscale.com/kb/1085/auth-keys ↩ ↩2 ↩3
-
nixpkgs, “services.tailscale module (tailscaled-autoconnect).” https://github.com/NixOS/nixpkgs/blob/nixos-unstable/nixos/modules/services/networking/tailscale.nix ↩
-
Tailscale, “tailscale up command.” https://tailscale.com/docs/reference/tailscale-cli/up ↩
-
Tailscale, “Syntax reference for the tailnet policy file.” https://tailscale.com/docs/reference/syntax/policy-file ↩
-
Tailscale, “Subnet routers.” https://tailscale.com/kb/1019/subnets ↩