Overview
I spent a weekend building a WireGuard setup for my home lab. It worked, right up until my ISP rotated my public IP and I had to update the endpoint on every client. Tailscale replaces all of that with one install command and a browser login[reference:5].
The pitch: every device gets a stable 100.x.x.x address, reachable from anywhere, without port forwarding, without a static IP, without firewall rules. This is what that looks like in practice.
What you're actually getting
| Feature | What it means |
|---|---|
| Mesh network | Every device talks directly to every other device, peer-to-peer |
| NAT traversal | Works behind CGNAT, hotel wifi, mobile networks — no port forwarding |
| MagicdnS | Reach devices by hostname instead of IP |
| Subnet routing | Reach devices that don't have Tailscale installed |
| Exit nodes | Route all your traffic through a remote server |
| Tailscale SSH | SSH access without managing keys |
Under the hood, it's WireGuard. Tailscale's value is the coordination layer — key distribution, NAT traversal, and access control — which is the part that made running WireGuard manually annoying[reference:6].
Step 1: Install on your server
curl -fsSL https://tailscale.com/install.sh | sh
This adds the repo and installs the latest stable release. Verify the daemon is running:
tailscale version
sudo systemctl status tailscaled --no-pager | head -8
The status should show Active: active (running) with Status: "Needs login"[reference:7]. That's expected — the daemon is up but hasn't joined a network.
Step 2: Authenticate
sudo tailscale up --hostname=homelab
A URL prints. Open it in any browser where you're signed into Tailscale, approve the device, and the CLI returns to a prompt[reference:8]. Set a hostname you'll recognize — it becomes the MagicDNS name.
Verify:
tailscale status
tailscale ip -4
status lists every device on your tailnet with its IP, OS, and connection method (direct or relayed). ip -4 prints your server's 100.x.x.x address.
Step 3: Install on your devices
Download the app for macOS, Windows, Linux, iOS, or Android from the Tailscale site. Sign in with the same account, and every device can reach every other[reference:9].
# From your laptop, once Tailscale is running
ping homelab
ssh homelab
curl http://homelab:8096
MagicDNS makes this work without remembering IPs. Enable it in the admin console under DNS → Enable MagicDNS[reference:10].
Subnet routing: reach devices without Tailscale
Tailscale only reaches devices with Tailscale installed. Your printer, your NAS, your IoT devices — none of them run Tailscale. A subnet router bridges that gap.
# On the server, advertise your local subnet
sudo tailscale up --advertise-routes=192.168.1.0/24
# Enable IP forwarding
echo 'net.ipv4.ip_forward = 1' | sudo tee -a /etc/sysctl.d/99-tailscale.conf
echo 'net.ipv6.conf.all.forwarding = 1' | sudo tee -a /etc/sysctl.d/99-tailscale.conf
sudo sysctl -p /etc/sysctl.d/99-tailscale.conf
Then approve the route in the admin console under Machines → your server → Edit route settings[reference:11]. Without approval, the route is advertised but not used.
Now from any device on your tailnet, 192.168.1.50 routes through the subnet router. Your printer is reachable from a coffee shop.
Exit nodes: route all traffic through a remote server
An exit node lets you send all your internet traffic through a specific machine — useful for public wifi, or for making your traffic appear to come from a different country.
# On the server
sudo tailscale up --advertise-exit-node
# On your laptop, use it
sudo tailscale up --exit-node=homelab
Approve the exit node in the admin console first. Then check your public IP from the laptop — it should match the server's.
For a VPS exit node, the setup is the same. Pick a location with good latency and enough bandwidth, and enable IP forwarding on the VPS.
Tailscale SSH: keys without key management
sudo tailscale up --ssh
This enables SSH access controlled by Tailscale's ACLs rather than ~/.ssh/authorized_keys. You still need an SSH client, but authentication is handled by the tailnet — no keys to copy around[reference:12].
Access control lives in the admin console under Access Controls. The default policy allows everything; a reasonable starting policy:
{
"acls": [
{
"action": "accept",
"src": ["autogroup:member"],
"dst": ["*:*"]
}
],
"ssh": [
{
"action": "accept",
"src": ["autogroup:member"],
"dst": ["homelab"],
"users": ["root", "deploy"]
}
]
}
Docker and containers
For a service running in Docker, you have two options. Install Tailscale on the host and expose the container's port on localhost — simplest, and works for most cases. Or run Tailscale in a sidecar container and share the network namespace.
The sidecar pattern is useful when you want the service to be only reachable via Tailscale, not on the host's LAN:
services:
tailscale:
image: tailscale/tailscale:latest
environment:
- TS_AUTHKEY=${TS_AUTHKEY}
- TS_STATE_DIR=/var/lib/tailscale
volumes:
- tailscale-state:/var/lib/tailscale
cap_add:
- NET_ADMIN
- SYS_MODULE
app:
image: myapp
network_mode: service:tailscale
volumes:
tailscale-state:
The app container shares Tailscale's network namespace, so it's reachable on the tailnet but not on the host network.
What it costs
Free for personal use: up to 100 devices and 3 users[reference:13]. That's more than enough for a home lab and a handful of VPS machines. Paid plans add SSO, audit logging, and more users.
When not to use Tailscale
- You need public access. Tailscale is private by design. For a public website, use Cloudflare Tunnel or a reverse proxy with proper auth.
- You want to avoid a third-party coordination server. Tailscale's control plane knows your device list and can see connection metadata. If that's a concern, headscale is an open-source reimplementation you can self-host.
- You're connecting two fixed servers and nothing else. Plain WireGuard between two static IPs is simpler and has no dependencies.
For everything else — accessing a home lab from a laptop, reaching a VPS from a phone, letting a few machines talk without opening ports — Tailscale is the shortest path I've found.
