Releases: juanfont/headscale
v0.28.0-beta.1
Minimum supported Tailscale client version: v1.74.0
Tags as identity
Tags are now implemented following the Tailscale model where tags and user ownership are mutually exclusive. Devices can be either
user-owned (authenticated via web/OIDC) or tagged (authenticated via tagged PreAuthKeys). Tagged devices receive their identity from
tags rather than users, making them suitable for servers and infrastructure. Applying a tag to a device removes user-based
ownership. See the Tailscale tags documentation for details on how tags work.
User-owned nodes can now request tags during registration using --advertise-tags. Tags are validated against the tagOwners policy
and applied at registration time. Tags can be managed via the CLI or API after registration.
Smarter map updates
The map update system has been rewritten to send smaller, partial updates instead of full network maps whenever possible. This reduces bandwidth usage and improves performance, especially for large networks. The system now properly tracks peer
changes and can send removal notifications when nodes are removed due to policy changes.
#2856 #2961
Pre-authentication key security improvements
Pre-authentication keys now use bcrypt hashing for improved security #2853. Keys
are stored as a prefix and bcrypt hash instead of plaintext. The full key is only displayed once at creation time. When listing keys,
only the prefix is shown (e.g., hskey-auth-{prefix}-***). All new keys use the format hskey-auth-{prefix}-{secret}. Legacy plaintext keys in the format {secret} will continue to work for backwards compatibility.
Web registration templates redesign
The OIDC callback and device registration web pages have been updated to use the Material for MkDocs design system from the official
documentation. The templates now use consistent typography, spacing, and colours across all registration flows.
Database migration support removed for pre-0.25.0 databases
Headscale no longer supports direct upgrades from databases created before version 0.25.0. Users on older versions must upgrade
sequentially through each stable release, selecting the latest patch version available for each minor release.
BREAKING
- Tags: The gRPC
SetTagsendpoint now allows converting user-owned nodes to tagged nodes by setting tags. Once a node is tagged, it cannot be converted back to a user-owned node. #2885 - Tags: Tags are now resolved from the node's stored Tags field only #2931
--advertise-tagsis processed during registration, not on every policy evaluation- PreAuthKey tagged devices ignore
--advertise-tagsfrom clients - User-owned nodes can use
--advertise-tagsif authorized bytagOwnerspolicy - Tags can be managed via CLI (
headscale nodes tag) or the SetTags API after registration
- Database migration support removed for pre-0.25.0 databases #2883
- If you are running a version older than 0.25.0, you must upgrade to 0.25.1 first, then upgrade to this release
- See the upgrade path documentation for detailed guidance
- In version 0.29, all migrations before 0.28.0 will also be removed
- Remove ability to move nodes between users #2922
- The
headscale nodes moveCLI command has been removed - The
MoveNodeAPI endpoint has been removed - Nodes are permanently associated with their user at registration time
- The
Changes
- Smarter change notifications send partial map updates and node removals instead of full maps #2961
- Send lightweight endpoint and DERP region updates instead of full maps #2856
- Add
oidc.email_verified_requiredconfig option to control email verification requirement #2860- When
true(default), only verified emails can authenticate via OIDC withallowed_domainsorallowed_users - When
false, unverified emails are allowed for OIDC authentication
- When
- Add NixOS module in repository for faster iteration #2857
- Add favicon to webpages #2858
- Redesign OIDC callback and registration web templates #2832
- Reclaim IPs from the IP allocator when nodes are deleted #2831
- Add bcrypt hashing for pre-authentication keys #2853
- Add prefix to API keys (
hskey-api-{prefix}-{secret}) #2853 - Add prefix to registration keys for web authentication tracking (
hskey-reg-{random}) #2853 - Tags can now be tagOwner of other tags #2930
- Add
taildrop.enabledconfiguration option to enable/disable Taildrop file sharing #2955 - Allow disabling the metrics server by setting empty
metrics_listen_addr#2914 - Log ACME/autocert errors for easier debugging #2933
- Improve CLI list output formatting #2951
- Use Debian 13 distroless base images for containers #2944
- Fix ACL policy not applied to new OIDC nodes until client restart #2890
- Fix autogroup:self preventing visibility of nodes matched by other ACL rules #2882
- Fix nodes being rejected after pre-authentication key expiration #2917
- Fix list-routes command respecting identifier filter with JSON output #2927
Upgrade
Please follow the steps outlined in the upgrade guide to update your existing Headscale installation.
It's best to update from one stable version to the next (e.g., 0.24.0 → 0.25.1 → 0.26.1) in case you are multiple releases behind. You should always pick the latest available patch release.
Be sure to check the changelog above for version-specific upgrade instructions and breaking changes.
Backup Your Database
Always backup your database before upgrading. Here's how to backup a SQLite database:
# Stop headscale
systemctl stop headscale
# Backup sqlite database
cp /var/lib/headscale/db.sqlite /var/lib/headscale/db.sqlite.backup
# Backup sqlite WAL/SHM files (if they exist)
cp /var/lib/headscale/db.sqlite-wal /var/lib/headscale/db.sqlite-wal.backup
cp /var/lib/headscale/db.sqlite-shm /var/lib/headscale/db.sqlite-shm.backup
# Start headscale (migration will run automatically)
systemctl start headscaleChangelog
- c460034 .github/workflows: prebuilt integration test artifacts (#2954)
- 2c3c943 .github/workflows: split long TestAutoApproveMultiNetwork into multiple jobs
- 5655ef8 AGENTS: golangci-lint from main, no "full matrix"
- 249630b Add API documentation
- 14af9b3 Add docs to manage headscale from another local user
- 21af106 Containers should be read-only
- a288f04 Dockerfile: align packages
- 9c33cbf Exclude docs/ only for prettier pre-commit hook
- 665cc44 Explicitly drop
apt-get cleanand usedist-clean - c5133ee Fix trailing whitespace
- 5c6cd62 Legacy preauthkeys must be used as-is
- e86d063 Mention /health instead of /windows
- f00c412 Move static doc assets into docs/assets
- 2010805 Provide Headscale's favicon at its expected place
- 72d5fd0 Remove duplicated documentation and link to getting started instead
- e0c9e18 Update OIDC documentation for allowed groups filter
- 9b327f6 Update pre-commit-hooks
- 6359511 Use debian13 distroless images
- bba91a8 Use lists for integration docs
- 218a8db add favicon to webpages (#2858)
- 6d24afb add pre-commit hooks, move claude to agents. (#2877)
- 0e16730 all: remove deadcode (#2952)
- 56bec66 app: only wire up debug server if set
- 3cf2d71 auth: ensure machines are allowed in when pak change (#2917)
- f3767dd batcher: ensure removal from batcher
- 616c0e8 batcher: fix closed panic
- 7fb0f9a batcher: send endpoint and derp only updates. (#2856)
- e875361...
v0.27.2-rc.1
Changes
- Fix ACL policy not applied to new OIDC nodes until client restart
#2890 - Fix autogroup:self preventing visibility of nodes matched by other ACL rules
#2882 - Fix nodes being rejected after pre-authentication key expiration
#2917
Upgrade
Please follow the steps outlined in the upgrade guide to update your existing Headscale installation.
It's best to update from one stable version to the next (e.g., 0.24.0 → 0.25.1 → 0.26.1) in case you are multiple releases behind. You should always pick the latest available patch release.
Be sure to check the changelog above for version-specific upgrade instructions and breaking changes.
Backup Your Database
Always backup your database before upgrading. Here's how to backup a SQLite database:
# Stop headscale
systemctl stop headscale
# Backup sqlite database
cp /var/lib/headscale/db.sqlite /var/lib/headscale/db.sqlite.backup
# Backup sqlite WAL/SHM files (if they exist)
cp /var/lib/headscale/db.sqlite-wal /var/lib/headscale/db.sqlite-wal.backup
cp /var/lib/headscale/db.sqlite-shm /var/lib/headscale/db.sqlite-shm.backup
# Start headscale (migration will run automatically)
systemctl start headscaleChangelog
v0.27.1
Minimum supported Tailscale client version: v1.64.0
Changes
- Expire nodes with a custom timestamp
#2828 - Fix issue where node expiry was reset when tailscaled restarts
#2875 - Fix OIDC authentication when multiple login URLs are opened
#2861 - Fix node re-registration failing with expired auth keys
#2859 - Remove old unused database tables and indices
#2844
#2872 - Ignore litestream tables during database validation
#2843 - Fix exit node visibility to respect ACL rules
#2855 - Fix SSH policy becoming empty when unknown user is referenced
#2874 - Fix policy validation when using bypass-grpc mode
#2854 - Fix autogroup:self interaction with other ACL rules
#2842 - Fix flaky DERP map shuffle test
#2848 - Use current stable base images for Debian and Alpine containers
#2827
Upgrade
Please follow the steps outlined in the upgrade guide to update your existing Headscale installation.
It's best to update from one stable version to the next (e.g., 0.24.0 → 0.25.1 → 0.26.1) in case you are multiple releases behind. You should always pick the latest available patch release.
Be sure to check the changelog above for version-specific upgrade instructions and breaking changes.
Backup Your Database
Always backup your database before upgrading. Here's how to backup a SQLite database:
# Stop headscale
systemctl stop headscale
# Backup sqlite database
cp /var/lib/headscale/db.sqlite /var/lib/headscale/db.sqlite.backup
# Backup sqlite WAL/SHM files (if they exist)
cp /var/lib/headscale/db.sqlite-wal /var/lib/headscale/db.sqlite-wal.backup
cp /var/lib/headscale/db.sqlite-shm /var/lib/headscale/db.sqlite-shm.backup
# Start headscale (migration will run automatically)
systemctl start headscaleChangelog
- abed534 Document how to restrict access to exit nodes per user/group
- d23fa26 Fix flaky TestShuffleDERPMapDeterministic by ensuring deterministic map iteration (#2848)
- 0a43aab Use Debian 12 as minimum version for the deb package
- 4bd614a Use current stable base images for Debian and Alpine
- 785168a changelog: prepare for 0.27.1
- 19a3339 changelog: set 0.27 date (#2823)
- af2de35 chore: fix autogroup:self with other acl rules (#2842)
- 02c7c1a cli: only validate bypass-grpc set policy (#2854)
- 5a2ee0c db: add comment about removing migrations
- 28faf8c db: add defensive removal of old indicies
- 456a5d5 db: ignore _litestream tables when validating (#2843)
- ddbd3e1 db: remove all old, unused tables (#2844)
- f9bb88a expire nodes with a custom timestamp (#2828)
- 5cd15c3 fix: make state cookies valid when client uses multiple login URLs
- 3bd4ecd fix: preserve node expiry when tailscaled restarts
- 3455d1c hscontrol/db: fix RenameUser to use Updates()
- 4a8dc2d hscontrol/state,db: preserve node expiry on MapRequest updates
- 4728a2b hscontrol/state: allow expired auth keys for node re-registration
- ddd31ba hscontrol: use Updates() instead of Save() for partial updates
- 773a46a integration: add test to replicate #2862
- 84fe3de integration: reduce TestAutoApproveMultiNetwork matrix to 3 tests (#2815)
- d9c3eaf matcher: Add func for comparing Dests and TheInternet
- f658a8e mkdocs: 0.27.1
- c649c89 policy: Reproduce exit node visibility issues
- 21e3f25 policy: fix issue where non existent user results in empty ssh pol
- a28d9be policy: reproduce 2863 in test
- d7a43a7 state: use AllApprovedRoutes instead of SubnetRoutes
- 2024219 types: Distinguish subnet and exit node access
- bd9cf42 types: NodeView CanAccess uses internal
- 1c0bb03 types: split SubnetRoutes and ExitRoutes
v0.27.0
Minimum supported Tailscale client version: v1.64.0
Database integrity improvements
This release includes a significant database migration that addresses
longstanding issues with the database schema and data integrity that has
accumulated over the years. The migration introduces a schema.sql file as the
source of truth for the expected database schema to ensure new migrations that
will cause divergence does not occur again.
These issues arose from a combination of factors discovered over time: SQLite
foreign keys not being enforced for many early versions, all migrations being
run in one large function until version 0.23.0, and inconsistent use of GORM's
AutoMigrate feature. Moving forward, all new migrations will be explicit SQL
operations rather than relying on GORM AutoMigrate, and foreign keys will be
enforced throughout the migration process.
We are only improving SQLite databases with this change - PostgreSQL databases
are not affected.
Please read the
PR description for more
technical details about the issues and solutions.
SQLite Database Backup Example:
# Stop headscale
systemctl stop headscale
# Backup sqlite database
cp /var/lib/headscale/db.sqlite /var/lib/headscale/db.sqlite.backup
# Backup sqlite WAL/SHM files (if they exist)
cp /var/lib/headscale/db.sqlite-wal /var/lib/headscale/db.sqlite-wal.backup
cp /var/lib/headscale/db.sqlite-shm /var/lib/headscale/db.sqlite-shm.backup
# Start headscale (migration will run automatically)
systemctl start headscaleDERPMap update frequency
The default DERPMap update frequency has been changed from 24 hours to 3 hours.
If you set the derp.update_frequency configuration option, it is recommended
to change it to 3h to ensure that the headscale instance gets the latest
DERPMap updates when upstream is changed.
Autogroups
This release adds support for the three missing autogroups: self
(experimental), member, and tagged. Please refer to the
documentation for a detailed
explanation.
autogroup:self is marked as experimental and should be used with caution, but
we need help testing it. Experimental here means two things; first, generating
the packet filter from policies that use autogroup:self is very expensive, and
it might perform, or straight up not work on Headscale installations with a
large number of nodes. Second, the implementation might have bugs or edge cases
we are not aware of, meaning that nodes or users might gain more access than
expected. Please report bugs.
Node store (in memory database)
Under the hood, we have added a new datastructure to store nodes in memory. This
datastructure is called NodeStore and aims to reduce the reading and writing
of nodes to the database layer. We have not benchmarked it, but expect it to
improve performance for read heavy workloads. We think of it as, "worst case" we
have moved the bottle neck somewhere else, and "best case" we should see a good
improvement in compute resource usage at the expense of memory usage. We are
quite excited for this change and think it will make it easier for us to improve
the code base over time and make it more correct and efficient.
BREAKING
- Remove support for 32-bit binaries
#2692 - Policy: Zero or empty destination port is no longer allowed
#2606 - Stricter hostname validation #2383
- Hostnames must be valid DNS labels (2-63 characters, alphanumeric and
hyphens only, cannot start/end with hyphen) - Client Registration (New Nodes): Invalid hostnames are automatically
renamed toinvalid-XXXXXXformatmy-laptop→ accepted as-isMy-Laptop→my-laptop(lowercased)my_laptop→invalid-a1b2c3(underscore not allowed)test@host→invalid-d4e5f6(@ not allowed)laptop-🚀→invalid-j1k2l3(emoji not allowed)
- Hostinfo Updates / CLI: Invalid hostnames are rejected with an error
- Valid names are accepted or lowercased
- Names with invalid characters, too short (<2), too long (>63), or
starting/ending with hyphen are rejected
- Hostnames must be valid DNS labels (2-63 characters, alphanumeric and
Changes
- Database schema migration improvements for SQLite
#2617- IMPORTANT: Backup your SQLite database before upgrading
- Introduces safer table renaming migration strategy
- Addresses longstanding database integrity issues
- Add flag to directly manipulate the policy in the database
#2765 - DERPmap update frequency default changed from 24h to 3h
#2741 - DERPmap update mechanism has been improved with retry, and is now failing
conservatively, preserving the old map upon failure.
#2741 - Add support for
autogroup:member,autogroup:tagged
#2572 - Fix bug where return routes were being removed by policy
#2767 - Remove policy v1 code #2600
- Refactor Debian/Ubuntu packaging and drop support for Ubuntu 20.04.
#2614 - Remove redundant check regarding
noiseconfig
#2658 - Refactor OpenID Connect documentation
#2625 - Don't crash if config file is missing
#2656 - Adds
/robots.txtendpoint to avoid crawlers
#2643 - OIDC: Use group claim from UserInfo
#2663 - OIDC: Update user with claims from UserInfo before comparing with allowed
groups, email and domain
#2663 - Policy will now reject invalid fields, making it easier to spot spelling
errors #2764 - Add FAQ entry on how to recover from an invalid policy in the database
#2776 - EXPERIMENTAL: Add support for
autogroup:self
#2789 - Add healthcheck command #2659
Upgrade
Please follow the steps outlined in the upgrade guide to update your existing Headscale installation.
It's best to update from one stable version to the next (e.g., 0.24.0 → 0.25.1 → 0.26.1) in case you are multiple releases behind. You should always pick the latest available patch release.
Be sure to check the changelog above for version-specific upgrade instructions and breaking changes.
Backup Your Database
Always backup your database before upgrading. Here's how to backup a SQLite database:
# Stop headscale
systemctl stop headscale
# Backup sqlite database
cp /var/lib/headscale/db.sqlite /var/lib/headscale/db.sqlite.backup
# Backup sqlite WAL/SHM files (if they exist)
cp /var/lib/headscale/db.sqlite-wal /var/lib/headscale/db.sqlite-wal.backup
cp /var/lib/headscale/db.sqlite-shm /var/lib/headscale/db.sqlite-shm.backup
# Start headscale (migration will run automatically)
systemctl start headscaleChangelog
v0.27.0-beta.2
Minimum supported Tailscale client version: v1.64.0
Database integrity improvements
This release includes a significant database migration that addresses
longstanding issues with the database schema and data integrity that has
accumulated over the years. The migration introduces a schema.sql file as the
source of truth for the expected database schema to ensure new migrations that
will cause divergence does not occur again.
These issues arose from a combination of factors discovered over time: SQLite
foreign keys not being enforced for many early versions, all migrations being
run in one large function until version 0.23.0, and inconsistent use of GORM's
AutoMigrate feature. Moving forward, all new migrations will be explicit SQL
operations rather than relying on GORM AutoMigrate, and foreign keys will be
enforced throughout the migration process.
We are only improving SQLite databases with this change - PostgreSQL databases
are not affected.
Please read the
PR description for more
technical details about the issues and solutions.
SQLite Database Backup Example:
# Stop headscale
systemctl stop headscale
# Backup sqlite database
cp /var/lib/headscale/db.sqlite /var/lib/headscale/db.sqlite.backup
# Backup sqlite WAL/SHM files (if they exist)
cp /var/lib/headscale/db.sqlite-wal /var/lib/headscale/db.sqlite-wal.backup
cp /var/lib/headscale/db.sqlite-shm /var/lib/headscale/db.sqlite-shm.backup
# Start headscale (migration will run automatically)
systemctl start headscaleDERPMap update frequency
The default DERPMap update frequency has been changed from 24 hours to 3 hours.
If you set the derp.update_frequency configuration option, it is recommended
to change it to 3h to ensure that the headscale instance gets the latest
DERPMap updates when upstream is changed.
Autogroups
This release adds support for the three missing autogroups: self
(experimental), member, and tagged. Please refer to the
documentation for a detailed
explanation.
autogroup:self is marked as experimental and should be used with caution, but
we need help testing it. Experimental here means two things; first, generating
the packet filter from policies that use autogroup:self is very expensive, and
it might perform, or straight up not work on Headscale installations with a
large number of nodes. Second, the implementation might have bugs or edge cases
we are not aware of, meaning that nodes or users might gain more access than
expected. Please report bugs.
Node store (in memory database)
Under the hood, we have added a new datastructure to store nodes in memory. This
datastructure is called NodeStore and aims to reduce the reading and writing
of nodes to the database layer. We have not benchmarked it, but expect it to
improve performance for read heavy workloads. We think of it as, "worst case" we
have moved the bottle neck somewhere else, and "best case" we should see a good
improvement in compute resource usage at the expense of memory usage. We are
quite excited for this change and think it will make it easier for us to improve
the code base over time and make it more correct and efficient.
BREAKING
- Remove support for 32-bit binaries
#2692 - Policy: Zero or empty destination port is no longer allowed
#2606 - Stricter hostname validation #2383
- Hostnames must be valid DNS labels (2-63 characters, alphanumeric and
hyphens only, cannot start/end with hyphen) - Client Registration (New Nodes): Invalid hostnames are automatically
renamed toinvalid-XXXXXXformatmy-laptop→ accepted as-isMy-Laptop→my-laptop(lowercased)my_laptop→invalid-a1b2c3(underscore not allowed)test@host→invalid-d4e5f6(@ not allowed)laptop-🚀→invalid-j1k2l3(emoji not allowed)
- Hostinfo Updates / CLI: Invalid hostnames are rejected with an error
- Valid names are accepted or lowercased
- Names with invalid characters, too short (<2), too long (>63), or
starting/ending with hyphen are rejected
- Hostnames must be valid DNS labels (2-63 characters, alphanumeric and
Changes
- Database schema migration improvements for SQLite
#2617- IMPORTANT: Backup your SQLite database before upgrading
- Introduces safer table renaming migration strategy
- Addresses longstanding database integrity issues
- Add flag to directly manipulate the policy in the database
#2765 - DERPmap update frequency default changed from 24h to 3h
#2741 - DERPmap update mechanism has been improved with retry, and is now failing
conservatively, preserving the old map upon failure.
#2741 - Add support for
autogroup:member,autogroup:tagged
#2572 - Fix bug where return routes were being removed by policy
#2767 - Remove policy v1 code #2600
- Refactor Debian/Ubuntu packaging and drop support for Ubuntu 20.04.
#2614 - Remove redundant check regarding
noiseconfig
#2658 - Refactor OpenID Connect documentation
#2625 - Don't crash if config file is missing
#2656 - Adds
/robots.txtendpoint to avoid crawlers
#2643 - OIDC: Use group claim from UserInfo
#2663 - OIDC: Update user with claims from UserInfo before comparing with allowed
groups, email and domain
#2663 - Policy will now reject invalid fields, making it easier to spot spelling
errors #2764 - Add FAQ entry on how to recover from an invalid policy in the database
#2776 - EXPERIMENTAL: Add support for
autogroup:self
#2789 - Add healthcheck command #2659
Changelog
- 047dbda Add FAQ on how to disable log submission
- 2a1392f Add healthcheck to container docs
- 46477b8 Downgrade completed broadcast message to debug
- ed38d00 Fix autogroup:self alternative example
- c97d0ff Fix fatal error on missing config file by handling viper.ConfigFileNotFoundError
- 8becb7e Mention explicitly that @ is only required in policy
- 8010cc5 Remove outdated hint about an empty config file
- 2c9e98d fix: guard every error statement with early return (#2810)
- 6682623 integration: add tests for api bypass (#2811)
- 2bf1200 policy: fix autogroup:self propagation and optimize cache invalidation (#2807)
- 1cdea7e stricter hostname validation and replace (#2383)
v0.27.0-beta.1
Minimum supported Tailscale client version: v1.64.0
Database integrity improvements
This release includes a significant database migration that addresses
longstanding issues with the database schema and data integrity that has
accumulated over the years. The migration introduces a schema.sql file as the
source of truth for the expected database schema to ensure new migrations that
will cause divergence does not occur again.
These issues arose from a combination of factors discovered over time: SQLite
foreign keys not being enforced for many early versions, all migrations being
run in one large function until version 0.23.0, and inconsistent use of GORM's
AutoMigrate feature. Moving forward, all new migrations will be explicit SQL
operations rather than relying on GORM AutoMigrate, and foreign keys will be
enforced throughout the migration process.
We are only improving SQLite databases with this change - PostgreSQL databases
are not affected.
Please read the PR description for more technical details about the issues and solutions.
SQLite Database Backup Example:
# Stop headscale
systemctl stop headscale
# Backup sqlite database
cp /var/lib/headscale/db.sqlite /var/lib/headscale/db.sqlite.backup
# Backup sqlite WAL/SHM files (if they exist)
cp /var/lib/headscale/db.sqlite-wal /var/lib/headscale/db.sqlite-wal.backup
cp /var/lib/headscale/db.sqlite-shm /var/lib/headscale/db.sqlite-shm.backup
# Start headscale (migration will run automatically)
systemctl start headscaleDERPMap update frequency
The default DERPMap update frequency has been changed from 24 hours to 3 hours.
If you set the derp.update_frequency configuration option, it is recommended
to change it to 3h to ensure that the headscale instance gets the latest
DERPMap updates when upstream is changed.
Autogroups
This release adds support for the three missing autogroups: self
(experimental), member, and tagged. Please refer to the
documentation for a detailed
explanation.
autogroup:self is marked as experimental and should be used with caution, but
we need help testing it. Experimental here means two things; first, generating
the packet filter from policies that use autogroup:self is very expensive, and
it might perform, or straight up not work on Headscale installations with a
large number of nodes. Second, the implementation might have bugs or edge cases
we are not aware of, meaning that nodes or users might gain more access than
expected. Please report bugs.
Node store (in memory database)
Under the hood, we have added a new datastructure to store nodes in memory. This
datastructure is called NodeStore and aims to reduce the reading and writing
of nodes to the database layer. We have not benchmarked it, but expect it to
improve performance for read heavy workloads. We think of it as, "worst case" we
have moved the bottle neck somewhere else, and "best case" we should see a good
improvement in compute resource usage at the expense of memory usage. We are
quite excited for this change and think it will make it easier for us to improve
the code base over time and make it more correct and efficient.
BREAKING
- Remove support for 32-bit binaries
#2692 - Policy: Zero or empty destination port is no longer allowed
#2606
Changes
- Database schema migration improvements for SQLite
#2617- IMPORTANT: Backup your SQLite database before upgrading
- Introduces safer table renaming migration strategy
- Addresses longstanding database integrity issues
- Add flag to directly manipulate the policy in the database
#2765 - DERPmap update frequency default changed from 24h to 3h
#2741 - DERPmap update mechanism has been improved with retry, and is now failing
conservatively, preserving the old map upon failure.
#2741 - Add support for
autogroup:member,autogroup:tagged
#2572 - Fix bug where return routes were being removed by policy
#2767 - Remove policy v1 code #2600
- Refactor Debian/Ubuntu packaging and drop support for Ubuntu 20.04.
#2614 - Remove redundant check regarding
noiseconfig
#2658 - Refactor OpenID Connect documentation
#2625 - Don't crash if config file is missing
#2656 - Adds
/robots.txtendpoint to avoid crawlers
#2643 - OIDC: Use group claim from UserInfo
#2663 - OIDC: Update user with claims from UserInfo before comparing with allowed
groups, email and domain
#2663 - Policy will now reject invalid fields, making it easier to spot spelling
errors #2764 - Add FAQ entry on how to recover from an invalid policy in the database
#2776 - EXPERIMENTAL: Add support for
autogroup:self
#2789 - Add healthcheck command #2659
Changelog
- 0512f7c .github/ISSUE_TEMPLATE: add node number to environment
- 05996a5 .github/workflow: only run a few selected postgres tests
- f6c4b33 .github/workflows: add generate check
- 5ba7120 .github/workflows: prettier
- 4a8d2d9 .github/workflows: reduce integration retry to 3
- 7f8b14f .github/workflows: remove integration retry
- e949859 Add DERP docs
- bd35fcf Add FAQ entry about policy migration in the database
- 30d12da Add FAQ entry about the recommended upgrade path
- bcd80ee Add debugging and troubleshooting guide
- 76ca7a2 Add headscale-console
- 98fc056 Bump version in docs
- 33e9e7a CLAUDE: split into agents
- 3f72ee9 Clarify SIGHUP log message (#2661)
- 51c6367 Correctly document the default for dns.override_local_dns
- 2f3c365 Describe how to remove a DERP region
- 49b3468 Do not ignore config-example.yml
- c15aa54 Document HEADSCALE_CONFIG
- b50e10a Document breaking change for dns.override_local_dns
- 30cec3a Document ports in use
- c04e17d Document valid log levels
- cd70457 Drop support for Ubuntu 20.04
- 43c9c50 Drop syslog.target and systemd-managed /var/run
- be337c6 Enable derp.server.verify_clients by default
- e73b2a9 Ensure that a username starts with a letter (#2635)
- fa619ea Fix CHANGELOG for autogroup:member and autogroup:tagged (#2733)
- 086fcad Fix Internal server error on /verify (#2735)
- bad7833 Fix
/machine/mapendpoint vulnerability (#2642) - 46c59a3 Fix command in bug report template
- a8f2eeb Fix config param name in TLS doc
- e7fe645 Fix invocation of golangci-lint (#2703)
- 3123d52 Fix typos
- 4e6d42d Keycloak's group format is configurable
- 30a1f7e Log registrationID to simplify interactive node registration
- 2d680b5 Misc typos and spelling
- 5d8a2c2 OIDC: Query userinfo endpoint before verifying user
- 4a941a2 Refactor Debian/Ubuntu package
- d461db3 Refactor OpenID Connect documentation
- a2a6d20 Refactor to use reflect.TypeFor
- 8ff5baa Refresh OIDC docs
- b8044c2 Replace magic-nix-cache-action (#2575)
- a98d9bd The preauthkeys commands expect a user id instead of a username
- 881a6b9 The sequential prefix allocation uses a best-effort approach
- 3fbde7a Update official.md
- 860a8a5 Update tools.md
- 4d61da3 Use an IPv4 address range suitable for documentation
- c6427aa Use group id instead of group name for Entra ID
- c07cc49 add health command (#2659)
- 7fce506 all: remove 32 bit sup...
v0.26.1
v0.26.0
BREAKING
Routes
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.
- Route API and CLI has been removed
#2422 - Routes are now managed via the Node API
#2422 - Only routes accessible to the node will be sent to the node
#2561
Policy v2
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
- The policy is validated and "resolved" when loading, providing errors for
invalid rules and conditions.- Previously this was done as a mix between load and runtime (when it was
applied to a node). - This means that when you convert the first time, what was previously a
policy that loaded, but failed at runtime, will now fail at load time.
- Previously this was done as a mix between load and runtime (when it was
- Error messages should be more descriptive and informative.
- There is still work to be here, but it is already improved with "typing"
(e.g. only Users can be put in Groups)
- There is still work to be here, but it is already improved with "typing"
- All users must contain an
@character.- If your user naturally contains and
@, like an email, this will just work. - If its based on usernames, or other identifiers not containing an
@, an
@should be appended at the end. For example, if your user isjohn, it
must be written asjohn@in the policy.
- If your user naturally contains and
Migration notes when the policy is stored in the database.
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).
- Start Headscale 0.26 with the environment variable
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: 1 - Dump the policy to a file:
headscale policy get > policy.json - Edit
policy.jsonand migrate to policy V2. Use the command
headscale policy check --file policy.jsonto check for policy errors. - Load the modified policy:
headscale policy set --file policy.json - Restart Headscale without the environment variable
HEADSCALE_POLICY_V1.
Headscale should now print the messageUsing policy manager version: 2and
startup successfully.
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
Other breaking changes
- Disallow
server_urlandbase_domainto be equal
#2544 - Return full user in API for pre auth keys instead of string
#2542 - Pre auth key API/CLI now uses ID over username
#2542
Changes
- Use Go 1.24 #2427
- Add
headscale policy checkcommand to check policy
#2553 oidc.map_legacy_usersandoidc.strip_email_domainhas been removed
#2411- Add more information to
/debugendpoint
#2420- It is now possible to inspect running goroutines and take profiles
- View of config, policy, filter, ssh policy per node, connected nodes and
DERPmap
- OIDC: Fetch UserInfo to get EmailVerified if necessary
#2493- If a OIDC provider doesn't include the
email_verifiedclaim in its ID
tokens, Headscale will attempt to get it from the UserInfo endpoint.
- If a OIDC provider doesn't include the
- OIDC: Try to populate name, email and username from UserInfo
#2545 - Improve performance by only querying relevant nodes from the database for node
updates #2509 - node FQDNs in the netmap will now contain a dot (".") at the end. This aligns
with behaviour of tailscale.com
#2503 - Restore support for "Override local DNS"
#2438 - Add documentation for routes
#2496
Changelog
v0.26.0-beta.2
BREAKING
Routes
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.
- Route API and CLI has been removed
#2422 - Routes are now managed via the Node API
#2422 - Only routes accessible to the node will be sent to the node
#2561
Policy v2
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
- The policy is validated and "resolved" when loading, providing errors for
invalid rules and conditions.- Previously this was done as a mix between load and runtime (when it was
applied to a node). - This means that when you convert the first time, what was previously a
policy that loaded, but failed at runtime, will now fail at load time.
- Previously this was done as a mix between load and runtime (when it was
- Error messages should be more descriptive and informative.
- There is still work to be here, but it is already improved with "typing"
(e.g. only Users can be put in Groups)
- There is still work to be here, but it is already improved with "typing"
- All users must contain an
@character.- If your user naturally contains and
@, like an email, this will just work. - If its based on usernames, or other identifiers not containing an
@, an
@should be appended at the end. For example, if your user isjohn, it
must be written asjohn@in the policy.
- If your user naturally contains and
Migration notes when the policy is stored in the database.
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).
- Start Headscale 0.26 with the environment variable
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: 1 - Dump the policy to a file:
headscale policy get > policy.json - Edit
policy.jsonand migrate to policy V2. Use the command
headscale policy check --file policy.jsonto check for policy errors. - Load the modified policy:
headscale policy set --file policy.json - Restart Headscale without the environment variable
HEADSCALE_POLICY_V1.
Headscale should now print the messageUsing policy manager version: 2and
startup successfully.
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
Other breaking changes
- Disallow
server_urlandbase_domainto be equal
#2544 - Return full user in API for pre auth keys instead of string
#2542 - Pre auth key API/CLI now uses ID over username
#2542
Changes
- Use Go 1.24 #2427
- Add
headscale policy checkcommand to check policy
#2553 oidc.map_legacy_usersandoidc.strip_email_domainhas been removed
#2411- Add more information to
/debugendpoint
#2420- It is now possible to inspect running goroutines and take profiles
- View of config, policy, filter, ssh policy per node, connected nodes and
DERPmap
- OIDC: Fetch UserInfo to get EmailVerified if necessary
#2493- If a OIDC provider doesn't include the
email_verifiedclaim in its ID
tokens, Headscale will attempt to get it from the UserInfo endpoint.
- If a OIDC provider doesn't include the
- OIDC: Try to populate name, email and username from UserInfo
#2545 - Improve performance by only querying relevant nodes from the database for node
updates #2509 - node FQDNs in the netmap will now contain a dot (".") at the end. This aligns
with behaviour of tailscale.com
#2503 - Restore support for "Override local DNS"
#2438 - Add documentation for routes
#2496
Changelog
- dd0cbdf Add migration steps when policy is stored in the database (#2581)
- 833e0f6 Remove subnet router visibility workaround from docs (#2569)
- d81b005 Simplify policy migration (#2582)
- 43943ae bring back last_seen in database (#2579)
- 377b854 cli: policy check, dont require config or log (#2580)
- 37dc0da policy/v2: separate exit node and 0.0.0.0/0 routes (#2578)
- 56db4ed policy/v2: validate that no undefined group or tag is used (#2576)
v0.26.0-beta.1
BREAKING
Routes
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.
- Route API and CLI has been removed
#2422 - Routes are now managed via the Node API
#2422 - Only routes accessible to the node will be sent to the node
#2561
Policy v2
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
- The policy is validated and "resolved" when loading, providing errors for
invalid rules and conditions.- Previously this was done as a mix between load and runtime (when it was
applied to a node). - This means that when you convert the first time, what was previously a
policy that loaded, but failed at runtime, will now fail at load time.
- Previously this was done as a mix between load and runtime (when it was
- Error messages should be more descriptive and informative.
- There is still work to be here, but it is already improved with "typing"
(e.g. only Users can be put in Groups)
- There is still work to be here, but it is already improved with "typing"
- All users must contain an
@character.- If your user naturally contains and
@, like an email, this will just work. - If its based on usernames, or other identifiers not containing an
@, an
@should be appended at the end. For example, if your user isjohn, it
must be written asjohn@in the policy.
- If your user naturally contains and
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
Other breaking changes
- Disallow
server_urlandbase_domainto be equal
#2544 - Return full user in API for pre auth keys instead of string
#2542 - Pre auth key API/CLI now uses ID over username
#2542
Changes
- Use Go 1.24 #2427
- Add
headscale policy checkcommand to check policy
#2553 oidc.map_legacy_usersandoidc.strip_email_domainhas been removed
#2411- Add more information to
/debugendpoint
#2420- It is now possible to inspect running goroutines and take profiles
- View of config, policy, filter, ssh policy per node, connected nodes and
DERPmap
- OIDC: Fetch UserInfo to get EmailVerified if necessary
#2493- If a OIDC provider doesn't include the
email_verifiedclaim in its ID
tokens, Headscale will attempt to get it from the UserInfo endpoint.
- If a OIDC provider doesn't include the
- OIDC: Try to populate name, email and username from UserInfo
#2545 - Improve performance by only querying relevant nodes from the database for node
updates #2509 - node FQDNs in the netmap will now contain a dot (".") at the end. This aligns
with behaviour of tailscale.com
#2503 - Restore support for "Override local DNS"
#2438 - Add documentation for routes
#2496
Changelog
- 586a20f Add a FAQ entry about two nodes seeing each other
- 18d21d3 Add documentation for routes (#2496)
- d2a6356 Add unraid-headscale-admin web UI to docs (#2515)
- 29ba294 Add usage example to routes flag
- fe06a00 Container images are also available on GHCR (#2470)
- e52f1e8 Drop routes table
- 87326f5 Experimental implementation of Policy v2 (#2214)
- 24ad235 Explicitly handle /headscale/{config,lib,run} in container docs
- cb7c017 Fix deprecation warnings (#2558)
- 92e587a Fix goroutine leak in EphemeralGC on node cancel (#2538)
- 56d085b Fix panic on fast reconnection of node (#2536)
- 4651d06 Make matchers part of the Policy interface (#2514)
- b9868f6 Make more granular SSH tests for both Policies (#2555)
- f3a1e69 Mention "Network flow logs" as a missing feature
- 707438f Mention that private keys generated if needed
- 9a86ffc Misc doc fixes (#2562)
- 603f3ad Multi network integration tests (#2464)
- b5953d6 OIDC: Fetch UserInfo to get EmailVerified if necessary (#2493)
- 0d31347 Only read relevant nodes from database in PeerChangedResponse (#2509)
- 7891378 Redo route code (#2422)
- cbce8f6 Remove coderabbit
- 0a243b4 Remove leftover printf
- 1e0516b Restore support for "Override local DNS" (#2438)
- 0520209 Set content-type to JSON for some debug endpoints
- 53d9c95 Update container.md
- 3287aa8 Update oidc.md
- 7dc8636 Update source.md
- c61fbe9 activate json logs (#2424)
- 098ab03 add casbin user test (#2474)
- 818046f add faq section on scaling/performance (#2476)
- 3bf7d5a add git hash to binary, print on startup (#2415)
- 00d5d64 add third-party tool headscale-pf
- e3521be allow users to be defined with @ in v1 (#2495)
- 1dddd3e app: throw away not found body (#2566)
- eb1ecef auth: ensure that routes are autoapproved when the node is stored (#2550)
- a4a203b cli/nodes: filter nodes without any routes (#2551)
- 93afb03 cmd: add policy check command (#2553)
- 30539b2 config: disallow same server url and base_domain (#2544)
- 1099890 ensure final dot on node name (#2503)
- c923f46 error on undefined host in policy (#2490)
- 03a9169 feat: Create headscale user and group as system user/groups (#2322)
- 5a18e91 fix auto approver on register and new policy (#2506)
- 1686819 fix double login URL with OIDC (#2445)
- da2ca05 fix routes not being saved when new nodes registers (#2444)
- f120632 fix webauth + autoapprove routes (#2528)
- b3fa16f flake.lock: Update (#2419)
- 2cce3a9 flake.lock: Update (#2430)
- b220fb7 flake.lock: Update (#2440)
- b6fbd37 flake.lock: Update (#2454)
- badbb68...