
Headscale: Self-Hosted Tailscale Control Server on Linux
- AuthorTimothy M.
- Date
- Time17 minutes
Headscale reimplements the Tailscale control plane so you can keep coordination private while still using official Tailscale clients. It is ideal when compliance, data residency, or cost rules block SaaS VPN control planes. This guide installs Headscale behind HTTPS and enrolls the first nodes with ACLs.
Note: Headscale is community software and not affiliated with Tailscale Inc. Validate licensing and supported client versions before production rollout.
Prerequisites
- Public Linux VPS for the control server
- DNS for headscale.example.com
- Clients that can install the Tailscale client
Step 1: Install Headscale
HEADSCALE_VERSION=0.23.0
curl -fsSL -o /tmp/headscale.deb \\
https://github.com/juanfont/headscale/releases/download/v\${HEADSCALE_VERSION}/headscale_\${HEADSCALE_VERSION}_linux_amd64.deb
sudo apt install -y /tmp/headscale.deb
sudo mkdir -p /etc/headscale /var/lib/headscale
sudo useradd --system --home-dir /var/lib/headscale --shell /usr/sbin/nologin headscale || true
sudo chown -R headscale:headscale /var/lib/headscaleStep 2: Configure Headscale
sudo tee /etc/headscale/config.yaml >/dev/null <<'EOF'
server_url: https://headscale.example.com
listen_addr: 127.0.0.1:8080
metrics_listen_addr: 127.0.0.1:9090
grpc_listen_addr: 127.0.0.1:50443
grpc_allow_insecure: false
private_key_path: /var/lib/headscale/private.key
noise:
private_key_path: /var/lib/headscale/noise_private.key
prefixes:
v4: 100.64.0.0/10
v6: fd7a:115c:a1e0::/48
database:
type: sqlite
sqlite:
path: /var/lib/headscale/db.sqlite
dns:
magic_dns: true
base_domain: ts.example.com
nameservers:
global:
- 1.1.1.1
- 9.9.9.9
EOF
sudo chown root:headscale /etc/headscale/config.yaml
sudo chmod 640 /etc/headscale/config.yamlStep 3: systemd and Nginx TLS
sudo systemctl enable --now headscale
sudo systemctl status headscale --no-pager
sudo tee /etc/nginx/sites-available/headscale >/dev/null <<'EOF'
server {
listen 80;
server_name headscale.example.com;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
EOF
sudo ln -sf /etc/nginx/sites-available/headscale /etc/nginx/sites-enabled/headscale
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d headscale.example.com --redirectStep 4: Create Users and Pre-Auth Keys
sudo headscale users create ops
sudo headscale preauthkeys create --user ops --reusable --expiration 24h
# Use the printed key on clients:
# sudo tailscale up --login-server https://headscale.example.com --authkey <KEY>Step 5: Apply Basic ACLs
sudo tee /etc/headscale/acl.json >/dev/null <<'EOF'
{
"acls": [
{ "action": "accept", "src": ["group:ops"], "dst": ["*:*"] },
{ "action": "accept", "src": ["group:devs"], "dst": ["tag:dev:*"] }
],
"groups": {
"group:ops": ["ops@"],
"group:devs": ["devs@"]
}
}
EOF
# Point policy.path in config.yaml to /etc/headscale/acl.json and restart headscale.Production Checklist
- Back up /var/lib/headscale and private keys together.
- Prefer short-lived pre-auth keys over reusable long-lived ones.
- Monitor control-server uptime; clients cannot enroll if Headscale is down.
- Keep Tailscale client versions within Headscale-supported ranges.
- Document break-glass SSH access that does not depend on the mesh.
"A self-hosted mesh is only safer than SaaS when ACLs, key rotation, and out-of-band recovery are part of the design."



