Deployment guide

Headscale on VPS: replace the Tailscale coordination server

Deploy on a VPS Cloud →

Tutorial

Headscale on VPS: replace the Tailscale coordination server

Deployment13 min read15 steps

Tailscale radically simplifies WireGuard networking — until you hit the limits of the free plan, or realize that every connection between your machines passes through a coordination server you don't control. Headscale is the open-source implementation of that coordination server. You install it on your own VPS, point your existing Tailscale clients to it, and your mesh network stays entirely under your control. The installation takes less than twenty minutes. Maintenance amounts to package updates. This article guides you step by step, from installing the binary to verifying that two nodes can reach each other via MagicDNS.

Contents· Why replace Tailscale's cloud coordination server1/9
  1. 01Why replace Tailscale's cloud coordination server
  2. 02Prerequisites before you begin
  3. 03Installing Headscale on a Debian/Ubuntu VPS
  4. 04Connecting client nodes to your own Headscale server
  5. 05Verification: can the nodes see each other?
  6. 06Free Tailscale vs Headscale: factual comparison
  7. 07Use case: SSH access between dev and prod VPS without exposing port 22
  8. 08Troubleshooting: the most common errors
  9. 09Headscale: taking back control of your mesh network

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.

  1. Download and install the Headscale binary

    Headscale distributes .deb packages for amd64 and arm64. Fetch the latest release from GitHub and install it with dpkg:

    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.deb

    On ARM64 (Raspberry Pi, Ampere servers), replace linux_amd64 with linux_arm64. Verify the installation: headscale version should return the installed version number.

  2. Create the YAML configuration file

    The package automatically creates the headscale system user and the /etc/headscale/ directory. Edit the main configuration file:

    nano /etc/headscale/config.yaml

    Minimal 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.com

    Replace your-domain.com with your actual domain. The server_url value must match the URL your clients can reach from the Internet.

  3. 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/headscale

    Run Headscale once to automatically generate the private keys:

    headscale generate private-key

    The files private.key and noise_private.key are created in /var/lib/headscale/. Never share them and back them up — they sign the identity of your coordination server.

  4. Enable and start the systemd service

    The .deb package installs the systemd unit automatically. Enable it at startup and launch the service:

    systemctl enable --now headscale
    systemctl status headscale

    The output should display Active: active (running). Check logs in real time if the service fails to start:

    journalctl -u headscale -f

    Common errors at first startup are a malformed server_url (it must start with https:// or http://) or an inaccessible /var/lib/headscale directory.

  5. 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.com

    nginx 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.

  1. 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 list

    You can create as many users as needed to separate environments (dev, prod, contractors).

  2. 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 24h

    The --reusable option 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.

  3. 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-server flag:

    On Linux:

    tailscale up --login-server https://your-domain.com --authkey YOUR_PREAUTHKEY

    On macOS, run from Terminal:

    tailscale up --login-server https://your-domain.com --authkey YOUR_PREAUTHKEY

    If 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>
  4. Verify node registration

    From the server VPS, list the registered nodes:

    headscale nodes list

    Each registered node displays its name, its mesh IP (in the 100.64.x.x prefix), its user, and its status. An online status 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.

  1. Check network status from a client node

    From any registered client node, run:

    tailscale status

    The command lists all reachable peers with their mesh IP, name, and connection status (active (direct) or active (relay)). A peer showing direct means the WireGuard connection is established without a relay — this is the nominal case when both nodes can reach each other directly.

  2. Test connectivity by ping

    Identify the mesh IP of the target node from tailscale status (format 100.64.x.x) then ping it:

    ping 100.64.0.2

    A 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.

  3. Test MagicDNS resolution

    If you enabled magic_dns: true in the Headscale configuration and defined a base_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.com

    DNS 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 --self shows the active DNS server.

Free Tailscale vs Headscale: factual comparison

Scroll the table

CriterionFree TailscaleHeadscale (self-managed)
Number of users3 users maximumNo limit imposed by the software
Number of nodes100 nodes maximumNo limit imposed by the software
Coordination serverTailscale cloud (third-party infrastructure)Your own VPS, under your control
Monthly costFree within plan limitsVPS cost only (from a few euros/month)
Client usedOfficial Tailscale clientOfficial Tailscale client (compatible, --login-server)
MagicDNSYes, on Tailscale-managed tailnetYes, on your custom domain
OIDC / SSOAvailable on paid plansAvailable for free via YAML configuration
Operational maintenanceNone (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.

  1. 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 -4

    Note the mesh IP of the prod VPS (e.g. 100.64.0.3) and the dev VPS (e.g. 100.64.0.2).

  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 enable

    Verify the rules:

    ufw status verbose

    Port 22 is no longer accessible from the Internet, but remains reachable from any node in the mesh network.

  3. 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.

A VPS ready for Headscale in a few clicks

Our Debian and Ubuntu VPS come preconfigured with root SSH access, a dedicated IP, and UDP port 41641 open. Deploy Headscale without friction and keep control of your network infrastructure.

Need help?

Browse our help center and FAQ, or reach our team — callback, WhatsApp or email. Support in French, English and Arabic.

Message us on WhatsAppopens in a new tab