How to Self-Host Headscale: Open Source Tailscale Control Server 2026
TL;DR
Headscale (BSD 3-Clause, ~18K GitHub stars, Go) is an open source, self-hosted implementation of the Tailscale coordination server. It lets you run your own Tailscale-compatible VPN mesh with all official Tailscale clients — macOS, Windows, iOS, Android, Linux. Tailscale's free tier is limited to 3 users and 100 devices; the Teams plan is $6/user/month. Headscale is free with unlimited users and devices. You run the control plane; WireGuard handles the actual encrypted tunnels between devices.
Key Takeaways
- Headscale: BSD 3-Clause, ~18K stars — open source Tailscale control server
- All official clients: Use the official Tailscale apps on iOS, Android, macOS, Windows, Linux
- WireGuard mesh: Devices connect peer-to-peer via WireGuard — you don't proxy traffic
- Unlimited users/devices: No per-seat pricing
- Exit nodes: Route all internet traffic through a specific device
- Split tunneling: Only route specific subnets through the VPN
How Tailscale / Headscale Works
Without Tailscale:
Device A ←→ Internet ←→ Device B (blocked by NAT/firewall)
With Tailscale/Headscale:
Device A ←→ [WireGuard encrypted tunnel] ←→ Device B
(direct peer-to-peer after initial coordination)
Control plane (Headscale): Manages identity, key exchange, routing tables
Data plane (WireGuard): Encrypted p2p tunnels between devices
Part 1: Server Setup
Docker Setup
# docker-compose.yml
services:
headscale:
image: headscale/headscale:latest
container_name: headscale
restart: unless-stopped
ports:
- "8080:8080" # gRPC/HTTP API
- "9090:9090" # Metrics
volumes:
- ./config:/etc/headscale
- headscale_data:/var/lib/headscale
command: headscale serve
volumes:
headscale_data:
mkdir config
Configuration
# 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:
- fd7a:115c:a1e0::/48 # IPv6
- 100.64.0.0/10 # IPv4 CGNAT range (Tailscale default)
derp:
server:
enabled: false # Disable embedded DERP — use Tailscale's DERP or run your own
urls:
- https://controlplane.tailscale.com/derpmap/default
disable_check_updates: true
database:
type: sqlite3
sqlite:
path: /var/lib/headscale/db.sqlite
log:
level: info
docker compose up -d
HTTPS with Caddy
headscale.yourdomain.com {
reverse_proxy localhost:8080
}
Part 2: Create Users
In Headscale, "users" are namespaces (one user per device group or person):
# Create a user:
docker exec headscale headscale users create alice
# List users:
docker exec headscale headscale users list
Part 3: Connect Devices
Linux (CLI)
# Install Tailscale client:
curl -fsSL https://tailscale.com/install.sh | sh
# Connect to your Headscale server instead of Tailscale:
sudo tailscale up \
--login-server https://headscale.yourdomain.com \
--accept-routes \
--accept-dns=false
# This outputs a URL — register the device:
# https://headscale.yourdomain.com/register/MKEY:...
On the Headscale server, register the device:
# Get pending registration requests:
docker exec headscale headscale nodes register --user alice --key MKEY:your-machine-key
# List registered nodes:
docker exec headscale headscale nodes list
macOS / Windows / iOS / Android
- Install the official Tailscale app
- Not signed in yet → click "Sign in"
- The sign-in page appears
- Change server / Use a custom server →
https://headscale.yourdomain.com - A URL appears — register via Headscale CLI:
docker exec headscale headscale nodes register --user alice --key nodekey:...
Using Headscale UI (Optional)
Headscale UI adds a web GUI:
# Add to docker-compose.yml:
services:
headscale-ui:
image: ghcr.io/gurucomputing/headscale-ui:latest
container_name: headscale-ui
restart: unless-stopped
ports:
- "8082:80"
headscale.yourdomain.com {
handle /web* {
reverse_proxy localhost:8082
}
reverse_proxy localhost:8080
}
Part 4: Key Management
# Generate a pre-auth key (for automated device enrollment):
docker exec headscale headscale preauthkeys create \
--user alice \
--reusable \
--expiration 24h
# Output: nodekey:your-preauth-key
# Use on device (no manual registration required):
sudo tailscale up \
--login-server https://headscale.yourdomain.com \
--auth-key nodekey:your-preauth-key
Pre-auth keys are useful for:
- Automating server enrollment in Ansible/Terraform
- Docker containers joining the tailnet
Part 5: Exit Nodes
Route all internet traffic through a specific device (like a home server):
On the exit node device:
sudo tailscale up --login-server https://headscale.yourdomain.com --advertise-exit-node
Approve on Headscale:
docker exec headscale headscale routes list
docker exec headscale headscale routes enable -r ROUTE_ID
On client devices:
# Route all traffic through the exit node:
sudo tailscale up --exit-node=100.64.0.1
# Or just specific subnets:
sudo tailscale up --advertise-routes=192.168.1.0/24
Part 6: Subnet Routing (Access Home Network)
Expose your home LAN to all Tailscale devices:
# On your home server:
sudo tailscale up \
--login-server https://headscale.yourdomain.com \
--advertise-routes=192.168.1.0/24
# Approve on Headscale:
docker exec headscale headscale routes enable -r ROUTE_ID
# On other devices — access home devices by their LAN IP:
# ssh 192.168.1.100 ← your home NAS, from anywhere
Part 7: DNS with MagicDNS
Tailscale's MagicDNS gives each device a name (alice-laptop.headscale.yourdomain.com):
# config/config.yaml
dns_config:
magic_dns: true
base_domain: vpn.yourdomain.com
nameservers:
- 1.1.1.1
- 8.8.8.8
override_local_dns: false
Devices are accessible via device-name.vpn.yourdomain.com from any Headscale node.
Maintenance
# Update Headscale:
docker compose pull
docker compose up -d
# Node management:
docker exec headscale headscale nodes list
docker exec headscale headscale nodes delete --identifier NODE_ID
# User management:
docker exec headscale headscale users list
docker exec headscale headscale users rename --name alice --new-name alice-work
# Backup (SQLite):
tar -czf headscale-backup-$(date +%Y%m%d).tar.gz \
$(docker volume inspect headscale_headscale_data --format '{{.Mountpoint}}') \
./config/
# Logs:
docker compose logs -f headscale
See all open source networking and VPN tools at OSSAlt.com/categories/networking.