NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
Go modules · #36 by repository stars
Last release 6 days ago
23 Sep 2026
Ships fairly regularly
a new release about every 3 weeks
Most releases are documented
notes for 38 of the last 60 stable releases
Nothing withdrawn
no release was ever pulled
6 years old
570 releases · first in 2020
One column per quarter.
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
…upstream has been closed and this might be a breaking change for some users. Please refer to the upstream documentation for more information on which…
Route internals have been rewritten, removing the dedicated route table in the database. This was done to simplify the codebase, which had grown unnecessarily complex after the routes were split into separate tables. The overhead of having to go via the database and keeping the state in sync made the code very hard to reason about and prone to errors. The majority of the route state is only relevant when headscale is running, and is now only kept in memory. As part of this, the CLI and API has been simplified to reflect the changes;
$ headscale nodes list-routes
ID | Hostname | Approved | Available | Serving (Primary)
1 | ts-head-ruqsg8 | | 0.0.0.0/0, ::/0 |
2 | ts-unstable-fq7ob4 | | 0.0.0.0/0, ::/0 |
$ headscale nodes approve-routes --identifier 1 --routes 0.0.0.0/0,::/0
Node updated
$ headscale nodes list-routes
ID | Hostname | Approved | Available | Serving (Primary)
1 | ts-head-ruqsg8 | 0.0.0.0/0, ::/0 | 0.0.0.0/0, ::/0 | 0.0.0.0/0, ::/0
2 | ts-unstable-fq7ob4 | | 0.0.0.0/0, ::/0 |
Note that if an exit route is approved (0.0.0.0/0 or ::/0), both IPv4 and IPv6 will be approved.
This release introduces a new policy implementation. The new policy is a complete rewrite, and it introduces some significant quality and consistency improvements. In principle, there are not really any new features, but some long standing bugs should have been resolved, or be easier to fix in the future. The new policy code passes all of our tests.
Changes
@ character.
@, like an email, this will just work.@, an
@ should be appended at the end. For example, if your user is john, it
must be written as john@ in the policy.<details>
<summary>Migration notes when the policy is stored in the database.</summary>
This section only applies if the policy is stored in the database and
Headscale 0.26 doesn't start due to a policy error
(failed to load ACL policy).
HEADSCALE_POLICY_V1=1
set. You can check that Headscale picked up the environment variable by
observing this message during startup: Using policy manager version: 1headscale policy get > policy.jsonpolicy.json and migrate to policy V2. Use the command
headscale policy check --file policy.json to check for policy errors.headscale policy set --file policy.jsonHEADSCALE_POLICY_V1.
Headscale should now print the message Using policy manager version: 2 and
startup successfully.</details>
SSH
The SSH policy has been reworked to be more consistent with the rest of the
policy. In addition, several inconsistencies between our implementation and
Tailscale's upstream has been closed and this might be a breaking change for
some users. Please refer to the
upstream documentation
for more information on which types are allowed in src, dst and users.
There is one large inconsistency left, we allow * as a destination as we
currently do not support autogroup:self, autogroup:member and
autogroup:tagged. The support for * will be removed when we have support for
the autogroups.
Current state
The new policy is passing all tests, both integration and unit tests. This does not mean it is perfect, but it is a good start. Corner cases that is currently working in v1 and not tested might be broken in v2 (and vice versa).
We do need help testing this code
server_url and base_domain to be equal #2544dns.nameservers.global if the configuration option dns.override_local_dns
is enabled or is not specified in the configuration file. This aligns with
behaviour of tailscale.com.
#2438headscale policy check command to check policy #2553oidc.map_legacy_users and oidc.strip_email_domain has been removed #2411/debug endpoint #2420
email_verified claim in its ID
tokens, Headscale will attempt to get it from the UserInfo endpoint.Nothing published for this version
Nothing published for this version
Fix issue where registration errors are sent correctly #2435
Nothing published for this version
Authentication flow has been rewritten #2374 This change should be transparent to users with the exception of some buxfixes that has been discovered a
oidc.map_legacy_users is now false by default #2350Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Fix migration error caused by nodes having invalid auth keys #2412
Fix issue where email and username being equal fails to match in Policy #2388
Fix migration issue with user table for PostgreSQL #2367
Nothing published for this version
Nothing published for this version
Nothing published for this version
The following issue _only_ affects Headscale installations which authenticate with OIDC.
The following issue only affects Headscale installations which authenticate with OIDC.
Headscale v0.23.0 and earlier identified OIDC users by the "username" part of
their email address (when strip_email_domain: true, the default) or whole
email address (when strip_email_domain: false).
Depending on how Headscale and your Identity Provider (IdP) were configured,
only using the email claim could allow a malicious user with an IdP account to
take over another Headscale user's account, even when
strip_email_domain: false.
This would also cause a user to lose access to their Headscale account if they changed their email address.
Headscale v0.24.0 now identifies OIDC users by the iss and sub claims.
These are guaranteed by the OIDC specification to be stable and unique,
even if a user changes email address. A well-designed IdP will typically set
sub to an opaque identifier like a UUID or numeric ID, which has no relation
to the user's name or email address.
Headscale v0.24.0 and later will also automatically update profile fields with OIDC data on login. This means that users can change those details in your IdP, and have it populate to Headscale automatically the next time they log in. However, this may affect the way you reference users in policies.
Headscale v0.23.0 and earlier never recorded the iss and sub fields, so all
legacy (existing) OIDC accounts need to be migrated to be properly secured.
Headscale v0.24.0 has an automatic migration feature, which is enabled by
default (map_legacy_users: true). This will be disabled by default in a
future version of Headscale – any unmigrated users will get new accounts.
The migration will mostly be done automatically, with one exception. If your
OIDC does not provide an email_verified claim, Headscale will ignore the
email. This means that either the administrator will have to mark the user
emails as verified, or ensure the users verify their emails. Any unverified
emails will be ignored, meaning that the users will get new accounts instead of
being migrated.
After this exception is ensured, make all users log into Headscale with their account, and Headscale will automatically update the account record. This will be transparent to the users.
When all users have logged in, you can disable the automatic migration by
setting map_legacy_users: false in your configuration file.
Please note that map_legacy_users will be set to false by default in v0.25.0
and the migration mechanism will be removed in v0.26.0.
<details>
<summary>What does automatic migration do?</summary>
When automatic migration is enabled (map_legacy_users: true), Headscale will
first match an OIDC account to a Headscale account by iss and sub, and then
fall back to matching OIDC users similarly to how Headscale v0.23.0 did:
strip_email_domain: true (the default): the Headscale username matches
the "username" part of their email address.strip_email_domain: false: the Headscale username matches the whole
email address.On migration, Headscale will change the account's username to their
preferred_username. This could break any ACLs or policies which are
configured to match by username.
Like with Headscale v0.23.0 and earlier, this migration only works for users who haven't changed their email address since their last Headscale login.
A successful automated migration should otherwise be transparent to users.
Once a Headscale account has been migrated, it will be unavailable to be
matched by the legacy process. An OIDC login with a matching username, but
non-matching iss and sub will instead get a new Headscale account.
Because of the way OIDC works, Headscale's automated migration process can only work when a user tries to log in after the update.
Legacy account migration should have no effect on new installations where all
users have a recorded sub and iss.
</details>
<details>
<summary>What happens when automatic migration is disabled?</summary>
When automatic migration is disabled (map_legacy_users: false), Headscale will
only try to match an OIDC account to a Headscale account by iss and sub.
If there is no match, it will get a new Headscale account – even if there was a legacy account which could have matched and migrated.
We recommend new Headscale users explicitly disable automatic migration – but it
should otherwise have no effect if every account has a recorded iss and sub.
When automatic migration is disabled, the strip_email_domain setting will have
no effect.
</details>
Special thanks to @micolous for reviewing, proposing and working with us on these changes.
Headscale now uses the standard OIDC claims to populate and update user information every time they log in:
| Headscale profile field | OIDC claim | Notes / examples |
|---|---|---|
| email address | email |
Only used when "email_verified": true |
| display name | name |
eg: Sam Smith |
| username | preferred_username |
Varies depending on IdP and configuration, eg: ssmith, ssmith@idp.example.com, \\example.com\ssmith |
| profile picture | picture |
URL to a profile picture or avatar |
These should show up nicely in the Tailscale client.
This will also affect the way you reference users in policies.
dns.use_username_in_magic_dns configuration option #2020,
#2279
GET /api/v1/user/{name} and GetUser have been removed in favour of
ListUsers with an ID parameterRenameUser and DeleteUser now require an ID instead of a name.stable-debug container tag #2232server_url and base_domain check. It was overly strict in some
cases. #2248--identifier in addition to --name,
usage of --identifier is recommended
#2261dns.extra_records_path configuration option #2262Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
…a lot of improvements. This does come with some breaking changes,
This release was intended to be mainly a code reorganisation and refactoring, significantly improving the maintainability of the codebase. This should allow us to improve further and make it easier for the maintainers to keep on top of the project. However, as you all have noticed, it turned out to become a much larger, much longer release cycle than anticipated. It has ended up to be a release with a lot of rewrites and changes to the code base and functionality of Headscale, cleaning up a lot of technical debt and introducing a lot of improvements. This does come with some breaking changes,
Please remember to always back up your database between versions
Code has been organised into modules, reducing use of global variables/objects, isolating concerns and “putting the right things in the logical place”.
The new policy and mapper package, containing the ACL/Policy logic and the logic for creating the data served to clients (the network “map”) has been rewritten and improved. This change has allowed us to finish SSH support and add additional tests throughout the code to ensure correctness.
The “poller”, or streaming logic has been rewritten and instead of keeping track of the latest updates, checking at a fixed interval, it now uses go channels, implemented in our new notifier package and it allows us to send updates to connected clients immediately. This should both improve performance and potential latency before a client picks up an update.
Headscale now supports sending “delta” updates, thanks to the new mapper and poller logic, allowing us to only inform nodes about new nodes, changed nodes and removed nodes. Previously we sent the entire state of the network every time an update was due.
While we have a pretty good test harness for validating our changes, the changes came down to 284 changed files with 32,316 additions and 24,245 deletions and bugs are expected. We need help testing this release. In addition, while we think the performance should in general be better, there might be regressions in parts of the platform, particularly where we prioritised correctness over speed.
There are also several bugfixes that has been encountered and fixed as part of implementing these changes, particularly after improving the test harness as part of adopting #1460.
derp.server.private_key_pathheadscale serve to serve/var/lib/headscale and /var/run/headscale is no longer created
automatically, see container docsip_prefixes option is now prefixes.v4 and prefixes.v6prefixes.allocation can be set to assign IPs at sequential or random.
#1869use_username_in_magic_dns can be used to turn this behaviour on again, but
note that this option will be removed when tags are fixed.
autogroup:internet to Policy #1917serve) only requires minimal configuration, no more
errors or warnings from unset settings
#2109Your coding agent can read these notes before it upgrades. Set up the MCP server →