Tailscale and the Zero-Trust Home Network
The traditional answer to “I want to access my home network remotely” is a VPN server: install OpenVPN or WireGuard on a box with a public IP, open a firewall port, distribute certificates or keys to clients, and manage it forever. It works, but it requires a machine with a reachable public IP, manual key distribution, and ongoing maintenance. If you have two networks you want to connect, you do it again. If you add a team member who needs access, you generate keys by hand.
Tailscale takes a different approach. It handles the key distribution, the NAT traversal, and the coordination automatically. You install a client on each device, authenticate once, and the devices form a mesh network where every node can reach every other node directly — no central gateway, no traffic bottleneck, no single point of failure. The underlying encryption is WireGuard. The magic is everything Tailscale builds on top of it.
This post covers how the system actually works, the full feature set (subnet routing, exit nodes, Funnel, MagicDNS, ACLs, Tailscale SSH), and how to self-host the coordination layer with Headscale if you want complete independence from Tailscale’s cloud.
How It Works: The Two Planes
Tailscale separates its operation into a control plane and a data plane. Understanding this separation explains why the system is reliable even when Tailscale’s servers are unreachable, and why it behaves so differently from a traditional hub-and-spoke VPN.
The control plane is login.tailscale.com — Tailscale’s coordination server. Its job is key distribution and policy delivery. Each device generates a WireGuard keypair locally; the private key never leaves the device. The public key is uploaded to the coordination server. When device A wants to communicate with device B, the coordination server gives each device the other’s public key, IP address, and DERP relay assignment. That’s it. The control plane carries no user traffic — only tiny metadata messages.
The data plane is a direct WireGuard mesh. Once devices have each other’s public keys (from the coordination server), they communicate directly, peer-to-peer, using WireGuard. The coordination server is no longer involved. If Tailscale’s servers went down after your devices have connected, your existing connections would continue to work.
┌─────────────────────────────────┐
│ login.tailscale.com │
│ (coordination server) │
│ │
│ - stores public keys │
│ - delivers ACL policies │
│ - assigns DERP regions │
│ - NO user traffic passes here │
└──────┬───────────────┬───────────┘
│ keys+policy │ keys+policy
│ │
┌──────▼──┐ ┌────▼──────┐
│ Laptop │◄─────►│ Server │
│ 100.x.y │ direct│ 100.a.b │
│ .1 │ WireG │ .2 │
└─────────┘ uard └───────────┘
Each device on a Tailscale network (called a tailnet) gets a stable IP address in the 100.64.0.0/10 range (Carrier-Grade NAT space, reserved for this purpose). These addresses are stable across networks and locations — your laptop is always 100.x.y.z regardless of whether it’s on home WiFi, a coffee shop, or a cellular connection.
NAT Traversal: How Direct Connections Happen
The hard problem Tailscale solves is connecting two devices that are both behind NAT — both on home routers, corporate firewalls, or cloud VPCs that do not have publicly reachable IPs. This describes most real-world devices.
The technique is called UDP hole punching, and it works because most NAT devices use predictable port mappings for outbound UDP traffic.
- Both devices report their NAT-external address and port to the coordination server (discovered via STUN — the same protocol WebRTC uses).
- The coordination server shares these external addresses with both peers simultaneously.
- Both devices send UDP packets to each other’s external address at the same time.
- Each NAT device sees an outbound packet to the other peer’s address and creates a mapping. When the return packet arrives from that same address, the NAT forwards it through — because outbound traffic already created the hole.
Laptop (192.168.1.5) Server (10.0.0.3)
behind home router behind cloud VPC NAT
external: 203.0.113.10:42000 external: 198.51.100.5:51820
Step 1: Both report externals to coordination server
Step 2: Coordination server shares: "Laptop is at 203.0.113.10:42000"
"Server is at 198.51.100.5:51820"
Step 3: Laptop sends UDP to 198.51.100.5:51820 ─┐ simultaneous
Server sends UDP to 203.0.113.10:42000 ─┘
Step 4: Each NAT accepts the return traffic → direct connection established
This works for most NAT configurations. The failure case is symmetric NAT, which assigns a different external port for every destination. Some corporate firewalls and certain carrier-grade NAT implementations use symmetric NAT, making the external port unpredictable and hole punching impossible.
For those cases, Tailscale falls back to DERP (Designated Encrypted Relay for Packets). DERP servers are hosted by Tailscale in multiple geographic regions. When a direct connection cannot be established, traffic is relayed through the closest DERP server. Critically, DERP traffic is still end-to-end WireGuard encrypted — the DERP server relays encrypted packets it cannot read. You trade latency for reliability.
Connection establishment sequence:
1. Try direct connection (hole punching) ─── success → use direct
2. Try peer relay ─── success → use peer relay
3. Fall back to DERP relay ─── always works, higher latency
You can check which connection type is active with tailscale status — it shows direct or relay for each peer along with the latency.
Installation and Basic Setup
The client is available for Linux, macOS, Windows, iOS, Android, and as a Docker image. On most platforms:
|
|
Every device you add authenticates through your identity provider (Google, GitHub, Microsoft, or SAML/OIDC for enterprise). The authentication step is one-time — after that, the device is trusted until you revoke it. No key files to distribute, no certificate renewals, no authorized_keys management.
Subnet Routing
A subnet router advertises routes for an entire private subnet to the tailnet. Devices on that subnet do not need Tailscale installed — they are reachable through the subnet router.
This is the primary use case for homelab access: install Tailscale on one machine (a Pi, a NAS, any always-on device), advertise your home LAN’s subnet, and every device on your tailnet can reach every device on your home network by its normal LAN IP — NAS, printer, IoT devices, switches, anything.
|
|
After approval, devices on your tailnet can reach 192.168.1.x addresses directly through the subnet router. The traffic path:
Your laptop (traveling) → tailnet → subnet router → home LAN device
100.x.y.z WireGuard 192.168.1.10 192.168.1.50
For multiple sites — homelab plus a cloud VPC, or homelab plus a remote office — run a subnet router at each site advertising its local subnet. All sites connect through the tailnet mesh without any additional configuration. There is no VPN gateway to configure, no IPSec tunnel to maintain.
|
|
Exit Nodes
An exit node routes all internet traffic from a tailnet device through another tailnet device. This is the conventional VPN use case: your traffic appears to come from the exit node’s location.
|
|
Practical uses: route traffic through a homelab server to bypass geo-restrictions, use a cloud server as an exit node for consistent egress IP (useful for services that whitelist IPs), or route sensitive work traffic through a known-good corporate egress.
The important distinction from subnet routing: exit nodes affect all internet traffic. Subnet routers only affect traffic destined for the advertised subnet. A device can be both simultaneously, though Tailscale recommends separating the roles onto two different machines to avoid routing table conflicts.
MagicDNS
MagicDNS automatically assigns human-readable DNS names to every device in your tailnet. Instead of remembering 100.x.y.z, you use homelab-server or the fully qualified homelab-server.your-tailnet.ts.net.
Enable it in the DNS settings of your admin console. Once on, every device is reachable by its machine name from any other tailnet device. Names are stable — the name follows the machine, not the IP.
You can also configure custom DNS in MagicDNS to split-DNS your internal domains:
# In the Tailscale admin console → DNS:
# Custom nameservers:
# For domain: internal.example.com → 192.168.1.53 (your internal DNS)
# Global nameserver: 1.1.1.1
# Result: queries for *.internal.example.com go to your internal DNS
# all other queries go to 1.1.1.1
# Works for all devices on the tailnet automatically
This replaces the common homelab pattern of either running a split-horizon DNS server that requires VPN to reach, or hardcoding internal IP addresses everywhere.
Tailscale SSH
Tailscale SSH lets you SSH to tailnet devices without managing SSH keys at all. Authentication is handled by the tailnet identity — if your ACL policy allows it, ssh user@device works, and Tailscale handles the session authentication transparently.
Enable it on a node:
|
|
Then in your tailnet policy file, grant access:
|
|
The check action enables session recording and requires periodic re-authentication, making it suitable for production access patterns where you want an audit trail. Sessions can be recorded to S3 or a local directory.
The practical benefit for homelabs: no ~/.ssh/authorized_keys management. Add a new user to your tailnet and the right ACL group, and they can SSH to the appropriate machines immediately. Revoke them from Tailscale and access is gone instantly — no hunting for SSH keys to delete.
Funnel: Public HTTPS Without a Server
Funnel exposes a local service to the public internet over HTTPS without requiring a public IP, a cloud server, or any inbound firewall rules. Tailscale’s infrastructure proxies inbound traffic to your device.
|
|
Tailscale automatically provisions a TLS certificate for the *.ts.net subdomain. The traffic is encrypted in transit and Tailscale terminates TLS at their edge, then re-encrypts for the last hop to your device (or delivers plaintext to localhost — check the docs for your specific use case).
Serve is the complement to Funnel — it exposes a service only within your tailnet, not the public internet:
|
|
Funnel is not a replacement for a proper reverse proxy under sustained traffic — it is designed for developer sharing, webhooks, temporary public access, and homelab services where you do not have an inbound-capable server. For a production public service, a VPS with nginx/Caddy is still the right answer.
ACL Policies: Access Control
The tailnet policy file is a JSON document that defines who can reach what. By default, all devices on a tailnet can reach all other devices. The ACL file lets you restrict this.
|
|
Tags assign a role to a device independent of who owns it. A tag:servers device is a server regardless of which user registered it. This is important because if the user who registered a device leaves the organization and their account is removed, untagged devices disappear with them. Tagged devices persist.
The policy file is version-controlled (Tailscale’s admin console shows a diff view and history), and changes are applied to all devices in the tailnet within seconds.
Headscale: Self-Hosting the Control Plane
Headscale is an open-source reimplementation of the Tailscale coordination server. Your devices still use the official Tailscale client software; only the coordination server is replaced. The WireGuard mesh, NAT traversal, and DERP relay selection all function the same way — though for DERP, you can either continue using Tailscale’s public DERP infrastructure or run your own DERP servers.
Reasons to run Headscale: complete metadata sovereignty (no public keys or device information leaves your infrastructure), independence from Tailscale’s pricing and account policies, and the ability to run a tailnet with more than the free tier’s device limit without a subscription.
The tradeoff: Headscale implements most but not all Tailscale features. As of 2025, Tailscale SSH, Funnel, and some enterprise ACL features are absent or partial. For homelab use, the feature set is generally sufficient.
Deploying Headscale with Docker:
|
|
|
|
Headscale must be accessible from the public internet (or at least from all devices that will join the network) — it needs a reachable address and port for device registration. A VPS with a public IP is the typical host. Put nginx or Caddy in front for TLS termination.
Basic operations:
|
|
After registering, the device behaves identically to a Tailscale-coordinated device: same WireGuard mesh, same 100.x.y.z addressing, same tailscale status output. The only difference is which server coordinates the key exchange.
Practical Homelab Patterns
Pattern 1: Remote access to everything on your LAN
Install Tailscale on any always-on machine (Pi, NAS, mini-PC). Advertise your LAN subnet. Every device you own that has Tailscale installed can reach your entire home network from anywhere — NAS, Proxmox web UI, IPMI consoles, everything.
|
|
Pattern 2: Multi-site mesh with no central VPN gateway
Homelab + cloud VPC + remote office. Each site has one Tailscale device advertising its subnet. All sites form a mesh automatically. No site is “the hub” — any site can reach any other directly.
Pattern 3: Secure IoT isolation
Tag IoT devices (tag:iot). Write ACL rules that prevent IoT devices from initiating connections to any other devices, but allow your management machines to reach them. IoT devices can still reach the internet through their normal LAN gateway — only tailnet-initiated connections are restricted.
Pattern 4: Webhook receiver without a public server
Use tailscale funnel 3000 on your development machine. Paste the *.ts.net URL into the webhook configuration. Receive webhooks from GitHub, Stripe, Twilio directly on your laptop without ngrok. Works through any NAT, no account required.
Pattern 5: Shared access for family or small team
Add users to your tailnet with ACL rules that restrict them to specific services. Revoke access by removing them from Tailscale — no SSH key hunting, no firewall rule cleanup.
Limits and Honest Trade-offs
The free tier allows up to 3 users and 100 devices, which is plenty for personal homelab use. Beyond that, the Personal plan ($6/user/month as of 2025) is needed for features like custom ACLs per-user, and Teams/Enterprise plans for SSO and advanced features.
Relying on Tailscale’s control plane means accepting that if Tailscale’s servers are unreachable for an extended period, you cannot add new devices or push ACL changes. Existing connections between already-connected devices continue working (the mesh is peer-to-peer), but new registrations fail. Headscale eliminates this dependency entirely.
Not all NAT penetration succeeds. Symmetric NAT — found in some enterprise firewalls and certain ISPs — defeats UDP hole punching. Traffic falls back to DERP relay, which adds latency (typically 10-40ms extra depending on which DERP region is closest). For interactive SSH sessions this is imperceptible; for latency-sensitive applications it may matter.
DERP relay traffic is encrypted but visible in volume. Tailscale can observe that traffic is flowing between two of your devices and approximately how much, even though they cannot read its content. For most users this is acceptable; for high-security or compliance scenarios it is worth either running your own DERP servers or evaluating whether Headscale’s full self-hosting model is warranted.
Tailscale is not a replacement for network segmentation at the host level. ACLs in the policy file control which tailnet IPs can reach which services. They do not replace firewall rules on the devices themselves, and they do not provide defense-in-depth if a device is compromised. Run local firewalls on sensitive services in addition to tailnet ACLs, not instead of them.
For most homelab operators, Tailscale is the lowest-friction path to a functional zero-trust network that would have required significant infrastructure and maintenance investment five years ago. The free tier handles most personal use cases. When you hit its limits or want full data sovereignty, Headscale is a straightforward migration — change --login-server and re-register your devices.
Comments