Guides

Headscale on a VPS: your own Tailscale control server

Headscale is a self-hosted, open-source Tailscale control server. Install it on a VPS, put it behind a reverse proxy, create a user, and connect the Tailscale client.

Published ~7 min read

Headscale is an open-source, self-hosted implementation of the Tailscale control server — the part that issues keys, tells devices about each other and relays traffic when a direct connection cannot be made. Your devices still run the normal, official Tailscale client; you just point them at your own server instead of Tailscale’s. It needs a publicly reachable address to do that job, which a home connection behind CGNAT or a changing IP usually cannot offer reliably — a small VPS is the natural place to run it.

Debian · Ubuntu
sudo apt install -y wget
wget --output-document=headscale.deb \
  "https://github.com/juanfont/headscale/releases/download/v0.29.5/headscale_0.29.5_linux_amd64.deb"
sudo apt install ./headscale.deb

Replace 0.29.5 with the current release on headscale’s own releases page (0.29.5 was current when this was checked, 14 Oct 2026) and amd64 with your server’s architecture if it is arm64.

Why Tailscale needs a control server at all

Tailscale is a mesh VPN built on WireGuard: devices mostly talk to each other directly, peer to peer, once they know each other’s keys and current addresses. Something still has to hand out those keys, keep the list of who is allowed to talk to whom (the ACL), and act as a fallback relay (a DERP server) for the pairs of devices that cannot reach each other directly — both behind separate home NATs, say. That coordinating role is the control server. Tailscale’s own hosted one is free for personal use up to a device limit and is the default every Tailscale client talks to unless told otherwise. Headscale is a from-scratch, independent reimplementation of the same control protocol that you run yourself, so no third party ever sees your device list, your ACLs or (unless you also use its relay) your traffic metadata.

Minimal configuration

The package installs an example at the standard path; copy and edit it:

Debian · Ubuntu
sudo cp /usr/share/doc/headscale/examples/config-example.yaml /etc/headscale/config.yaml
sudo nano /etc/headscale/config.yaml

The fields that matter for a first run on a public server:

FieldExampleWhat it is
server_urlhttps://headscale.example.com:443The address clients connect to — put your real domain here, not the default 127.0.0.1:8080
listen_addr0.0.0.0:8080What headscale itself binds to; the config ships 127.0.0.1:8080, fine behind a reverse proxy, but 0.0.0.0 if headscale is to be reached directly
metrics_listen_addr127.0.0.1:9090Keep this on localhost — it is not meant to face the internet
database.typesqliteThe default and fine for personal use; database.sqlite.path is /var/lib/headscale/db.sqlite
noise.private_key_path/var/lib/headscale/noise_private.keyEncrypts client traffic to headscale; generated automatically if missing

server_url is the one field people get wrong first: it has to be the address clients will actually reach, including the port, and it has to match whatever certificate and reverse proxy you put in front of it — not the loopback address the example file ships with.

Running it, end to end

  1. 1Open the firewall
    Debian · Ubuntu
    sudo ufw allow 443/tcp
    sudo ufw allow 3478/udp   # headscale's own DERP relay, if you enable derp.server

    Setting up ufw covers the rest of a first firewall if this server does not have one configured yet.

  2. 2Put it behind a reverse proxy with a real certificate

    Point a domain at the server (see pointing a domain at a VPS) and proxy it with nginx and Let’s Encrypt, so server_url is a plain HTTPS address on port 443 rather than a bare port headscale listens on itself:

    /etc/nginx/sites-available/headscale
    server {
        listen 443 ssl;
        server_name headscale.example.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 $connection_upgrade;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
        }
    }

    The Upgrade/Connection headers matter: Tailscale clients hold a long-lived connection to the control server, not a plain request/response, so a proxy that does not forward the upgrade breaks registration in ways that are confusing to debug from the client side.

  3. 3Start headscale
    Debian · Ubuntu
    sudo systemctl enable --now headscale
    sudo systemctl status headscale
  4. 4Create a user
    on the server
    sudo headscale users create myself
  5. 5Connect a device

    On the device — any platform the official Tailscale client runs on — point it at your server instead of Tailscale’s default:

    Linux · macOS
    sudo tailscale up --login-server https://headscale.example.com

    The client prints a URL to open in a browser to authenticate. Approve the pending registration from the server:

    on the server
    sudo headscale nodes register --user myself --key <the key the client printed>

    For a device you cannot interact with directly — a second VPS, a container, a headless box — a pre-auth key skips the browser step entirely:

    on the server
    sudo headscale preauthkeys create --user myself
    on the other device
    sudo tailscale up --login-server https://headscale.example.com --authkey <the key printed above>

Using it for a VPN, not just device-to-device mesh

Headscale by itself only gives devices a private mesh to reach each other. To route all of a device’s internet traffic through the server as well — a plain VPN, rather than mesh-only — advertise the VPS itself as an exit node and have clients route through it, the same concept covered for a Tailscale-managed setup in running your own WireGuard VPN server, which this effectively layers a management plane on top of.

Windows and RDP clients

The official Tailscale Windows client works against a Headscale server the same way — tailscale up --login-server https://headscale.example.com from an elevated prompt or PowerShell. Once a Windows RDP server and your laptop are both nodes on the same Headscale network, RDP can connect over that private address without opening port 3389 to the internet at all, the same pattern covered in setting up a VPN on a Windows RDP server.

Why this needs a server you control

  • A fixed, reachable address. Clients need somewhere stable to register against; a home connection behind CGNAT often has no public IPv4 address of its own at all, which is exactly the NAT problem Tailscale is meant to solve for the devices behind it, not for the control server itself.
  • Uptime. New device registrations, ACL updates and (if you enable it) DERP relaying for peers that cannot connect directly all need the control server reachable; a laptop that sleeps is a bad place to run it.
  • Light load, so size is not the deciding factor. Headscale itself is not resource-hungry — a small standard KVM plan is enough for a personal or small-team node count; size for the DERP relay bandwidth if you enable it and expect peers that cannot connect directly often, not for headscale’s own CPU or RAM use.

Frequently asked

What is Headscale?

An open-source, self-hosted reimplementation of Tailscale's control server — the part that issues keys, tells devices about each other and can relay traffic when a direct connection fails. Your devices run the ordinary, official Tailscale client; you just point them at your own Headscale server instead of Tailscale's.

Does Headscale need a public server?

Yes, effectively. Clients need a stable, reachable address to register against and to reach for updates, which a home connection behind CGNAT or a changing residential IP usually cannot offer reliably. A small VPS with a fixed IP is the standard place to run it.

How do I connect a device to my Headscale server?

tailscale up --login-server https://your-headscale-domain, the normal Tailscale client command with --login-server pointed at your own server instead of Tailscale's default. Approve the pending registration on the server with headscale nodes register, or skip that interactive step entirely with a pre-auth key from headscale preauthkeys create.

Is Headscale affiliated with Tailscale?

No. It's an independent, community-maintained reimplementation of the same control protocol, not an official Tailscale product. It tracks the official client closely but can lag behind very recent client-side changes.

Related articles

All guides