Why replace Tailscale's cloud coordination server
Tailscale delegates WireGuard orchestration to a third-party cloud service. Headscale reproduces this function as open-source on your VPS — no external dependency, topology data stays within your infrastructure.
Tailscale does not carry your network traffic: WireGuard packets travel directly from node to node, end-to-end encrypted. What Tailscale manages via its cloud is the control plane — public key exchange, peer discovery, IP address assignment in the 100.x.x.x subnet, MagicDNS resolution, and DERP relay distribution. Without this coordination server, nodes cannot find each other. Headscale is the open-source implementation of this server: it speaks exactly the same protocol as the Tailscale controller, which means your existing Tailscale clients work without modification — you only need to point them to a new login URL.
- Metadata privacy: none of your machine list, internal IP addresses, or node names transits through a third-party server. The control plane stays in your infrastructure.
- No imposed node limit: Headscale sets no ceiling on the number of registered machines — you are limited by your VPS resources, not a pricing grid.
- No user limit: the free Tailscale plan is restricted to 3 users; Headscale manages as many users as you create.
- BYOD without a Tailscale account: your collaborators connect via the pre-authentication key you generate, without needing to create an account on tailscale.com.
- Optional OIDC integration: Headscale supports authentication delegation to an OIDC provider (Keycloak, Authelia, Google Workspace) for teams that already have SSO.
- Customizable DERP servers: you can configure your own DERP relays on your VPS to minimize latency for connections that cannot be direct.
- Longevity: your mesh network does not depend on the commercial decisions of a third-party vendor — nor on potential outages of their infrastructure.
Prerequisites before you begin
Before installing Headscale, confirm that your environment meets the following requirements.
- A VPS running Debian 11/12 or Ubuntu 22.04/24.04, with at least 1 GB of RAM and root access — Headscale uses less than 50 MB in normal operation.
- UDP port 41641 accessible from the Internet on the server VPS: this is the WireGuard signaling port that Tailscale clients use to contact the coordinator.
- TCP port 443 or 8080 open for Headscale's HTTP/HTTPS API (clients connect here for registration and config retrieval).
- Tailscale client installed on each node you want to connect — the official Tailscale app works as-is with Headscale.
- Optional: a domain name pointing to your VPS if you want to enable HTTPS with a Let's Encrypt certificate and MagicDNS on a custom suffix.
Installing Headscale on a Debian/Ubuntu VPS
Headscale is installed via the official .deb package. The setup includes the binary, the systemd service, and the nginx configuration that serves the gRPC API and DERP interface.
Download and install the Headscale binary
Headscale distributes
.debpackages for amd64 and arm64. Fetch the latest release from GitHub and install it withdpkg:HEADSCALE_VERSION=$(curl -s https://api.github.com/repos/juanfont/headscale/releases/latest | grep tag_name | cut -d '"' -f4 | tr -d 'v') curl -Lo /tmp/headscale.deb \ https://github.com/juanfont/headscale/releases/latest/download/headscale_${HEADSCALE_VERSION}_linux_amd64.deb dpkg -i /tmp/headscale.debOn ARM64 (Raspberry Pi, Ampere servers), replace
linux_amd64withlinux_arm64. Verify the installation:headscale versionshould return the installed version number.Create the YAML configuration file
The package automatically creates the
headscalesystem user and the/etc/headscale/directory. Edit the main configuration file:nano /etc/headscale/config.yamlMinimal working configuration:
server_url: https://your-domain.com listen_addr: 0.0.0.0: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 ip_prefixes: - fd7a:115c:a1e0::/48 - 100.64.0.0/10 derp: server: enabled: false urls: - https://controlplane.tailscale.com/derpmap/default auto_update_enabled: true update_frequency: 24h disable_check_updates: false ephemeral_node_inactivity_timeout: 30m db_type: sqlite3 db_path: /var/lib/headscale/db.sqlite log: level: info dns_config: override_local_dns: true nameservers: - 1.1.1.1 domains: [] magic_dns: true base_domain: your-domain.comReplace
your-domain.comwith your actual domain. Theserver_urlvalue must match the URL your clients can reach from the Internet.Create data directories and generate keys
Create the data directory and assign it the correct permissions:
mkdir -p /var/lib/headscale chown headscale:headscale /var/lib/headscaleRun Headscale once to automatically generate the private keys:
headscale generate private-keyThe files
private.keyandnoise_private.keyare created in/var/lib/headscale/. Never share them and back them up — they sign the identity of your coordination server.Enable and start the systemd service
The
.debpackage installs the systemd unit automatically. Enable it at startup and launch the service:systemctl enable --now headscale systemctl status headscaleThe output should display
Active: active (running). Check logs in real time if the service fails to start:journalctl -u headscale -fCommon errors at first startup are a malformed
server_url(it must start withhttps://orhttp://) or an inaccessible/var/lib/headscaledirectory.Expose Headscale via an HTTPS reverse proxy (recommended)
To have clients connect over HTTPS, place Headscale behind nginx with a Let's Encrypt certificate. Install certbot and create the nginx configuration:
apt install -y nginx certbot python3-certbot-nginx certbot --nginx -d your-domain.comnginx configuration for Headscale (
/etc/nginx/sites-available/headscale):server { listen 443 ssl; server_name your-domain.com; ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem; location / { proxy_pass http://localhost: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; } }Enable the site and reload nginx:
ln -s /etc/nginx/sites-available/headscale /etc/nginx/sites-enabled/ nginx -t && systemctl reload nginx
Connecting client nodes to your own Headscale server
Once the Headscale server is running, each client machine runs the Tailscale daemon pointing to your Headscale URL instead of Tailscale Inc.'s servers.
Create a Headscale user
Headscale organizes nodes by users (the equivalent of "namespaces" in older versions). Create a first user from the server VPS:
headscale users create my-team headscale users listYou can create as many users as needed to separate environments (dev, prod, contractors).
Generate a pre-authentication key (preauthkey)
A preauthkey allows registering a node without manual intervention. Generate one for your user:
headscale preauthkeys create --user my-team --expiration 24hThe
--reusableoption creates a key usable multiple times — handy for registering several machines in automation. Without this option, the key is single-use. Copy the returned value.Connect a client node with the --login-server option
On each client machine (Linux, macOS, Windows, iOS, Android), the official Tailscale client is used. On first connection, specify your Headscale server URL with the
--login-serverflag:On Linux:
tailscale up --login-server https://your-domain.com --authkey YOUR_PREAUTHKEYOn macOS, run from Terminal:
tailscale up --login-server https://your-domain.com --authkey YOUR_PREAUTHKEYIf you don't pass a preauthkey, Tailscale displays an authentication URL. You must then validate the registration manually on the Headscale server side:
# On the server, list pending nodes headscale nodes register --user my-team --key <NODE_KEY_SHOWN_BY_TAILSCALE>Verify node registration
From the server VPS, list the registered nodes:
headscale nodes listEach registered node displays its name, its mesh IP (in the
100.64.x.xprefix), its user, and its status. Anonlinestatus confirms the node is active and was able to reach the coordinator.
Verification: can the nodes see each other?
After registering the nodes, verify end-to-end connectivity before routing real traffic through the mesh network.
Check network status from a client node
From any registered client node, run:
tailscale statusThe command lists all reachable peers with their mesh IP, name, and connection status (
active (direct)oractive (relay)). A peer showingdirectmeans the WireGuard connection is established without a relay — this is the nominal case when both nodes can reach each other directly.Test connectivity by ping
Identify the mesh IP of the target node from
tailscale status(format100.64.x.x) then ping it:ping 100.64.0.2A responding ping confirms the WireGuard tunnel is established between the two nodes and that the Headscale control plane is working correctly. If the ping fails but the node appears in
tailscale status, refer to the troubleshooting section.Test MagicDNS resolution
If you enabled
magic_dns: truein the Headscale configuration and defined abase_domain, each node is reachable by its short name. Test from a client node:ping node-name # or with the FQDN ping node-name.your-domain.comDNS resolution works via the mesh subnet — no public DNS records are needed for internal names. If resolution fails, verify that the Tailscale client is using the DNS resolver injected by the mesh:
tailscale status --selfshows the active DNS server.
Free Tailscale vs Headscale: factual comparison
Scroll the table
| Criterion | Free Tailscale | Headscale (self-managed) |
|---|---|---|
| Number of users | 3 users maximum | No limit imposed by the software |
| Number of nodes | 100 nodes maximum | No limit imposed by the software |
| Coordination server | Tailscale cloud (third-party infrastructure) | Your own VPS, under your control |
| Monthly cost | Free within plan limits | VPS cost only (from a few euros/month) |
| Client used | Official Tailscale client | Official Tailscale client (compatible, --login-server) |
| MagicDNS | Yes, on Tailscale-managed tailnet | Yes, on your custom domain |
| OIDC / SSO | Available on paid plans | Available for free via YAML configuration |
| Operational maintenance | None (managed service) | Package updates, key backup |
Use case: SSH access between dev and prod VPS without exposing port 22
The Headscale mesh network enables SSH access between servers without opening port 22 on the public interface — the connection travels via the internal Tailscale IP through the WireGuard tunnel.
One of the most common use cases for a mesh network is SSH access between machines without exposing port 22 to the Internet. With Headscale, your dev and prod VPS are registered on the same mesh network. Here is how to lock down SSH so it is only accessible via the mesh.
Identify mesh interfaces and addresses
On each VPS, the WireGuard interface created by Tailscale is named
tailscale0. Retrieve the local mesh IP:ip addr show tailscale0 # or tailscale ip -4Note the mesh IP of the prod VPS (e.g.
100.64.0.3) and the dev VPS (e.g.100.64.0.2).Configure UFW to restrict SSH to the mesh network
On the prod VPS, modify UFW rules to allow SSH only from the mesh subnet and block the rest:
# Allow SSH from the Headscale mesh subnet ufw allow in on tailscale0 to any port 22 proto tcp # Block SSH from the Internet ufw deny 22 ufw enableVerify the rules:
ufw status verbosePort 22 is no longer accessible from the Internet, but remains reachable from any node in the mesh network.
Connect via SSH over the mesh network
From the dev VPS (or your workstation registered on the same mesh network), connect to the prod VPS via its mesh IP or MagicDNS name:
ssh [email protected] # or via MagicDNS if enabled ssh [email protected]The connection travels entirely within the encrypted WireGuard tunnel. No port is publicly open on the prod VPS.
Hardening: enable Headscale ACLs (acls: in config.yaml) to precisely define which nodes can reach which other nodes and on which ports. Enable audit logs (log: level: info) and configure log forwarding to a centralized collector if you manage multiple nodes. Update Headscale regularly — compatibility with recent Tailscale client versions is maintained in recent server versions.
Troubleshooting: the most common errors
The most common issues when deploying Headscale involve network connectivity, TLS certificates, and WireGuard key synchronization.
UDP port 41641 closed. This is the most common cause of direct connection failure between nodes. Port 41641 must be open in UDP on the server VPS for nodes to establish their WireGuard tunnels. Check with ufw status and open it if needed: ufw allow 41641/udp. If the port stays closed, connections fall back to DERP relay mode — nodes work but with higher latency.
DERP issue: nodes visible but unreachable. Headscale uses the public Tailscale DERP map by default (controlplane.tailscale.com/derpmap/default). If your VPS is in an uncovered region or outbound HTTPS is filtered, DERP relays are unreachable. Check with tailscale netcheck from a client node — the command measures latency to each DERP region and shows which ones are unreachable.
Clock skew: authentication error. Headscale uses JWT tokens with short expiry. If the server VPS or a client node's clock drifts more than a few minutes, tokens are rejected with a token is expired or token is not yet valid error. Synchronize the clock: systemctl enable --now systemd-timesyncd on Debian/Ubuntu.
Node registers but appears offline. The node successfully contacted the server during registration but does not maintain a persistent connection. Verify the Tailscale service is running on the node: systemctl status tailscaled. Check logs: journalctl -u tailscaled -f. The problem is often a local firewall blocking outbound UDP connections.
Headscale: taking back control of your mesh network
Headscale turns a subscription to a proprietary cloud service into network infrastructure you operate, audit, and extend yourself — without changing the client-side experience.
Headscale moves the Tailscale coordination server from a third party's infrastructure to your own VPS. The result is a functionally identical WireGuard mesh network — the same clients, the same MagicDNS, the same NAT traversal behavior — but entirely under your control. For teams exceeding the 3 users or 100 nodes of the free Tailscale plan, Headscale is a direct alternative with no client-side tooling changes. For those who simply do not want a third-party server in their infrastructure loop, it is the only option consistent with that requirement. The installation described here takes under twenty minutes; maintenance amounts to updating a Debian package and backing up two key files.