Setting Up Headscale: Self-Hosted Tailscale Control Server
Nihesh Rachakonda••7 min readHeadscale is an open-source, self-hosted implementation of the Tailscale control server. It allows you to create your own private mesh VPN network without relying on Tailscale's cloud infrastructure. This guide walks through setting up Headscale and connecting Tailscale clients.
What is Headscale?
Headscale provides:
- A self-hosted alternative to Tailscale's coordination server
- Full control over your mesh network infrastructure
- WireGuard-based secure networking
- No dependency on third-party cloud services
- Support for all major Tailscale clients
If you want the benefits of Tailscale's mesh VPN but need to keep everything on your own infrastructure, Headscale is the solution.
Prerequisites
- Ubuntu/Debian server (or similar Linux distribution)
- Root or sudo access
- Domain name pointing to your server
- SSL certificate (we'll use Let's Encrypt)
- Open ports: 443 (HTTPS), 3478 (STUN)
Step 1: Install Headscale
Download and install the latest Headscale release:
HEADSCALE_VERSION="" # See above URL for latest version, e.g. "X.Y.Z" (NOTE: do not add the "v" prefix!) HEADSCALE_ARCH="" # Your system architecture, e.g. "amd64" wget --output-document=headscale.deb \ "https://github.com/juanfont/headscale/releases/download/v${HEADSCALE_VERSION}/headscale_${HEADSCALE_VERSION}_linux_${HEADSCALE_ARCH}.deb"
Verify the installation:
headscale version
Step 2: Configure Nginx with SSL
Install Nginx and Certbot:
sudo apt update sudo apt install nginx certbot python3-certbot-nginx -y
Create an Nginx config for Headscale:
sudo nano /etc/nginx/sites-available/headscale
Add the following configuration:
server { listen 80; server_name headscale.your-domain.com; location / { proxy_pass http://127.0.0.1:8080; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; 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_buffering off; proxy_read_timeout 86400; } }
Enable the site and obtain SSL certificate:
sudo ln -s /etc/nginx/sites-available/headscale /etc/nginx/sites-enabled/ sudo nginx -t sudo systemctl reload nginx sudo certbot --nginx -d headscale.your-domain.com
Certbot will automatically configure SSL and set up auto-renewal.
Step 3: Configure Headscale
Edit the configuration file:
sudo nano /etc/headscale/config.yaml
Here's a recommended configuration:
# Server configuration server_url: https://headscale.your-domain.com listen_addr: 127.0.0.1:8080 metrics_listen_addr: 127.0.0.1:9090 # gRPC settings grpc_listen_addr: 127.0.0.1:50443 grpc_allow_insecure: false # Database configuration database: type: sqlite sqlite: path: /var/lib/headscale/db.sqlite # TLS handled by Nginx reverse proxy tls_cert_path: "" tls_key_path: "" # Noise protocol (required for newer clients) noise: private_key_path: /var/lib/headscale/noise_private.key # IP prefixes for your network prefixes: v4: 100.64.0.0/10 v6: fd7a:115c:a1e0::/48 # DERP configuration derp: server: enabled: true region_id: 999 region_code: "headscale" region_name: "Headscale Embedded DERP" stun_listen_addr: "0.0.0.0:3478" private_key_path: /var/lib/headscale/derp_server_private.key automatically_add_embedded_derp_region: true ipv4: YOUR_PUBLIC_IP ipv6: "" urls: [] paths: [] auto_update_enabled: true update_frequency: 24h # Disable external DERP servers (optional, for full self-hosting) # derp: # urls: [] # DNS configuration dns: magic_dns: true base_domain: tailnet.your-domain.com nameservers: global: - 1.1.1.1 - 8.8.8.8 # Log settings log: format: text level: info # Policy (ACLs) - optional policy: mode: file path: "" # Unix socket for CLI unix_socket: /var/run/headscale/headscale.sock unix_socket_permission: "0770"
Replace the following values:
headscale.your-domain.comwith your actual domainYOUR_PUBLIC_IPwith your server's public IP addresstailnet.your-domain.comwith your preferred MagicDNS base domain
Step 4: Create Required Directories
sudo mkdir -p /var/lib/headscale sudo mkdir -p /var/run/headscale sudo chown -R headscale:headscale /var/lib/headscale sudo chown -R headscale:headscale /var/run/headscale
Step 5: Configure Firewall
Allow the necessary ports:
sudo ufw allow 'Nginx Full' sudo ufw allow 3478/udp sudo ufw reload
For cloud providers, ensure your security groups allow:
- TCP: 80, 443 (HTTP/HTTPS via Nginx)
- UDP: 3478 (STUN for DERP)
Step 6: Start Headscale
Enable and start the service:
sudo systemctl enable headscale sudo systemctl start headscale sudo systemctl status headscale
Check the logs for any errors:
sudo journalctl -u headscale -f
Step 7: Create a User
Headscale organizes devices by users. Create your first user:
sudo headscale users create myuser
List users:
sudo headscale users list
Step 8: Generate Pre-Authentication Keys
Pre-auth keys allow devices to join without manual approval:
# Create a reusable key (expires in 24 hours by default) sudo headscale preauthkeys create --user myuser --reusable --expiration 24h
For a one-time use key:
sudo headscale preauthkeys create --user myuser --expiration 1h
List existing keys:
sudo headscale preauthkeys list --user myuser
Step 9: Connect Tailscale Clients
Linux Client
Install Tailscale:
curl -fsSL https://tailscale.com/install.sh | sh
Connect to your Headscale server:
sudo tailscale up --login-server https://headscale.your-domain.com --authkey YOUR_PREAUTH_KEY
Or without a pre-auth key (requires manual approval):
sudo tailscale up --login-server https://headscale.your-domain.com
Then approve the node on the server:
sudo headscale nodes register --user myuser --key nodekey:XXXXX
macOS Client
Install Tailscale from the App Store or via Homebrew:
brew install tailscale
Connect using the CLI:
tailscale up --login-server https://headscale.your-domain.com --authkey YOUR_PREAUTH_KEY
Windows Client
- Download Tailscale from tailscale.com/download
- Open PowerShell as Administrator
- Run:
tailscale up --login-server https://headscale.your-domain.com --authkey YOUR_PREAUTH_KEY
iOS and Android
For mobile devices, you'll need to use the web-based registration flow:
- Install Tailscale from the App Store or Play Store
- On your server, generate a registration URL:
sudo headscale nodes register --user myuser --key nodekey:XXXXX
- Open the Tailscale app and use the custom login server option
Step 10: Managing Nodes
List all connected nodes:
sudo headscale nodes list
Delete a node:
sudo headscale nodes delete --identifier NODE_ID
Rename a node:
sudo headscale nodes rename --identifier NODE_ID "new-hostname"
Move a node to a different user:
sudo headscale nodes move --identifier NODE_ID --user newuser
Step 11: Enable Exit Nodes (Optional)
To use a node as an exit node for routing all traffic:
On the exit node:
sudo tailscale up --login-server https://headscale.your-domain.com --advertise-exit-node
Approve the exit node on the server:
sudo headscale routes enable --route "0.0.0.0/0" --identifier NODE_ID sudo headscale routes enable --route "::/0" --identifier NODE_ID
On client devices, use the exit node:
tailscale up --exit-node=EXIT_NODE_IP
Step 12: Configure Access Control (ACLs)
Create an ACL policy file:
sudo nano /etc/headscale/acl.json
Example policy allowing all users to communicate:
{ "acls": [ { "action": "accept", "src": ["*"], "dst": ["*:*"] } ], "tagOwners": {}, "hosts": {} }
Update the config to use the ACL file:
policy: mode: file path: /etc/headscale/acl.json
Restart Headscale:
sudo systemctl restart headscale
Troubleshooting
Connection Issues
Check if the server is reachable:
curl -I https://headscale.your-domain.com/health
Certificate Errors
Verify certificate validity:
openssl s_client -connect headscale.your-domain.com:443 -servername headscale.your-domain.com
Client Not Connecting
Check client logs:
# Linux sudo journalctl -u tailscaled -f # macOS log stream --predicate 'subsystem == "com.tailscale.ipn.macos"'
DERP Connectivity
Test if DERP is working:
# On the server, check if STUN port is listening sudo ss -tulpn | grep 3478
View Server Logs
sudo journalctl -u headscale -f --no-pager
Security Considerations
- Keep Headscale updated to the latest version
- Use strong, unique pre-auth keys
- Set appropriate expiration times for pre-auth keys
- Implement ACLs to restrict network access
- Regularly audit connected nodes
- Enable automatic certificate renewal
- Consider running Headscale behind a reverse proxy for additional security
Conclusion
Headscale provides a powerful self-hosted alternative to Tailscale's coordination server. With this setup, you have complete control over your mesh VPN infrastructure while still benefiting from Tailscale's excellent client software and WireGuard's security.
The combination of Headscale's simplicity and Tailscale's cross-platform clients makes it an excellent choice for homelab enthusiasts, small teams, or organizations that need to keep their network infrastructure fully self-hosted.
Remember to keep your server updated, monitor your logs, and regularly backup your configuration and database!

