Taking Control of Your Own Network
Tailscale has made mesh VPN networking almost trivially easy – you install a client, log in, and your devices can talk to each other across the internet as if they were on the same local network. The catch is that the coordination layer, the control server that manages keys, routes, and device authentication, runs on Tailscale’s own infrastructure. For most people that’s fine. For anyone running a home lab, a small business, or simply committed to keeping their infrastructure fully self-contained, handing that control to a third party is a compromise worth avoiding.
Headscale is an open-source reimplementation of the Tailscale control server. It speaks the same protocol that official Tailscale clients use, which means you can run the coordination layer yourself on your own hardware while still using the polished, well-maintained Tailscale client apps on every device. No forked clients, no proprietary agents – just your server, your keys, and your network. This guide walks through setting up Headscale on a Linux server from scratch.

What You Need Before Starting
The setup assumes you have a Linux server with a public IP address or a domain name pointing at it. Headscale needs to be reachable by all the devices that will join your network, so a machine tucked behind a home NAT without port forwarding will not work as the control server. A small VPS from any major provider works well for this role – the resource requirements are minimal. You will also need a domain or subdomain to point at the server, since Headscale serves over HTTPS and certificate generation expects a real hostname.
Install Docker and Docker Compose if they are not already on the server. While Headscale can be installed as a binary directly on the host, running it in Docker keeps the configuration portable and makes upgrades straightforward. The official Headscale image is published on Docker Hub under headscale/headscale. You will also want nginx or Caddy available as a reverse proxy to handle TLS termination – Headscale itself does not manage certificates out of the box.
Installation and Initial Configuration
Start by creating a working directory and pulling the default Headscale configuration file. The project’s GitHub repository provides a sample config at config-example.yaml – copy it to your working directory as config.yaml. The two settings to change immediately are server_url, which should be set to your public domain with HTTPS, and listen_addr, which should be 0.0.0.0:8080 when running behind a reverse proxy. Set metrics_listen_addr to a localhost-only port if you plan to scrape metrics later.
The configuration file also controls the IP range that Headscale assigns to devices. The default is 100.64.0.0/10, which matches Tailscale’s standard address space. Leave this alone unless you have a specific reason to change it, since some Tailscale client behavior is tied to that range. Configure your DNS settings in the same file – Headscale supports MagicDNS-style name resolution for nodes, and setting base_domain to something like mesh.yourdomain.com will let devices resolve each other by name automatically. Database backend defaults to SQLite, which is more than adequate for personal or small-team use.

With configuration in place, create your docker-compose.yml. Mount the config file and a data directory as volumes, expose port 8080 to the reverse proxy, and set the container to restart unless stopped. A minimal Compose file looks like this:
- image: headscale/headscale:latest
- volumes: ./config:/etc/headscale, ./data:/var/lib/headscale
- ports: 127.0.0.1:8080:8080
- command: headscale serve
- restart: unless-stopped
Run docker compose up -d to start the container. Check logs with docker compose logs -f – a clean start shows Headscale binding to its address and waiting for connections. If it fails, the most common cause is a permissions error on the mounted data directory. Fix it with chown -R 1000:1000 ./data and restart.
Reverse Proxy, Certificates, and Connecting Devices
Caddy is the easiest choice for handling TLS here because it provisions and renews Let’s Encrypt certificates automatically with no extra configuration. A minimal Caddyfile block proxies your domain to Headscale’s local port and handles everything else:
- yourdomain.com {
- reverse_proxy localhost:8080
- }
If you prefer nginx, the configuration is slightly more involved – you need a separate Certbot setup for certificate provisioning and a server block that proxies HTTP/2 to the Headscale backend. Either way, the goal is the same: HTTPS traffic hits your domain, terminates at the proxy, and forwards to Headscale over plain HTTP on localhost. Test the setup by visiting https://yourdomain.com/health – a working Headscale instance returns a simple OK response at that endpoint.
Creating users and registering devices is done through the Headscale CLI inside the container. Create a user first – in Headscale terminology, users replace Tailscale’s concept of “tailnets” and group devices together. Run docker exec headscale headscale users create myuser. On the device you want to add to the network, install the standard Tailscale client and run tailscale up –login-server https://yourdomain.com. This generates a registration URL. Back on the server, approve the device with docker exec headscale headscale nodes register –user myuser –key <nodekey>, where the node key comes from the registration URL output. The device joins the network immediately.
Repeat the process for every device. Each one authenticates against your Headscale instance rather than Tailscale’s servers, and the keys never leave your infrastructure. You can verify connected nodes at any time with docker exec headscale headscale nodes list, which shows each device’s assigned IP, its status, and when it last checked in. Pre-authentication keys – generated with headscale preauthkeys create –user myuser – let you register devices non-interactively, which is useful for servers or containers that need to join the network as part of an automated deployment.

Headscale also supports exit nodes and subnet routing, both core Tailscale features. To use a node as an exit node, enable it on that device with tailscale up –advertise-exit-node, then approve the route on the server with headscale routes enable -r <routeID>. Subnet routes work identically – advertise the subnet from a device with access to it and approve the route in Headscale. If you are building out a broader home server setup, pairing Headscale with a service like Uptime Kuma lets you monitor whether the control server itself stays reachable and alert you if it goes down. The one operational reality worth keeping in mind: if your Headscale server goes offline, existing connections between devices continue working using cached keys, but no new devices can join and key rotation stops – so the server’s uptime actually matters more than it might appear.
Frequently Asked Questions
Can I use the official Tailscale app with Headscale?
Yes. Headscale implements the same control protocol Tailscale clients use, so official apps on all platforms work without modification.
Does Headscale support exit nodes and subnet routing?
Yes, both features are supported. You advertise routes from Tailscale clients and approve them through the Headscale CLI.





