Headscale: Self-Host Your Own Tailscale Control Server
Tailscale makes WireGuard mesh networking trivially easy — but it relies on Tailscale's cloud control plane. Every device in your tailnet authenticates through their servers. For homelab operators who care about privacy, data sovereignty, or simply avoiding vendor dependency, that's a meaningful caveat.
Photo by Aakash Dhage on Unsplash
Headscale is an open-source, self-hosted reimplementation of the Tailscale control server. You run it on your own infrastructure. Your devices still use the official Tailscale clients, but they phone home to your server instead of Tailscale's. The result: a fully self-contained mesh VPN you control completely.
What You Get with Headscale
- Full WireGuard mesh: Every device gets a direct encrypted tunnel to every other device, with NAT traversal via DERP relay servers (you can also self-host those)
- Official Tailscale clients: iOS, Android, Windows, macOS, Linux — all work unchanged
- No Tailscale account required: No SaaS login, no usage limits, no feature tiers
- Subnet routing: Expose entire home or office LAN segments across the tailnet
- Exit nodes: Route all traffic through a trusted node
The tradeoff: you manage the control plane yourself. That means patching Headscale, handling TLS, and occasionally debugging client registration quirks.
Prerequisites
- A Linux server with a public IP (or a reverse proxy in front of it)
- A domain name for the Headscale server (e.g.,
headscale.yourdomain.com) - Port 443 or 8080 accessible from the internet
- Docker or a bare-metal install (this guide uses Docker Compose)
Step 1: DNS and TLS Setup
Headscale clients connect over HTTPS. Create a DNS A record pointing headscale.yourdomain.com to your server's public IP. If your server is behind NAT, set up port forwarding for 443/TCP.
For TLS, Caddy handles certificates automatically if you use it as a reverse proxy:
headscale.yourdomain.com {
reverse_proxy localhost:8080
}
Alternatively, point an nginx or Traefik instance at port 8080.
Like what you're reading? Subscribe to HomeLab Starter — free weekly guides in your inbox.
Step 2: Docker Compose Configuration
Create a directory and config file:
mkdir -p /opt/headscale/config /opt/headscale/data
cd /opt/headscale
Create config/config.yaml:
server_url: https://headscale.yourdomain.com
listen_addr: 0.0.0.0:8080
metrics_listen_addr: 0.0.0.0:9090
private_key_path: /var/lib/headscale/private.key
noise:
private_key_path: /var/lib/headscale/noise_private.key
ip_prefixes:
- 100.64.0.0/10
derp:
server:
enabled: false
urls:
- https://controlplane.tailscale.com/derpmap/default
db_type: sqlite3
db_path: /var/lib/headscale/db.sqlite
acme_url: ""
tls_cert_path: ""
tls_key_path: ""
log:
level: info
dns_config:
override_local_dns: true
nameservers:
- 1.1.1.1
magic_dns: true
base_domain: yourdomain.internal
The ip_prefixes uses Tailscale's CGNAT range — devices get IPs in 100.64.0.0/10. magic_dns lets you reach devices by hostname instead of IP (e.g., ping myserver.yourdomain.internal).
Create docker-compose.yml:
services:
headscale:
image: headscale/headscale:latest
container_name: headscale
restart: unless-stopped
volumes:
- ./config:/etc/headscale
- ./data:/var/lib/headscale
ports:
- "8080:8080"
- "9090:9090"
command: serve
Start it:
docker compose up -d
docker compose logs -f headscale
Step 3: Create a User and Pre-Auth Key
Headscale organizes devices into users (previously called namespaces). Create one:
docker exec headscale headscale users create homelab
Generate a pre-auth key so devices can register without interactive browser login:
docker exec headscale headscale preauthkeys create \
--user homelab \
--reusable \
--expiration 24h
Copy the key — you'll use it when joining each device.
Step 4: Register Devices
On each device, install the official Tailscale client. Then log in pointing at your Headscale server:
Linux:
sudo tailscale up \
--login-server https://headscale.yourdomain.com \
--authkey <your-pre-auth-key>
macOS/Windows: Open a terminal and run:
tailscale login \
--login-server https://headscale.yourdomain.com \
--authkey <your-pre-auth-key>
iOS/Android: In the Tailscale app, tap your account → Use custom control server → enter your Headscale URL, then log in.
Verify the device registered:
docker exec headscale headscale nodes list
You should see the device with an assigned IP like 100.64.0.1.
Step 5: Enable Subnet Routing
To expose your home LAN (e.g., 192.168.1.0/24) to all tailnet devices, run on your homelab server:
sudo tailscale up \
--login-server https://headscale.yourdomain.com \
--advertise-routes=192.168.1.0/24
Then approve the route on the Headscale server:
docker exec headscale headscale routes list
docker exec headscale headscale routes enable -r <route-id>
Other tailnet devices can now reach 192.168.1.x addresses directly through the mesh.
Step 6: Verify Connectivity
From any registered device:
tailscale status # see all peers and their IPs
tailscale ping <peer-ip> # verify direct tunnel
The tailscale ping output will tell you if the connection is direct (fast) or relayed through a DERP server (slower, but still encrypted). Most home-to-home connections go direct once NAT traversal completes.
Optional: Self-Host DERP Relay Servers
Tailscale's DERP servers act as relay fallbacks when direct WireGuard tunnels can't be established. By default, Headscale uses Tailscale's public DERP servers. To remove this last external dependency, run your own:
# In config.yaml
derp:
server:
enabled: true
region_id: 999
region_code: "homelab"
region_name: "Homelab"
stun_listen_addr: 0.0.0.0:3478
Expose port 3478/UDP and 443/TCP for DERP, and clients will prefer your relay.
Managing the Tailnet
Headscale ships a CLI for all management tasks:
# List all nodes
docker exec headscale headscale nodes list
# Rename a node
docker exec headscale headscale nodes rename --identifier <id> --new-name myserver
# Remove a node
docker exec headscale headscale nodes delete --identifier <id>
# Expire a pre-auth key
docker exec headscale headscale preauthkeys expire --user homelab <key>
There's also a community-built web UI called Headscale-UI if you prefer a browser interface over the CLI.
Upgrade Path
Headscale updates frequently. With Docker Compose, upgrading is:
docker compose pull
docker compose up -d
Check the Headscale releases for breaking config changes before upgrading — the config.yaml schema occasionally changes between minor versions.
When to Use Headscale vs. Tailscale SaaS
Headscale is the right choice when you want zero external dependencies, full control over your mesh, or need more than Tailscale's free tier allows. The official SaaS is easier to operate and gets new features first (like Tailscale SSH and MagicDNS enhancements). For a homelab that's already self-hosting everything else, Headscale fits naturally into the stack.
Once set up, it's largely maintenance-free. Your devices stay connected, subnets stay routed, and no monthly subscription renews.
