Headscale
Headscale is an open-source, self-hosted implementation of the Tailscale control plane. It lets you run your own private WireGuard-based mesh VPN using the Tailscale client.
Configuration
Key settings in config.yaml:
- domain - Where the Headscale control plane API is accessible
- vpnBaseDomain - Base domain for MagicDNS hostnames assigned to nodes (default:
vpn.{your-cloud-domain}) - storage - Persistent volume size (default:
2Gi)
First-Time Setup
-
Add and deploy the app:
wild app add headscale wild app deploy headscale -
Create a user (namespace) in Headscale:
kubectl exec -n headscale deploy/headscale -- headscale users create <username> -
Generate a pre-auth key for that user:
kubectl exec -n headscale deploy/headscale -- headscale preauthkeys create --user <user-id> --reusable --expiration 24hNote:
--userrequires the numeric user ID, not the username. Runheadscale users listto get the ID. -
On each device you want to join the VPN, install the Tailscale client and connect it to your Headscale instance:
tailscale up --login-server https://<your-headscale-domain> --authkey <preauthkey>
Registering Nodes via Browser Auth
If a Tailscale client connects without a pre-auth key, it shows an auth URL in its logs. Register that node with:
kubectl exec -n headscale deploy/headscale -- headscale nodes register --auth-id <hskey-authreq-XXXX> --user <username>
Note: --user here accepts the username string (not the numeric ID).
Useful Commands
# List users
kubectl exec -n headscale deploy/headscale -- headscale users list
# List connected nodes
kubectl exec -n headscale deploy/headscale -- headscale nodes list
# Create a pre-auth key (requires numeric user ID)
kubectl exec -n headscale deploy/headscale -- headscale preauthkeys create --user <id> --reusable
# Delete a node
kubectl exec -n headscale deploy/headscale -- headscale nodes delete --identifier <node-id>
Notes
- Headscale uses SQLite by default — no external database dependency
- Configuration is managed via a ConfigMap (
headscale-config) — changes require restarting the pod - In Headscale v0.29+, different commands accept different user identifier types:
preauthkeysandusers destroyrequire the numeric ID, whilenodes registeraccepts the username string