Tailscale is brilliant — it creates a zero-config mesh VPN using WireGuard that just works. But it routes all coordination through Tailscale’s servers. If you want that same magic while keeping full control, Headscale is the answer.
Headscale is an open-source, self-hosted implementation of the Tailscale coordination server. Your devices still use the official Tailscale client, but they talk to your server instead of Tailscale’s cloud. Same great UX, complete sovereignty.
Why Headscale?
- Full control — your coordination server, your rules
- No account limits — Tailscale’s free tier caps at 100 devices; Headscale has no limits
- Privacy — connection metadata never leaves your infrastructure
- Same clients — uses official Tailscale apps on every platform
- WireGuard underneath — proven, audited crypto
- ACLs — fine-grained access control between devices
- Exit nodes — route internet traffic through any device
- MagicDNS — automatic DNS for all your devices
How It Works
Headscale handles key exchange and device registration. Actual traffic flows directly between devices via WireGuard (peer-to-peer) — your server isn’t a bottleneck.
Prerequisites
- A Linux server with a public IP (VPS works great — $5/month)
- A domain name pointed at that server
- Docker and Docker Compose
- SSL certificate (we’ll use Caddy for auto-HTTPS)
Note: Headscale works best on a VPS with a stable public IP. You can run it at home, but you’ll need port forwarding and dynamic DNS.
Step 1: Project Setup
mkdir -p ~/headscale/config && cd ~/headscale
Step 2: Generate Configuration
# Download the example config
wget -O config/config.yaml \
https://raw.githubusercontent.com/juanfont/headscale/main/config-example.yaml
Edit config/config.yaml with the essential settings:
# The URL clients will use to reach your Headscale server
server_url: https://hs.yourdomain.com
# Listen addresses
listen_addr: 0.0.0.0:8080
metrics_listen_addr: 0.0.0.0:9090
# gRPC for CLI
grpc_listen_addr: 0.0.0.0:50443
grpc_allow_insecure: false
# Private key storage
private_key_path: /var/lib/headscale/private.key
noise:
private_key_path: /var/lib/headscale/noise_private.key
# IP ranges for your mesh network
prefixes:
v4: 100.64.0.0/10
v6: fd7a:115c:a1e0::/48
# DERP (relay servers for when direct connections fail)
derp:
server:
enabled: true
region_id: 999
region_code: "self"
region_name: "Self-Hosted"
stun_listen_addr: "0.0.0.0:3478"
urls: []
auto_update_enabled: false
# Database
database:
type: sqlite
sqlite:
path: /var/lib/headscale/db.sqlite
# DNS configuration
dns:
magic_dns: true
base_domain: mesh.local
nameservers:
global:
- 1.1.1.1
- 9.9.9.9
Key changes from the example:
- Set
server_urlto your actual domain - Enable the built-in DERP relay
- Configure MagicDNS with a base domain
- Use SQLite (simple, no extra services needed)
Step 3: Docker Compose
Create docker-compose.yml:
services:
headscale:
image: headscale/headscale:latest
container_name: headscale
volumes:
- ./config:/etc/headscale
- headscale-data:/var/lib/headscale
ports:
- "8080:8080"
- "9090:9090"
- "3478:3478/udp"
command: serve
restart: unless-stopped
caddy:
image: caddy:latest
container_name: headscale-caddy
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy-data:/data
restart: unless-stopped
volumes:
headscale-data:
caddy-data:
Create Caddyfile:
Step 4: Start Headscale
docker compose up -d
Verify it’s running:
docker exec headscale headscale version
Step 5: Create a User
Headscale organizes devices under “users” (similar to Tailscale’s tailnets):
# Create a user for yourself
docker exec headscale headscale users create bird
# List users
docker exec headscale headscale users list
Step 6: Register Devices
Linux
# Install Tailscale client
curl -fsSL https://tailscale.com/install.sh | sh
# Connect to YOUR Headscale server
tailscale up --login-server https://hs.yourdomain.com
This prints a registration URL. Either:
Option A: Register via CLI (on the server)
docker exec headscale headscale nodes register \
--user bird \
--key mkey:abc123...
Option B: Use a pre-auth key
# Generate a key on the server
docker exec headscale headscale preauthkeys create \
--user bird \
--reusable \
--expiration 24h
# Use it on the client
tailscale up --login-server https://hs.yourdomain.com --authkey <key>
Pre-auth keys are great for automating device registration (Docker containers, cloud VMs, etc.).
macOS
- Install Tailscale from the App Store
- Open Terminal:
tailscale up --login-server https://hs.yourdomain.com - Register the node on your server
Windows
- Install Tailscale from tailscale.com/download
- Open Command Prompt as admin:
iOS / Android
The official Tailscale apps support custom coordination servers:
- Install the Tailscale app
- Before signing in, tap the three dots → “Use custom coordination server”
- Enter
https://hs.yourdomain.com - Complete registration
Step 7: Verify Your Mesh
From any connected device:
# List all devices in your network
tailscale status
# Ping another device
tailscale ping home-server
# Check direct vs relayed connection
tailscale netcheck
Step 8: Access Control Lists (ACLs)
Control which devices can talk to each other. Create config/acl.yaml:
# Allow all devices to reach the home server
acls:
- action: accept
src: ["*"]
dst: ["home-server:*"]
# Allow laptop to reach everything
- action: accept
src: ["bird-laptop"]
dst: ["*:*"]
# Allow phone only to access specific ports
- action: accept
src: ["bird-phone"]
dst: ["home-server:80,443,8080"]
Apply ACLs:
docker exec headscale headscale policy set -f /etc/headscale/acl.yaml
Step 9: Exit Nodes
Route all internet traffic through a specific device (like a VPN):
On the exit node device:
# Enable IP forwarding
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
# Advertise as exit node
tailscale up --login-server https://hs.yourdomain.com --advertise-exit-node
Approve it on the server:
docker exec headscale headscale routes enable -r <route-id>
On your phone/laptop:
tailscale up --exit-node=home-server
Now all traffic routes through your home server — instant VPN from anywhere.
Step 10: Subnet Routes
Expose your entire home network to mesh devices:
# On a device on your home LAN
tailscale up --login-server https://hs.yourdomain.com \
--advertise-routes=192.168.1.0/24
Approve on the server:
docker exec headscale headscale routes list
docker exec headscale headscale routes enable -r <route-id>
Now any mesh device can reach 192.168.1.x addresses directly — access printers, NAS, cameras, anything on your LAN from anywhere.
Headscale UI (Optional)
For a web dashboard, add Headscale-UI:
headscale-ui:
image: ghcr.io/gurucomputing/headscale-ui:latest
container_name: headscale-ui
ports:
- "8443:443"
restart: unless-stopped
Add to Caddyfile:
Troubleshooting
Device can’t connect to Headscale
- Verify DNS:
dig hs.yourdomain.comshould return your server’s IP - Check HTTPS:
curl https://hs.yourdomain.comshould respond - Check firewall: ports 80, 443, 3478/udp must be open
- View logs:
docker compose logs headscale
Devices connected but can’t reach each other
- Check
tailscale status— devices should show as online - Run
tailscale ping <device>— shows if connection is direct or relayed - Enable the DERP relay if direct connections fail (NAT issues)
- Verify ACLs aren’t blocking traffic
Slow connections
- Check if traffic is being relayed:
tailscale netcheck - Open UDP port 41641 on both ends for direct WireGuard connections
- The DERP relay is a fallback — direct connections are always faster
MagicDNS not resolving
- Verify
magic_dns: truein config - Restart Tailscale on the client:
sudo tailscale down && sudo tailscale up - Check
/etc/resolv.conf— Tailscale should have added its DNS
Resource Usage
| Component | RAM | CPU | Notes |
|---|---|---|---|
| Headscale | ~30MB | Minimal | Coordination only |
| Caddy | ~20MB | Minimal | TLS termination |
| DERP relay | ~10MB | Low | Only during relayed connections |
| Total | ~60MB | Minimal |
Headscale vs Alternatives
| Feature | Headscale | Tailscale | WireGuard (manual) | ZeroTier |
|---|---|---|---|---|
| Self-hosted control | ✅ | ❌ | ✅ | Partial |
| Zero config | ✅ | ✅ | ❌ | ✅ |
| NAT traversal | ✅ | ✅ | ❌ | ✅ |
| Device limit | Unlimited | 100 (free) | Unlimited | 25 (free) |
| MagicDNS | ✅ | ✅ | ❌ | ✅ |
| ACLs | ✅ | ✅ | Manual iptables | ✅ |
| Mobile apps | ✅ (Tailscale) | ✅ | Third-party | ✅ |
| Exit nodes | ✅ | ✅ | Manual | ✅ |
Conclusion
Headscale gives you everything great about Tailscale — the seamless mesh networking, NAT traversal, MagicDNS, exit nodes — without depending on anyone else’s infrastructure. Your keys, your coordination, your network.
It’s the ultimate self-hosting flex: a VPN that connects all your devices automatically, works behind any NAT, and runs on a $5 VPS with 60MB of RAM. Set it up once and you’ll wonder how you ever managed without it.