Mesh vs Hub
The classic homelab VPN is a hub: one WireGuard endpoint at home, every client dialing into it. That works, but every client-to-client connection traverses the hub even when the two endpoints could reach each other directly. The hub becomes a bottleneck and a single point of failure.
Tailscale builds a mesh on top of WireGuard. Every node has a direct, encrypted tunnel to every other node, NAT-traversed via STUN and (when that fails) relayed through DERP. You stop thinking about ports, public IPs, and dynamic DNS.
When to Use Which
- Stick with plain WireGuard (see the WireGuard hub guide) when you have a static public IP, need full control, or want zero external dependencies.
- Use Tailscale when you have CGNAT/no public IP, dozens of devices to manage, or want SSO-based access control without running your own auth stack.
- Use Headscale (open-source coordination server) when you want the Tailscale UX but self-host the control plane.
Core Concepts
- Tailnet: Your private network. Every device you add to it gets a stable 100.x.y.z address from the CGNAT-reserved range.
- MagicDNS: Each node is reachable as
hostname.tailnet-name.ts.netwith automatic resolution across the tailnet. - Subnet router: A node that advertises a local CIDR (e.g.,
192.168.1.0/24) so other tailnet devices can reach hosts that do not run Tailscale themselves. - Exit node: A node that other devices can route all their internet traffic through, like a VPN egress.
- ACLs: JSON policy that defines who can reach what. The default is "everyone can reach everyone," which is fine until it isn't.
Install
Linux (Debian/Ubuntu):
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up
The command prints a URL. Open it, sign in with your identity provider (Google, GitHub, Microsoft, custom OIDC), and the node joins your tailnet. Repeat on every device. macOS, iOS, Android, and Windows have native apps.
Subnet Router for Lab Access
You do not need Tailscale on every IoT switch and printer. Run one subnet router that advertises the lab network:
# On a lab host (e.g., Proxmox or a small VM)
sudo sysctl -w net.ipv4.ip_forward=1
sudo sysctl -w net.ipv6.conf.all.forwarding=1
echo 'net.ipv4.ip_forward = 1' | sudo tee -a /etc/sysctl.conf
sudo tailscale up --advertise-routes=192.168.1.0/24,192.168.10.0/24
Then in the admin console under Machines → [router] → Edit route settings, approve the advertised routes. Now every tailnet device can reach lab hosts by their normal LAN IPs.
Exit Node for Travel
Want all traffic from your laptop on hotel wifi to egress from home?
# On the home node you want to use as exit
sudo tailscale up --advertise-exit-node
# Approve in the admin console, then on the client:
sudo tailscale up --exit-node=home-router --exit-node-allow-lan-access
The --exit-node-allow-lan-access flag is important — without it, you lose access to the local network you are physically on (printers, Chromecasts).
MagicDNS and Service Discovery
Enable MagicDNS in the admin console under DNS → Use MagicDNS. Now ssh proxmox works from anywhere on the tailnet — no /etc/hosts edits, no Pi-hole entries to maintain.
For local domains served by Pi-hole or your router, configure split DNS: add the local domain (lab, home.example.com) under DNS → Restricted nameservers and point it at the tailnet IP of your Pi-hole. Queries for that domain go to Pi-hole; everything else uses MagicDNS or your global resolver.
ACLs: Lock It Down
The default ACL is permissive. For a homelab with multiple users (family, friends), you probably want segmentation. The policy file is JSON in the admin console:
{
"groups": {
"group:admin": ["[email protected]"],
"group:family": ["[email protected]", "[email protected]"]
},
"tagOwners": {
"tag:lab": ["group:admin"],
"tag:media": ["group:admin"]
},
"acls": [
{ "action": "accept", "src": ["group:admin"], "dst": ["*:*"] },
{ "action": "accept", "src": ["group:family"], "dst": ["tag:media:*"] }
]
}
Tag your lab and media servers (tailscale up --advertise-tags=tag:lab) and the ACL above gives admins everything, family only the media boxes.
Key Expiry and Auth Keys
- Node keys expire every 180 days by default. You will need to reauthenticate. For headless boxes, disable expiry per-machine in the admin console.
- Auth keys let you provision machines without browser login. Use ephemeral keys for short-lived nodes (CI runners, test VMs) so they self-clean. Reusable + pre-authorized for fleet provisioning.
- Tagged machines do not have an owner and never expire — the right choice for always-on lab infrastructure.
Headscale: Self-Hosted Control Plane
If you object to a third party knowing your topology, Headscale is a drop-in open-source coordination server. Same client binaries, your control plane.
- Run Headscale in Docker or as a single binary; expose via your reverse proxy.
- Point clients at it with
tailscale up --login-server=https://headscale.example.com. - You lose the polished admin UI (community web UIs exist) and the official mobile apps require workarounds. Worth it if self-hosting matters to you.
Performance Notes
- Direct connection is what you want. Run
tailscale status— entries showingdirectare good,relaymeans STUN failed and you are using DERP (slower, rate-limited). - Strict NAT on both sides forces relay. Enable UPnP on your router or set up a static port forward to your subnet router for the Tailscale daemon's port if you must.
- Tailscale SSH replaces standard sshd with WireGuard-authenticated access. Lower friction than managing SSH keys; pair with ACLs for least-privilege.
Common Pitfalls
- Forgetting to enable IP forwarding on subnet routers. Routes appear to advertise but nothing actually flows.
- Overlapping subnets: If two subnet routers advertise overlapping CIDRs, traffic gets routed to whichever Tailscale picks. Keep advertised ranges unique.
- DNS conflicts when MagicDNS is on alongside a local resolver. Use split DNS rather than disabling either.
- Key expiry on a remote node right when you need it. Disable expiry on infrastructure machines or tag them.
Validation Checklist
tailscale statusshows all nodes online, mostly withdirectconnectionsping <hostname>works using MagicDNS short names- Subnet routes are approved and reachable from a non-router tailnet node
- Exit node works without breaking local LAN access
- ACL policy is tested (denied connections actually fail)
- Infrastructure nodes have expiry disabled or are tagged
- Auth keys are stored in a password manager, not a config file