Tailscale: A Mesh VPN for Your Servers and Homelab

Photo by Arenamontanus on flickr
Tailscale is a mesh VPN built on the WireGuard protocol that gives every device a stable private IP and connects devices directly to each other whenever possible. Unlike a traditional VPN that routes all traffic through one central gateway, Tailscale only uses its coordination server for authentication and key exchange, so actual data can travel peer to peer with lower latency and no single point of failure.
Headscale is an open source, community maintained reimplementation of Tailscale control server that speaks the same protocol as official Tailscale clients. Teams choose it when they want the coordination plane running on infrastructure they fully own, whether for compliance requirements, cost control at large device counts, or simply to avoid depending on a third party service for core connectivity.
A subnet router is a regular tailnet device that advertises a whole local network range, such as a homelab LAN, to the rest of the mesh. Other devices on the tailnet can then reach anything on that advertised subnet, including printers, NAS appliances, or embedded devices, without installing the Tailscale client on each of them individually.
Yes, Tailscale enforces access control through a tailnet policy file written in a JSON flavored syntax. You tag devices by role, such as server or homelab, and write explicit accept rules naming the source tag, destination tag, and allowed ports, so once any rule exists everything not explicitly permitted is denied by default.
Existing peer to peer connections continue working because the WireGuard keys were already exchanged and cached on each device before the outage. New device registrations and fresh key exchanges will fail until the control server is back online, but already connected machines keep talking to each other over their established encrypted tunnels.

Photo by Arenamontanus on flickr
Key Takeaway
Tailscale builds an encrypted mesh network on top of WireGuard, giving every server, laptop, and homelab device a stable private IP and direct peer-to-peer connections instead of routing through a single VPN gateway. This guide covers installing Tailscale, exposing a homelab subnet through a router node, locking down access with ACLs, and self-hosting the control plane with Headscale.
Every developer eventually hits the same wall: a database sitting on a VPS that should never touch the public internet, a homelab NAS behind a residential router with no static IP, and a laptop that needs to reach both without opening a single port. Traditional VPNs solve this with a central gateway that becomes a bottleneck and a single point of failure. Tailscale takes a different approach entirely.
Tailscale builds a mesh network on top of WireGuard, where every device gets a stable private IP and talks directly to every other device over an encrypted tunnel, no matter where it physically sits. This post walks through installing it across servers and a homelab, locking traffic down with access control lists, exposing whole subnets through a router node, and finally replacing Tailscale's hosted coordination server with your own self-hosted Headscale instance.
Classic VPNs route every packet through one gateway. That gateway has to scale with your total traffic, it is the one thing that takes the whole network down if it fails, and it adds a hop of latency to every single connection even between two machines sitting in the same data center. A mesh network flips this: the control server only handles authentication and exchanges public keys, while the actual data travels peer to peer whenever a direct path exists.
Start by installing Tailscale on exactly two machines, a home laptop and one cloud server, and confirm you can ping the server by its assigned private IP before adding anything else. Getting the smallest possible mesh working first makes every later step, ACLs, subnet routes, self-hosting, much easier to debug.
The client installs the same way on Linux, macOS, and Windows, and the first run walks you through browser based authentication against your identity provider. Once authenticated, the device is assigned a stable IP in the 100.64.0.0 to 100.127.255.255 range that never changes even if you rejoin the network later. MagicDNS then gives every device a human readable hostname like homelab-nas dot your tailnet name, so you almost never need to memorize the IP addresses at all.
# Install on a Linux server
curl -fsSL https://tailscale.com/install.sh | sh
# Authenticate and join the tailnet
sudo tailscale up
# Check assigned IP and hostname
tailscale ip -4
tailscale status
# Ping another node by its MagicDNS name
ping homelab-nas.tailnet-name.ts.netInstalling the client on every single device in a homelab is not always practical, especially for embedded devices, printers, or a NAS appliance that does not run a general purpose operating system. A subnet router solves this by advertising an entire local network range to the tailnet from one machine that does run the client, letting every other device on the mesh reach anything on that subnet without installing anything extra.
# Enable IP forwarding on the router node
echo 'net.ipv4.ip_forward = 1' | sudo tee -a /etc/sysctl.conf
echo 'net.ipv6.conf.all.forwarding = 1' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p /etc/sysctl.conf
# Advertise the homelab's LAN subnet
sudo tailscale up --advertise-routes=192.168.1.0/24
# On another device, accept the route from the admin console,
# then enable it locally if using the CLI approval flow
sudo tailscale up --accept-routesA subnet router that advertises 0.0.0.0/0 or an overly broad range can accidentally expose far more of your network than intended. Always advertise the narrowest CIDR block that covers only the devices you actually want reachable, and double check the approved routes list in the admin console after every change.
By default every device on a tailnet can reach every other device, which is fine for a two machine test but is not what you want once servers, laptops, and a partner's contractor account all share the same mesh. The tailnet policy file, written in a JSON flavored syntax called HuJSON, lets you define tags for groups of devices and then write explicit accept rules describing exactly which tagged group can reach which ports on which other tagged group.
{
"tagOwners": {
"tag:server": ["autogroup:admin"],
"tag:homelab": ["autogroup:admin"]
},
"acls": [
{
"action": "accept",
"src": ["autogroup:member"],
"dst": ["tag:homelab:22,80,443,8080"]
},
{
"action": "accept",
"src": ["tag:server"],
"dst": ["tag:server:5432,6379"]
}
],
"ssh": [
{
"action": "check",
"src": ["autogroup:member"],
"dst": ["tag:server"],
"users": ["autogroup:nonroot"]
}
]
}| Concept | What it does | Practical note |
|---|---|---|
| Tag | Labels a device by role instead of by owner, such as server or homelab. | Tags survive device re-registration, unlike per user rules. |
| ACL rule | An explicit accept statement naming a source, a destination, and allowed ports. | Everything not explicitly accepted is denied once any rule exists. |
| Autogroup | Built in groups like members or admin that reference account roles. | Useful for rules that should apply to every human user, not a specific tag. |
| SSH check | A policy block that gates SSH access through Tailscale's own SSH feature. | Removes the need to distribute separate SSH keys to every server. |
Tailscale's hosted coordination server is free for personal use and handles authentication, key exchange, and DERP relay fallback for you, but some teams want the control plane itself running on infrastructure they own, whether for compliance, cost at scale, or simple curiosity. Headscale is an open source, community maintained reimplementation of that control server that speaks the same protocol as the official Tailscale clients, so the same install script and CLI commands work unmodified against it.
# docker-compose.yml
services:
headscale:
image: headscale/headscale:0.23.0
container_name: headscale
restart: unless-stopped
ports:
- "8080:8080"
volumes:
- ./config:/etc/headscale
- ./data:/var/lib/headscale
command: headscale serve
# Register a new device against your own control server
sudo tailscale up --login-server=https://vpn.example.comHeadscale is community maintained and not an official Tailscale product, but it deliberately targets protocol compatibility with the real Tailscale clients, so you keep the same install experience your team already knows while owning the control plane yourself.
Before treating any mesh VPN as production infrastructure for real servers, work through a short checklist so a misconfiguration does not turn into an outage or, worse, an accidental exposure.
A mesh VPN built on WireGuard removes an entire category of networking pain: no more manually punching holes in firewalls, no more juggling dynamic residential IPs, and no more single point of failure sitting between your laptop and the server you need to SSH into at two in the morning. Whether you stick with Tailscale's hosted control plane or move to a self-hosted Headscale instance, the underlying mesh behaves the same way, which makes it a rare piece of infrastructure you can adopt gradually without locking yourself into one vendor's control plane forever.