Headscale: Self-Hosted Tailscale Control Server on Linux
Networking

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

bash
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/headscale

Step 2: Configure Headscale

bash
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.yaml

Step 3: systemd and Nginx TLS

bash
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 --redirect

Step 4: Create Users and Pre-Auth Keys

bash
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

bash
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."

Technical Author

Timothy M.

Timothy M.

Support and server administration. Writes the work on the host: packages, services, and what to check when a machine misbehaves.