Tech Digest

Networking

Headscale

Self-hosted reimplementation of the Tailscale control server for official Tailscale clients

NetworkingBSD-3-ClauseYoungarm64 buildsNative OIDC
Last reviewed Profile maintained against the project's own documentation
Operational load
3 / 5
Moderate
Idle memory
40 MB
typical use ~120 MB
Moving parts
1
containers in a normal deployment
Backup shape
SQLite backup
needs a dump or a stop

Headscale is an independent implementation of Tailscale's coordination server, the piece Tailscale Inc does not let you run yourself. You keep using the official Tailscale clients and point them at your own server, so key exchange and device metadata stay on your hardware.

Our verdict on Headscale#

Headscale is a clean-room reimplementation of Tailscale's coordination server, not a fork of the client. Every device keeps running the official Tailscale client and joins with tailscale up --login-server=https://hs.example.com. That is the whole proposition: MagicDNS, ACLs and NAT traversal without your device inventory living at Tailscale Inc. The README states the project is not associated with Tailscale Inc, though one maintainer is employed there and contributes during work hours. Take the version coupling seriously. v0.29.3 (July 2026) documents a minimum Tailscale client of v1.80.0, and the project is still pre-1.0 with real breaking changes between minors: you must upgrade one minor at a time (0.26 to 0.27.1 to 0.28.0 to 0.29) because migrations for databases older than 0.25.0 were removed in 0.28. The FAQ is refreshingly blunt: it targets self-hosters, expects hundreds of nodes, and tells you to use SQLite because PostgreSQL is in maintenance mode.

Who Headscale is for#

Choose it if

  • Someone already happy with Tailscale who wants the coordination server on their own box without changing anything on the clients
  • A homelab needing remote access to a handful of machines behind CGNAT, where WireGuard port forwarding is not an option
  • Anyone who wants OIDC-gated device enrollment tied to their existing Authentik or Keycloak
  • Small teams (tens of devices) comfortable driving user and node management from a CLI

Look elsewhere if

  • You want a web admin UI; there is no official one, only the CLI and a gRPC API, with third-party UIs of varying quality
  • You need the commercial feature set (Tailscale SSH audit, Funnel, app connectors) or a support contract
  • You are running hundreds of chatty nodes, since the maintainers explicitly deprioritize performance in favour of maintainability

What running it actually looks like#

One container or one binary, plus a reverse proxy. Headscale listens on 127.0.0.1:8080 by default and expects TLS terminated in front of it on 443, because clients require HTTPS and server_url must exactly match the name they dial. State lives in /var/lib/headscale: db.sqlite, noise_private.key and the DERP key. Configuration is one YAML file at /etc/headscale/config.yaml, and you should take the example from the git tag matching the version you run, not from main. base_domain for MagicDNS must be a different domain from the one in server_url or startup fails. There is no web UI: users, pre-auth keys, node approval and route enabling are all headscale subcommands, or gRPC on 50443 if you enable it. Metrics sit on 9090. Turning on the embedded DERP relay also means opening udp/3478 for STUN.

Resource profile#

Memory
40 MB idle, around 120 MB in ordinary use. Memory tracks node count and how often the network map changes; a tailnet of 20 mostly-idle devices sits near the low figure.
CPU and acceleration
Quiet until nodes churn. Every join, leave or route change recomputes and pushes a network map to every peer, which is why the maintainers cap expectations at hundreds of nodes rather than thousands.
Storage growth
A few megabytes. db.sqlite grows with nodes and routes, not with traffic, since data plane traffic never touches the server unless you run the embedded DERP relay.
Operational load
3 of 5, Moderate. Small and quiet in steady state, but pre-1.0 with breaking changes between minors, a documented no-skipping upgrade path, and a moving minimum client version. Upgrades are read-the-notes events, not tag bumps.

Figures describe a small single-household install and are the working assumptions behind our stack planner. Your numbers will differ with library size, user count and hardware. See methodology.

Data and backup surface#

Everything worth keeping is in /var/lib/headscale plus /etc/headscale/config.yaml. db.sqlite holds users, nodes, subnet routes, pre-auth keys and (if you store it there) the ACL policy; noise_private.key is the server's identity, and losing it forces every node to re-register from scratch. If you use a file-based ACL policy, back that file up too. Stop headscale or use sqlite3 db.sqlite ".backup out.db" before copying, since a live cp of an open WAL database can restore inconsistent. The whole set is a few megabytes for a home tailnet. None of it is regenerable: node keys live on the clients, but the mapping of node to user, tag and 100.64.x.x address exists only here.

Derived backup shape

SQLite backup. sqlite3 <db> ".backup out.db" while running, or stop the container and copy. A live cp of a WAL-mode SQLite file can restore corrupt.

Traps and surprises#

You cannot skip minor versions on upgrade

Headscale requires stepping through each stable minor in order, for example 0.26.0 to 0.27.1 to 0.28.0 to 0.29.x. 0.28 removed database migrations for anything older than 0.25.0, so a machine that has been sitting on an old release cannot jump straight to current: it starts, fails the migration and leaves you restoring the backup. Pull each intermediate image, start it once, confirm it comes up, then move on.

The minimum Tailscale client version keeps moving

v0.29.3 documents a floor of Tailscale client v1.80.0. Distro-packaged tailscaled on an older stable release is frequently below that, and the failure mode is a registration that hangs or a node that connects and then never gets a working network map. Check client versions across your fleet before upgrading the server, and prefer Tailscale's own repositories over the distro package on long-lived machines.

base_domain must differ from the server_url domain

MagicDNS names are issued under base_domain. If you set server_url: https://vpn.example.com and base_domain: example.com, headscale refuses to start rather than create a resolution loop where clients cannot reach the control server they are configured against. Use something like server_url: https://headscale.example.com with base_domain: ts.example.net, or a dedicated internal suffix.

0.29 redefined the ACL wildcard

In 0.29 a bare * in a policy resolves to the Tailscale CGNAT range 100.64.0.0/10 plus the IPv6 ULA range, not every address. Rules that previously granted a group access to LAN subnets or internet destinations through an exit node stop working after the upgrade, and the symptom is a healthy tailnet with newly broken subnet routing. Rewrite those rules with explicit CIDRs first. The same release changed hostname collision suffixes from random strings to numeric ones (laptop-1).

No support relationship with Tailscale Inc

The README says the project is not associated with Tailscale Inc. The control protocol is Tailscale's and it moves; headscale follows. In practice that means a client auto-update can land a version headscale has not caught up with, so pin or delay client updates on machines you cannot physically reach, and read headscale release notes before letting your fleet upgrade itself.

Specifications#

Category
Networking, DNS and remote access
Licence
BSD-3-Clause (Permissive)
Written in
Go
First release
2021
Maturity
Young
Datastore
SQLite (recommended); PostgreSQL supported but in maintenance mode
Default ports
8080, 9090, 3478
Ships as
docker, deb package, native binary
arm64 builds
Yes
Identity
Native OIDC. Built-in OIDC: point it at Authentik, Keycloak or Google and node registration goes through your IdP instead of pre-auth keys.
Replaces
Tailscale, ZeroTier Central, Twingate, Cloudflare WARP
Project site
headscale.net
Source
github.com/juanfont/headscale
Documentation
headscale.net/stable

Alternatives to Headscale#

Everything else in networking, dns and remote access, closest in operational weight first.

ToolOps loadIdle RAMLicence
Technitium DNS Server3, Moderate130 MBGPL-3.0
Traefik3, Moderate60 MBMIT
AdGuard Home2, Light60 MBGPL-3.0
Nginx Proxy Manager2, Light100 MBMIT
Pi-hole2, Light100 MBEUPL-1.2
wg-easy2, Light90 MBAGPL-3.0-only

Where Headscale comes up elsewhere#

Sources#

Published . Last reviewed . Found something out of date? Tell us and we will fix it and log the change.