You’ve probably heard the pitch: “Tailscale is just WireGuard, but easy.” And yeah, that’s technically true. But that’s like saying a car is “just an engine, but with wheels.” The magic—and the real power—lives in what wraps around that WireGuard core.
Let me be honest: I spent two years managing WireGuard manually. Key distribution? Manual. IP allocation? Manual. Routing? Manual. It worked, sure. But at 2 AM when someone’s key got leaked and I had to rotate every single device, I swore there had to be a better way. Turns out, there was. I just didn’t want to pay for it.
Then Headscale happened. But we’ll get there.
WireGuard Alone Isn’t Enough
WireGuard is phenomenal—it’s fast, it’s lean, it’s auditable. But it’s also a VPN protocol, not a complete solution. You still have to:
- Generate and securely distribute keypairs to every device
- Decide on IP addresses for each node (and not fuck them up)
- Configure routing rules on each peer
- Set up DNS so your devices can actually find each other by name
- Handle devices behind NAT with hole-punching logic
- Manually update when device IPs change
This is why people end up with elaborate Ansible playbooks or custom scripts. It works, but it’s not something you want to debug at 11 PM on a Friday.
Tailscale takes all of that and hides it behind a CLI command. tailscale up, and you’re in the mesh. No key files to juggle. No static configs. Just works.
Getting Started: Five Minutes to a Working Mesh
Installation is stupidly simple:
# macOSbrew install tailscale
# Linux (Ubuntu/Debian)curl -fsSL https://tailscale.com/install.sh | sh
# Windows# Download from tailscale.com/download/windows
# Then just run:tailscale upYour browser opens, you authenticate with Google/Microsoft/GitHub (or your organization’s SSO if you’re on a team account), and boom—you’re on the tailnet. Your device gets a 100.x.x.x IP automatically. Other devices see it immediately.
Try pinging another device:
tailscale status # See all devices on your tailnetping 100.x.x.123 # Ping another node directlyIt just works. No configuration files. No routing tables. No crying.
ACLs: The Policy Engine That Keeps Everyone Honest
The real power emerges when you need to control who talks to what. That’s ACLs.
By default, Tailscale allows all devices on your tailnet to reach all other devices. That’s fine if you trust everyone. But once you’re connecting home servers, work laptops, VPS instances, and IoT garbage into the same mesh, you want granular control.
Open the Tailscale admin console at https://login.tailscale.com/admin/acls/file and you’ll see the ACL policy file in JSON:
{ "acls": [ // Admins can access everything { "action": "accept", "src": ["group:admins"], "dst": ["*:*"] },
// Work laptops can reach work servers, but not home lab { "action": "accept", "src": ["tag:work"], "dst": ["tag:work-servers:*"] },
// Home lab devices (tagged with `tag:homelab`) can only reach each other { "action": "accept", "src": ["tag:homelab"], "dst": ["tag:homelab:*"] },
// Home servers can reach the database, but nothing else { "action": "accept", "src": ["tag:home-servers"], "dst": ["tag:database:443,5432"] },
// Everything else is denied { "action": "deny", "src": ["*"], "dst": ["*:*"] } ],
"tagOwners": { // Only the admin can assign tags; you could delegate to specific users too "homelab": ["autogroup:admin"], "work": ["autogroup:admin"], "home-servers": ["autogroup:admin"], "database": ["autogroup:admin"] }}Tags are assigned to devices during the auth flow or by editing them in the admin console. Once tagged, they inherit ACL rules. Change the policy, and it’s live immediately—no SSH-ing into each device to update configs.
Subnet Routers: Bridge Your Home Lab Into the Mesh
Not every device on your home network needs Tailscale installed. You’ve got printers, NAS boxes, smart light switches—devices where installing Tailscale isn’t an option.
Enter: subnet routers.
Set up one Linux box on your home network as a subnet router, and it becomes the gateway. Every device on your home network suddenly accessible from the tailnet, without installing Tailscale on them.
On your router machine (or a spare VM):
# Enable IP forwardingecho 'net.ipv4.ip_forward = 1' | sudo tee -a /etc/sysctl.confecho 'net.ipv6.conf.all.forwarding = 1' | sudo tee -a /etc/sysctl.confsudo sysctl -p
# Advertise the home network (assume your LAN is 192.168.1.0/24)sudo tailscale up --advertise-routes=192.168.1.0/24
# Enable route acceptance in the admin console (one-time)# Login to https://login.tailscale.com/admin/machines# Click your router device → "Approve subnets"Now any device on the tailnet can reach 192.168.1.0/24. Ping the printer, RDP into the NAS, SSH into the Proxmox host—all through encrypted WireGuard tunnels, no VPN app installed on those devices.
Exit Nodes: Route Everything Through a VPS (Or Your Home)
Ever want to tunnel all your traffic through a remote server? Tailscale’s exit node does exactly that.
Set a VPS as an exit node:
# On your VPSsudo tailscale up --advertise-exit-nodeThen on any client:
# Use this VPS as the exit node for all traffictailscale set --exit-node-allow-lan-access=falsetailscale set --exit-node=<vps-ip>
# Check it's workingcurl https://ifconfig.me # Should show your VPS's public IP
# Disable it when donetailscale set --exit-node=Why use this? Appear to be in another country (geo-locked services). Hide your home IP when accessing stuff from the road. Route all traffic through your own server instead of trusting a random VPN provider.
One warning: exit nodes can get expensive bandwidth-wise. Be realistic about how much you’ll actually use it before flipping the switch.
MagicDNS and Split DNS
Manually remembering 100.x.x.45 sucks. MagicDNS gives you DNS names automatically.
In the admin console, enable MagicDNS. Now your devices are accessible by name:
ping mydesktop.tail12345.ts.netssh user@homeserver.tail12345.ts.netcurl https://nextcloud.tail12345.ts.netSplit DNS lets you override DNS for specific domains:
{ "dnsConfig": { "nameservers": ["1.1.1.1"], "overrides": { "internal.example.com": ["100.x.x.10"], "*.lab": ["100.x.x.11"] } }}Now queries for *.internal.example.com go to your internal DNS server (on the tailnet), but everything else goes to Cloudflare. No more keeping a split-brain DNS setup.
Taildrive and Taildrop: File Sharing Built In
Need to send files across the tailnet? Taildrop is instant peer-to-peer file sharing:
tailscale file cp ~/document.pdf alice@ # Sends to Alice's devicetailscale file list # See incoming filesFiles get pushed directly to the recipient’s device over the mesh—no central server needed.
Taildrive (newer) lets you mount directories from other devices as network shares:
# On the machine with files to share:tailscale drive share Documents
# On another device:tailscale drive list # See shared directories# Access via file browser or mount in /mnt/tailscale/...Perfect for backing up configs or accessing shared storage without NFS/SMB complexity.
Headscale: The Self-Hosted Escape Hatch
Here’s the uncomfortable truth: Tailscale works because you’re trusting Tailscale’s servers with your authentication and control plane. For most people, that’s fine. They’re a reputable company, they’ve never had a major breach, and closed-source control planes are actually pretty normal.
But what if you want full control? Or you don’t want to rely on another SaaS company? Enter Headscale—an open-source control plane that speaks the Tailscale protocol.
Headscale isn’t a Tailscale replacement; it’s a WireGuard controller that’s compatible with Tailscale clients. You run Headscale on your own hardware, point your devices at it, and you own the whole thing.
Basic Headscale setup (on a Linux box):
# Install Headscalecurl -L -o headscale.deb https://github.com/juanfont/headscale/releases/download/v0.24.0/headscale_0.24.0_linux_amd64.debsudo dpkg -i headscale.deb
# Configure itsudo nano /etc/headscale/config.yaml# Set server_url to your domain, listen_addr to localhost, etc.
# Start the servicesudo systemctl enable headscalesudo systemctl start headscale
# Create a namespace (like a team)headscale namespaces create personal
# Invite a deviceheadscale preauthkeys create -n personalOn your client:
tailscale up --login-server=https://headscale.yourdomain.com
# Approve the auth request on the Headscale serverheadscale nodes listheadscale nodes register --user=personal --key=<auth-key>Now you’ve got a complete, self-hosted mesh. No reliance on Tailscale’s infrastructure. No closed-source control plane. Just WireGuard doing what WireGuard does best.
The tradeoff? You’re responsible for backups, certificate renewal, and uptime. It’s absolutely worth learning, but know what you’re signing up for.
Tailscale vs Self-Managed WireGuard: The Honest Tradeoff
Go with Tailscale if:
- You want it to work now without ops toil
- You have 2–20 devices and don’t mind the cloud control plane
- You need users beyond your immediate household (team features, SSO)
- You can stomach the $10/user/month for the team version
Go with self-managed WireGuard or Headscale if:
- You need zero external dependencies
- You have the time/energy to write and maintain configs
- You want true air-gapped networking
- You’re comfortable debugging networking issues yourself
Honestly? For my setup—a handful of personal devices plus a home lab—I use Tailscale. The control plane stays with Headscale, but I’ll probably migrate a home instance before long. The time saved not managing keys and IPs is worth way more than the cognitive load of running another service.
Pick what fits. The whole point of mesh networking is flexibility.