Migrating from Tailscale to Headscale


Tailscale is a bit of UI and management on top of Wireguard.

The big thing Tailscale does over a more traditional VPN setup is that typically it runs point-to-point, rather than in a hub-and-spoke topology.

This means that (for example) traffic between my laptop on coffee shop wifi and my phone on 4G will go via the Internet as if both ends were public. Devices that are on the same LAN will still encrypt traffic, but will connect directly with minimal additional latency.

Tailscale can also be used to create network ACLs, based on users and devices, though I didn't make great use of this in my setup.

Why migrate away?


Whilst I do like the product, it was a bit of an odd outlier of being a SaaS product, rather than something running on a machine at my house.

Especially if I'm going to spend time setting up things like ACLs, I'd like that to be in something that can't unexpectedly change in annoying ways.

Other options?


In the past, I've run more traditional setups using OpenVPN or various flavours of IPSec. I've not used Wireguard, but from what I've heard it would not be horrible to get running. I don't already have a router that supports it, so it would still be running on a VM.

Since I need a VM anyway, and I like the point-to-point thing, I thought I'd have a go with Headscale - and open source re-implementation of the Tailscale control plane.

Installing Headscale


Requirements


The project stresses that they only support the following:


The Internet services required are a HTTPS server, used for coordinating the clients, and a service to forward traffic if nodes are unable to reach each other directly. There are reports of people getting the HTTP service to work behind a reverse proxy, but this configuration is strongly discouraged by the developers.

Installing


The Headscale project targets Debian and derivatives, so I started by installing Debian 12 on a new VM.

install guide, using pre-built packages. (https://headscale.net)


The short version is:

cd /tmp
wget https://github.com/juanfont/headscale/releases/download/v0.23.0/headscale_0.23.0_linux_amd64.deb
apt install ./headscale_0.23.0_linux_amd64.deb


Then, some customisation of the default configuration is required. The documentation points to a sample in the repo, but this was already in /etc/ as the default configuration file in the package.

The key bits I changed:

listen_addr: 0.0.0.0:443 # Listen on a non-localhost address
acme_email: "me@example.com" # This and the line below are required for automatic cert fetching
tls_letsencrypt_hostname: "headscale.example.com"
# nodes automatically get NODE_NAME.vpn.example.com set up. This happens automatically, and does not require changing DNS settings.
base_domain: vpn.example.com


I also disabled the use of Tailscale's DERP servers, since Headscale provides its own. If I have trouble, I can always enable this.

It's worth having a read through the rest of the configuration - it's well commented, and there may be other options you want to change.

Once Headscale is configured, start it:

systemctl enable --now headscale


If things are configured correctly, you should now get a blank 404 page if you open https://headscale.example.com.

Adding users


Users are used for ACLs.

headscale users create USERNAME


Adding hosts


The model used by Headscale (and Tailscale) is that users have accounts and groups, and these are used for access control. Nodes are tagged by the user who creates them, then access to a given node is allowed or denied depending on the user being in a group with access to that tag.

ACL reference (https://headscale.net)


For testing, you can add untagged hosts and ignore ACLs.

The process for adding a host differs a little between operating systems.

Linux


Install the official Tailscale client, then run:

tailscale up --login-server https://headscale.example.com


You will be given a URL to log in and connect the node, however Headscale doesn't come with a web UI. The URL will give you a command that must be run on the Headscale server, something like this:

headscale nodes register --user USERNAME --key mkey:ABCD....


Once that has been run successfully, you should have a new address on your node in the VPN range.

iOS


NOTE: The official docs are outdated - it is now easier to use a 3rd party server with the app.

Install the Tailscale iOS app from the App Store.

Open the app and open the Accounts screen. Do not click "Add account..." but use the three dots in the top right to choose "Use a custom coordination server".

You will then be prompted to enter your server URL.

At this point, the iOS app was a bit tricky for me - it did send me to the page with the token, but I had no easy way to get that to the Headscale server. I looked in the log file and copied the issued ID from there - but alternatively you could email it to yourself.

macOS


Install the Tailscale Mac app from the App Store or the website.

If open https://headscale.example.com/apple, there are instructions. The TL;DR is: after the app is installed, alt-click on the tray icon then go to Debug>Custom Login Server.

Testing


Once you have two nodes set up, you should be able to ping between them. During testing, I did a bit of swapping between mobile data an wifi on my phone to test that was working correctly.