# Headscale changelog > An open-source implementation of the Tailscale control server. - Vendor: Juan Font - Category: Developer Tools - Official site: https://headscale.net - Tracked by: What's New (https://whatsnew.fyi/product/headscale) - Harvested from: GitHub (juanfont/headscale) - Entries below: 10 (newest first) What's New is an index, not a publisher: every entry below links to the vendor's own release notes, which are the authoritative source. Entries are labelled where they are hand-curated sample data, pre-releases, or drawn from a secondary source such as a developer blog. ## Releases ### v0.29.3 - Date: 2026-07-29 - Version: v0.29.3 - Original notes: https://github.com/juanfont/headscale/releases/tag/v0.29.3 - Permalink: https://whatsnew.fyi/product/headscale/releases/v0.29.3 - **fixed** — Fix tagged node stuck expired after tailscale logout, unable to re-authenticate - **changed** — Re-registering a tagged node with a different pre-auth key now applies the new key's tags instead of silently keeping the old ones - **fixed** — Fix re-authenticating an already-tagged node with --advertise-tags being rejected when the authenticating user owns the tags - **fixed** — Fix ephemeral nodes lingering as disconnected after reconnect churn - **fixed** — Fix node registration falsely returning 401 registration timed out when auth completes as the request context expires - **security** — Check the machine key on the followup registration poll so a leaked auth ID cannot return the registering user's identity - **changed** — Reject /key requests below the supported capability version floor, matching /ts2021 **Minimum supported Tailscale client version: v1.80.0** ###### Changes - Fix tagged node stuck expired after `tailscale logout`, unable to re-authenticate [#3394](https://github.com/juanfont/headscale/pull/3394) - Re-registering a tagged node with a different pre-auth key now applies the new key's tags instead of silently keeping the old ones [#3394](https://github.com/juanfont/headscale/pull/3394) - Fix re-authenticating an already-tagged node with `--advertise-tags` being rejected when the authenticating user owns the tags [#3394](https://github.com/juanfont/headscale/pull/3394) - Fix ephemeral nodes lingering as disconnected after reconnect churn [#3383](https://github.com/juanfont/headscale/pull/3383) - Fix node registration falsely returning `401 registration timed out` when auth completes as the request context expires [#3392](https://github.com/juanfont/headscale/pull/3392) - Check the machine key on the followup registration poll so a leaked auth ID cannot return the registering user's identity [#3393](https://github.com/juanfont/headscale/pull/3393) - Reject `/key` requests below the supported capability version floor, matching `/ts2021` [#3391](https://github.com/juanfont/headscale/pull/3391) ##### Upgrade Please follow the steps outlined in the [upgrade guide](https://headscale.net/stable/setup/upgrade/) to update your existing Headscale installation. ##### Changelog * 235a57ec31388901a78d054b1adb9dff900658cc CHANGELOG: add 0.29.3 * 089d6c41092c1c9da6c22a1493407747b9da0e15 Explicitly select lunr as search provider * fba84ca2328330780433ce0ed45fa90e92559eac auth: check machine key on the followup registration path * 12928418b8330a9ed3024045eb3199a90d01e9d1 build: bump Go toolchain to 1.26.5 * 9609a0b87ddc9cb28ec85244148bfed3b42f041a hscontrol: gate /key on supported capability version * bdc3e996de162fb15149085b448d931e96c05eb9 hscontrol: prefer completed auth over expired ctx in followup wait * 5aff68b5b9921db5ccb88013bb1740077ab872fb mkdocs: bump version * 4a1e77359d288f94dccfc63ea2d1e869bd8e1daf policy,state: authorize reauth tags against the authenticating user * 5fb514e6e09648676c8bd850201b0f84a6123694 poll: do not cancel ephemeral GC until Connect succeeds * 1fccdb18bda259e92b439a447191643d33e52277 state: apply a new pre-auth key's tags on re-registration * d2028832007b6b4544b119967db9658c9be24c6a state: do not expire tagged nodes on logout ### v0.29.2 - Date: 2026-07-01 - Version: v0.29.2 - Original notes: https://github.com/juanfont/headscale/releases/tag/v0.29.2 - Permalink: https://whatsnew.fyi/product/headscale/releases/v0.29.2 - **fixed** — Fix map generation serializing on the policy lock, so a mass reconnect on autogroup:self, via or relay policies no longer stalls clients into unexpected EOF retry loops - **fixed** — Fix /ts2021 rejecting the WebSocket GET upgrade with 405, which prevented Tailscale JS/WASM control clients from connecting - **fixed** — Gracefully handle nodes with an invalid FQDN (empty or too long) instead of failing map delivery; offending names are logged at startup with the fix command ##### Changes - Fix map generation serializing on the policy lock, so a mass reconnect on `autogroup:self`, via or relay policies no longer stalls clients into `unexpected EOF` retry loops [#3358](https://github.com/juanfont/headscale/pull/3358) - Fix `/ts2021` rejecting the WebSocket `GET` upgrade with 405, which prevented Tailscale JS/WASM control clients from connecting [#3359](https://github.com/juanfont/headscale/pull/3359) - Gracefully handle nodes with an invalid FQDN (empty or too long) instead of failing map delivery; offending names are logged at startup with the fix command [#3349](https://github.com/juanfont/headscale/pull/3349) ##### Upgrade Please follow the steps outlined in the [upgrade guide](https://headscale.net/stable/setup/upgrade/) to update your existing Headscale installation. ##### Changelog * 8eea89488c642f3d5f617fab5493d5f51f6f4ad0 CHANGELOG: drop unreleased 0.30.0 stub * f708c5b0108ac2ba5ff384ae9b85bdef488e1ffc CHANGELOG: note /ts2021 WebSocket GET fix * 735742e3eea9be93c42c2bd76c9641a74dc11e20 CHANGELOG: note 0.29.2 invalid-name map fix * 3ac33cf1d5fa66bafcdf11fd0919d10f118ee8ea CHANGELOG: shorten 0.29.2 invalid-name entry, set date * f885d87827bcae30a07063f2723cd03458144a00 Fix invalid ip syntax * 1ec7b7fb726ea6270a1eb459534753ef27eda736 hscontrol: register /ts2021 for WebSocket GET * 8f4e69d2a643f41378275c1b33395448ba8a596d integration: add TS2021 WebSocket tests to CI matrix * e7851ef8815248699833ac82fc7bd4d127a05713 integration: test /ts2021 WebSocket GET with a real WASM client * 9d1327458fb21b240ea8078e64aa09ed30b59a96 mapper,policy: add reconnect-storm and lock-concurrency regression tests * ec6719736806e53f1aad1e219670bb8b79476052 mapper: skip peers with invalid names instead of failing the map * d4f2acf3ab3cada875ab88eab4e0f81e4118cd12 policy: take RLock for reads so map generation runs concurrently * 5fb76eb231f105c67ff38316958a889ab4b1534b poll: return an HTTP error on long-poll setup failure * fd154fdb663d4992fb15cd95796c4791e48b096f state: log nodes with map-breaking data at startup * 9c9206a92b7b3ca416e955f472e7f002e9215b75 state: reject renames whose FQDN exceeds the hostname limit ### v0.29.1 - Date: 2026-06-18 - Version: v0.29.1 - Original notes: https://github.com/juanfont/headscale/releases/tag/v0.29.1 - Permalink: https://whatsnew.fyi/product/headscale/releases/v0.29.1 - **fixed** — Fix nodes with tags='null' losing their assigned user on upgrade **Minimum supported Tailscale client version: v1.80.0** ###### Changes - Fix nodes with `tags='null'` losing their assigned user on upgrade [#3325](https://github.com/juanfont/headscale/pull/3325) ##### Upgrade Please follow the steps outlined in the [upgrade guide](https://headscale.net/stable/setup/upgrade/) to update your existing Headscale installation. ##### Changelog * 636f660caf3ca995fad5a9ed6f1b6b0578637b55 db: preserve user_id on untagged nodes with tags='null' ### v0.29.0 - Date: 2026-06-17 - Version: v0.29.0 - Original notes: https://github.com/juanfont/headscale/releases/tag/v0.29.0 - Permalink: https://whatsnew.fyi/product/headscale/releases/v0.29.0 - **added** — Support for SSH rules with check action that prompt users to authenticate via OIDC or CLI approval before access is granted - **added** — New headscale auth CLI command group with approve, reject, and register subcommands for managing authentication requests - **added** — Policy tests (beta) that evaluate the tests block in a policy file to assert reachability between named sources and destinations - **added** — SSH policy tests (beta) that evaluate the sshTests block in a policy file with accept, deny, and check assertions - **added** — Support for Tailscale grants alongside ACLs to control application-level features like Taildrive file sharing and peer relay - **added** — autogroup:danger-all that resolves to all IP addresses including those outside the tailnet - **added** — Node attributes (nodeAttrs) block in ACL policies to assign Tailscale node capabilities to nodes matching specified targets - **added** — randomizeClientPort top-level policy field that toggles the default client port randomization for every node - **added** — auto_update.enabled config option to control the tailnet-wide default for client auto-update - **changed** — Minimum supported Tailscale client version is now v1.80.0 - **changed** — SSH rule parsing now trims surrounding whitespace on action, users, src, and dst fields - **fixed** — Tailscale ACL compatibility improved through extensive test cases generated using Tailscale clients and official SaaS - **removed** — Wildcard * no longer matches all IPs in ACL rules, replaced by autogroup:danger-all **Minimum supported Tailscale client version: v1.80.0** ###### Tailscale ACL compatibility improvements Extensive test cases were systematically generated using Tailscale clients and the official SaaS to understand how the packet filter should be generated. We discovered a few differences, but overall our implementation was very close. [#3036](https://github.com/juanfont/headscale/pull/3036) ###### SSH check action SSH rules with `"action": "check"` are now supported. When a client initiates a SSH connection to a node with a `check` action policy, the user is prompted to authenticate via OIDC or CLI approval before access is granted. OIDC approval requires the authenticated user to own the source node; tagged source nodes cannot use SSH check-mode. A new `headscale auth` CLI command group supports the approval flow: - `headscale auth approve --auth-id ` approves a pending authentication request (SSH check or web auth) - `headscale auth reject --auth-id ` rejects a pending authentication request - `headscale auth register --auth-id --user ` registers a node (replaces deprecated `headscale nodes register`) [#1850](https://github.com/juanfont/headscale/pull/1850) [#3180](https://github.com/juanfont/headscale/pull/3180) ###### Policy tests (beta) Headscale now evaluates the `tests` block in a policy file. Tests assert reachability between named sources and destinations and cover the whole policy — both `acls` and `grants` rules contribute. They run on user-initiated writes via `headscale policy set`, on SIGHUP reload (`systemctl reload headscale` / `kill -HUP $(pidof headscale)`), and on `headscale policy check`. A failing test rejects the write before it is applied, with the same error message Tailscale SaaS would return for the same policy. At boot a stored policy whose tests no longer pass — for example because a referenced user was deleted while the server was offline — logs a warning and the server keeps running. Fix the policy and reload. This feature is **beta** while behavioural coverage against Tailscale SaaS broadens. [#3229](https://github.com/juanfont/headscale/pull/3229) ###### SSH policy tests (beta) Headscale now evaluates the `sshTests` block in a policy file. Each entry names a source, one or more destination hosts, and three optional user lists: `accept` asserts the listed login users reach every destination via an accept- or check-action SSH rule, `deny` asserts none of them reach any destination, and `check` requires reachability specifically through a check-action rule. Tests run on `headscale policy set`, on SIGHUP reload (`systemctl reload headscale` / `kill -HUP $(pidof headscale)`), and on `headscale policy check`. A failing test rejects the write before it is applied, with the same error message Tailscale SaaS would return for the same policy. At boot a stored policy whose sshTests no longer pass — for example because a referenced user was deleted while the server was offline — logs a warning and the server keeps running. Fix the policy and reload. This feature is **beta** while behavioural coverage against Tailscale SaaS broadens. [#3263](https://github.com/juanfont/headscale/pull/3263) ###### SSH rule validation SSH rule parsing now trims surrounding whitespace on `action`, `users`, `src`, and `dst`, rejects empty or wildcard entries in `users`, rejects empty `acceptEnv`, and rejects negative `checkPeriod`. `hosts:` aliases are rejected as SSH destinations, non-ASCII tag names are rejected at parse time, and the wording for group-nesting cycles matches Tailscale SaaS. [#3263](https://github.com/juanfont/headscale/pull/3263) ###### Grants We now support [Tailscale grants](https://tailscale.com/docs/features/access-control/grants) alongside ACLs. Grants extend what you can express in a policy beyond packet filtering: the `app` field controls application-level features like Taildrive file sharin _[Truncated at 4000 characters — full notes: https://github.com/juanfont/headscale/releases/tag/v0.29.0]_ ### v0.29.0-beta.4 - Date: 2026-06-15 - Version: v0.29.0-beta.4 - Original notes: https://github.com/juanfont/headscale/releases/tag/v0.29.0-beta.4 - Permalink: https://whatsnew.fyi/product/headscale/releases/v0.29.0-beta.4 - Labels: Pre-release - **changed** — Minimum supported Tailscale client version is now v1.80.0 - **changed** — Tailscale ACL compatibility improved through systematic test case generation against official SaaS - **added** — SSH rules with check action are now supported, prompting users to authenticate via OIDC or CLI approval before access is granted - **added** — New headscale auth CLI command group with approve, reject, and register subcommands for authentication workflows - **added** — Policy tests in the tests block now evaluated on policy writes, reloads, and checks, with failures preventing application - **added** — SSH policy tests in the sshTests block now evaluated on policy writes, reloads, and checks - **changed** — SSH rule parsing now trims whitespace on action, users, src, and dst fields and validates entries more strictly - **added** — Support for Tailscale grants alongside ACLs to control application-level features like Taildrive and peer relay - **added** — New autogroup:danger-all that resolves to all IP addresses including those outside the tailnet - **added** — ACL policies now accept a nodeAttrs block to hand Tailscale node capabilities to matching nodes - **added** — New randomizeClientPort top-level policy field to toggle client port randomization default for every node - **added** — New auto_update.enabled config option controls tailnet-wide default for client auto-update **Minimum supported Tailscale client version: v1.80.0** ###### Tailscale ACL compatibility improvements Extensive test cases were systematically generated using Tailscale clients and the official SaaS to understand how the packet filter should be generated. We discovered a few differences, but overall our implementation was very close. [#3036](https://github.com/juanfont/headscale/pull/3036) ###### SSH check action SSH rules with `"action": "check"` are now supported. When a client initiates a SSH connection to a node with a `check` action policy, the user is prompted to authenticate via OIDC or CLI approval before access is granted. OIDC approval requires the authenticated user to own the source node; tagged source nodes cannot use SSH check-mode. A new `headscale auth` CLI command group supports the approval flow: - `headscale auth approve --auth-id ` approves a pending authentication request (SSH check or web auth) - `headscale auth reject --auth-id ` rejects a pending authentication request - `headscale auth register --auth-id --user ` registers a node (replaces deprecated `headscale nodes register`) [#1850](https://github.com/juanfont/headscale/pull/1850) [#3180](https://github.com/juanfont/headscale/pull/3180) ###### Policy tests (beta) Headscale now evaluates the `tests` block in a policy file. Tests assert reachability between named sources and destinations and cover the whole policy — both `acls` and `grants` rules contribute. They run on user-initiated writes via `headscale policy set`, on SIGHUP reload (`systemctl reload headscale` / `kill -HUP $(pidof headscale)`), and on `headscale policy check`. A failing test rejects the write before it is applied, with the same error message Tailscale SaaS would return for the same policy. At boot a stored policy whose tests no longer pass — for example because a referenced user was deleted while the server was offline — logs a warning and the server keeps running. Fix the policy and reload. This feature is **beta** while behavioural coverage against Tailscale SaaS broadens. [#3229](https://github.com/juanfont/headscale/pull/3229) ###### SSH policy tests (beta) Headscale now evaluates the `sshTests` block in a policy file. Each entry names a source, one or more destination hosts, and three optional user lists: `accept` asserts the listed login users reach every destination via an accept- or check-action SSH rule, `deny` asserts none of them reach any destination, and `check` requires reachability specifically through a check-action rule. Tests run on `headscale policy set`, on SIGHUP reload (`systemctl reload headscale` / `kill -HUP $(pidof headscale)`), and on `headscale policy check`. A failing test rejects the write before it is applied, with the same error message Tailscale SaaS would return for the same policy. At boot a stored policy whose sshTests no longer pass — for example because a referenced user was deleted while the server was offline — logs a warning and the server keeps running. Fix the policy and reload. This feature is **beta** while behavioural coverage against Tailscale SaaS broadens. [#3263](https://github.com/juanfont/headscale/pull/3263) ###### SSH rule validation SSH rule parsing now trims surrounding whitespace on `action`, `users`, `src`, and `dst`, rejects empty or wildcard entries in `users`, rejects empty `acceptEnv`, and rejects negative `checkPeriod`. `hosts:` aliases are rejected as SSH destinations, non-ASCII tag names are rejected at parse time, and the wording for group-nesting cycles matches Tailscale SaaS. [#3263](https://github.com/juanfont/headscale/pull/3263) ###### Grants We now support [Tailscale grants](https://tailscale.com/docs/features/access-control/grants) alongside ACLs. Grants extend what you can express in a policy beyond packet filtering: the `app` field controls application-level features like Taildrive file sharin _[Truncated at 4000 characters — full notes: https://github.com/juanfont/headscale/releases/tag/v0.29.0-beta.4]_ ### v0.29.0-beta.3 - Date: 2026-06-11 - Version: v0.29.0-beta.3 - Original notes: https://github.com/juanfont/headscale/releases/tag/v0.29.0-beta.3 - Permalink: https://whatsnew.fyi/product/headscale/releases/v0.29.0-beta.3 - Labels: Pre-release - **added** — SSH rules with "action": "check" are now supported, prompting users to authenticate via OIDC or CLI approval before access is granted - **added** — New headscale auth CLI command group with approve, reject, and register subcommands for managing authentication requests and node registration - **added** — Policy tests (beta) that evaluate the tests block in policy files to assert reachability between sources and destinations - **added** — SSH policy tests (beta) that evaluate the sshTests block in policy files with accept, deny, and check assertions - **added** — Support for Tailscale grants alongside ACLs, including app and via fields for application-level features and traffic steering - **added** — autogroup:danger-all autogroup that resolves to all IP addresses (0.0.0.0/0 and ::/0) - **added** — Node attributes (nodeAttrs) block in ACL policies to assign Tailscale node capabilities to nodes matching specified targets - **added** — randomizeClientPort as a top-level policy field to toggle the default for every node - **added** — auto_update.enabled config option to control tailnet-wide default for client auto-update - **changed** — SSH rule parsing now trims surrounding whitespace on action, users, src, and dst fields - **changed** — Tailscale ACL compatibility improved based on systematic testing with Tailscale clients and official SaaS - **fixed** — SSH rule parsing now rejects empty or wildcard entries in users, empty acceptEnv, and negative checkPeriod - **fixed** — SSH rule parsing now rejects hosts: aliases as SSH destinations and non-ASCII tag names at parse time **Minimum supported Tailscale client version: v1.80.0** ###### Tailscale ACL compatibility improvements Extensive test cases were systematically generated using Tailscale clients and the official SaaS to understand how the packet filter should be generated. We discovered a few differences, but overall our implementation was very close. [#3036](https://github.com/juanfont/headscale/pull/3036) ###### SSH check action SSH rules with `"action": "check"` are now supported. When a client initiates a SSH connection to a node with a `check` action policy, the user is prompted to authenticate via OIDC or CLI approval before access is granted. OIDC approval requires the authenticated user to own the source node; tagged source nodes cannot use SSH check-mode. A new `headscale auth` CLI command group supports the approval flow: - `headscale auth approve --auth-id ` approves a pending authentication request (SSH check or web auth) - `headscale auth reject --auth-id ` rejects a pending authentication request - `headscale auth register --auth-id --user ` registers a node (replaces deprecated `headscale nodes register`) [#1850](https://github.com/juanfont/headscale/pull/1850) [#3180](https://github.com/juanfont/headscale/pull/3180) ###### Policy tests (beta) Headscale now evaluates the `tests` block in a policy file. Tests assert reachability between named sources and destinations and cover the whole policy — both `acls` and `grants` rules contribute. They run on user-initiated writes via `headscale policy set`, on SIGHUP reload (`systemctl reload headscale` / `kill -HUP $(pidof headscale)`), and on `headscale policy check`. A failing test rejects the write before it is applied, with the same error message Tailscale SaaS would return for the same policy. At boot a stored policy whose tests no longer pass — for example because a referenced user was deleted while the server was offline — logs a warning and the server keeps running. Fix the policy and reload. This feature is **beta** while behavioural coverage against Tailscale SaaS broadens. [#3229](https://github.com/juanfont/headscale/pull/3229) ###### SSH policy tests (beta) Headscale now evaluates the `sshTests` block in a policy file. Each entry names a source, one or more destination hosts, and three optional user lists: `accept` asserts the listed login users reach every destination via an accept- or check-action SSH rule, `deny` asserts none of them reach any destination, and `check` requires reachability specifically through a check-action rule. Tests run on `headscale policy set`, on SIGHUP reload (`systemctl reload headscale` / `kill -HUP $(pidof headscale)`), and on `headscale policy check`. A failing test rejects the write before it is applied, with the same error message Tailscale SaaS would return for the same policy. At boot a stored policy whose sshTests no longer pass — for example because a referenced user was deleted while the server was offline — logs a warning and the server keeps running. Fix the policy and reload. This feature is **beta** while behavioural coverage against Tailscale SaaS broadens. [#3263](https://github.com/juanfont/headscale/pull/3263) ###### SSH rule validation SSH rule parsing now trims surrounding whitespace on `action`, `users`, `src`, and `dst`, rejects empty or wildcard entries in `users`, rejects empty `acceptEnv`, and rejects negative `checkPeriod`. `hosts:` aliases are rejected as SSH destinations, non-ASCII tag names are rejected at parse time, and the wording for group-nesting cycles matches Tailscale SaaS. [#3263](https://github.com/juanfont/headscale/pull/3263) ###### Grants We now support [Tailscale grants](https://tailscale.com/docs/features/access-control/grants) alongside ACLs. Grants extend what you can express in a policy beyond packet filtering: the `app` field controls application-level features like Taildrive file sharin _[Truncated at 4000 characters — full notes: https://github.com/juanfont/headscale/releases/tag/v0.29.0-beta.3]_ ### v0.29.0-beta.2 - Date: 2026-05-29 - Version: v0.29.0-beta.2 - Original notes: https://github.com/juanfont/headscale/releases/tag/v0.29.0-beta.2 - Permalink: https://whatsnew.fyi/product/headscale/releases/v0.29.0-beta.2 - Labels: Pre-release - **changed** — Tailscale ACL compatibility improved through extensive test case generation and systematic comparison with official SaaS implementation - **added** — SSH rules with check action are now supported, prompting users to authenticate via OIDC or CLI approval before access is granted - **added** — New headscale auth CLI command group with approve, reject, and register subcommands for managing authentication requests and node registration - **added** — Policy file tests block that asserts reachability between sources and destinations, running on policy writes, SIGHUP reload, and policy check commands - **added** — SSH policy tests block (sshTests) that validates SSH rule reachability with accept, deny, and check user lists - **changed** — SSH rule parsing now trims whitespace on action, users, src, and dst fields, rejects empty or wildcard entries in users, empty acceptEnv, negative checkPeriod, and hosts aliases as SSH destinations - **added** — Support for Tailscale grants alongside ACLs, enabling app-level features like Taildrive file sharing and peer relay control via app and via fields - **added** — autogroup:danger-all that resolves to all IP addresses (0.0.0.0/0 and ::/0) replacing wildcard behavior for matching all IPs - **added** — nodeAttrs block in ACL policies to assign Tailscale node capabilities to nodes matching specified targets - **added** — randomizeClientPort as top-level policy field to set the default for every node - **added** — auto_update.enabled config option to control tailnet-wide default for client auto-update **Minimum supported Tailscale client version: v1.80.0** ###### Tailscale ACL compatibility improvements Extensive test cases were systematically generated using Tailscale clients and the official SaaS to understand how the packet filter should be generated. We discovered a few differences, but overall our implementation was very close. [#3036](https://github.com/juanfont/headscale/pull/3036) ###### SSH check action SSH rules with `"action": "check"` are now supported. When a client initiates a SSH connection to a node with a `check` action policy, the user is prompted to authenticate via OIDC or CLI approval before access is granted. OIDC approval requires the authenticated user to own the source node; tagged source nodes cannot use SSH check-mode. A new `headscale auth` CLI command group supports the approval flow: - `headscale auth approve --auth-id ` approves a pending authentication request (SSH check or web auth) - `headscale auth reject --auth-id ` rejects a pending authentication request - `headscale auth register --auth-id --user ` registers a node (replaces deprecated `headscale nodes register`) [#1850](https://github.com/juanfont/headscale/pull/1850) [#3180](https://github.com/juanfont/headscale/pull/3180) ###### Policy tests (beta) Headscale now evaluates the `tests` block in a policy file. Tests assert reachability between named sources and destinations and cover the whole policy — both `acls` and `grants` rules contribute. They run on user-initiated writes via `headscale policy set`, on SIGHUP reload (`systemctl reload headscale` / `kill -HUP $(pidof headscale)`), and on `headscale policy check`. A failing test rejects the write before it is applied, with the same error message Tailscale SaaS would return for the same policy. At boot a stored policy whose tests no longer pass — for example because a referenced user was deleted while the server was offline — logs a warning and the server keeps running. Fix the policy and reload. This feature is **beta** while behavioural coverage against Tailscale SaaS broadens. [#3229](https://github.com/juanfont/headscale/pull/3229) ###### SSH policy tests (beta) Headscale now evaluates the `sshTests` block in a policy file. Each entry names a source, one or more destination hosts, and three optional user lists: `accept` asserts the listed login users reach every destination via an accept- or check-action SSH rule, `deny` asserts none of them reach any destination, and `check` requires reachability specifically through a check-action rule. Tests run on `headscale policy set`, on SIGHUP reload (`systemctl reload headscale` / `kill -HUP $(pidof headscale)`), and on `headscale policy check`. A failing test rejects the write before it is applied, with the same error message Tailscale SaaS would return for the same policy. At boot a stored policy whose sshTests no longer pass — for example because a referenced user was deleted while the server was offline — logs a warning and the server keeps running. Fix the policy and reload. This feature is **beta** while behavioural coverage against Tailscale SaaS broadens. [#3263](https://github.com/juanfont/headscale/pull/3263) ###### SSH rule validation SSH rule parsing now trims surrounding whitespace on `action`, `users`, `src`, and `dst`, rejects empty or wildcard entries in `users`, rejects empty `acceptEnv`, and rejects negative `checkPeriod`. `hosts:` aliases are rejected as SSH destinations, non-ASCII tag names are rejected at parse time, and the wording for group-nesting cycles matches Tailscale SaaS. [#3263](https://github.com/juanfont/headscale/pull/3263) ###### Grants We now support [Tailscale grants](https://tailscale.com/docs/features/access-control/grants) alongside ACLs. Grants extend what you can express in a policy beyond packet filtering: the `app` field controls application-level features like Taildrive file sharin _[Truncated at 4000 characters — full notes: https://github.com/juanfont/headscale/releases/tag/v0.29.0-beta.2]_ ### v0.29.0-beta.1 - Date: 2026-05-22 - Version: v0.29.0-beta.1 - Original notes: https://github.com/juanfont/headscale/releases/tag/v0.29.0-beta.1 - Permalink: https://whatsnew.fyi/product/headscale/releases/v0.29.0-beta.1 - Labels: Pre-release - **changed** — Minimum supported Tailscale client version is now v1.80.0 - **changed** — Tailscale ACL compatibility improved through extensive test cases generated using Tailscale clients and official SaaS - **added** — SSH rules with check action are now supported, prompting users to authenticate via OIDC or CLI approval before access is granted - **added** — New headscale auth CLI command group with approve, reject, and register subcommands - **added** — Policy tests block in policy files now evaluated to assert reachability between named sources and destinations - **added** — SSH policy tests block (beta) now evaluated with accept, deny, and check assertions for SSH rule testing - **changed** — SSH rule parsing now trims whitespace on action, users, src, and dst fields and validates against empty or wildcard user entries - **added** — Support for Tailscale grants alongside ACLs to control application-level features like Taildrive file sharing and peer relay - **added** — New autogroup:danger-all that resolves to all IP addresses including those outside the tailnet - **added** — Node attributes (nodeAttrs) block in ACL policies to assign Tailscale node capabilities to nodes matching specified targets - **added** — randomizeClientPort as a top-level policy field that toggles the default for every node - **added** — New auto_update.enabled config option to control tailnet-wide default for client auto-update **Minimum supported Tailscale client version: v1.80.0** ###### Tailscale ACL compatibility improvements Extensive test cases were systematically generated using Tailscale clients and the official SaaS to understand how the packet filter should be generated. We discovered a few differences, but overall our implementation was very close. [#3036](https://github.com/juanfont/headscale/pull/3036) ###### SSH check action SSH rules with `"action": "check"` are now supported. When a client initiates a SSH connection to a node with a `check` action policy, the user is prompted to authenticate via OIDC or CLI approval before access is granted. OIDC approval requires the authenticated user to own the source node; tagged source nodes cannot use SSH check-mode. A new `headscale auth` CLI command group supports the approval flow: - `headscale auth approve --auth-id ` approves a pending authentication request (SSH check or web auth) - `headscale auth reject --auth-id ` rejects a pending authentication request - `headscale auth register --auth-id --user ` registers a node (replaces deprecated `headscale nodes register`) [#1850](https://github.com/juanfont/headscale/pull/1850) [#3180](https://github.com/juanfont/headscale/pull/3180) ###### Policy tests (beta) Headscale now evaluates the `tests` block in a policy file. Tests assert reachability between named sources and destinations and cover the whole policy — both `acls` and `grants` rules contribute. They run on user-initiated writes via `headscale policy set`, on SIGHUP reload (`systemctl reload headscale` / `kill -HUP $(pidof headscale)`), and on `headscale policy check`. A failing test rejects the write before it is applied, with the same error message Tailscale SaaS would return for the same policy. At boot a stored policy whose tests no longer pass — for example because a referenced user was deleted while the server was offline — logs a warning and the server keeps running. Fix the policy and reload. This feature is **beta** while behavioural coverage against Tailscale SaaS broadens. [#3229](https://github.com/juanfont/headscale/pull/3229) ###### SSH policy tests (beta) Headscale now evaluates the `sshTests` block in a policy file. Each entry names a source, one or more destination hosts, and three optional user lists: `accept` asserts the listed login users reach every destination via an accept- or check-action SSH rule, `deny` asserts none of them reach any destination, and `check` requires reachability specifically through a check-action rule. Tests run on `headscale policy set`, on SIGHUP reload (`systemctl reload headscale` / `kill -HUP $(pidof headscale)`), and on `headscale policy check`. A failing test rejects the write before it is applied, with the same error message Tailscale SaaS would return for the same policy. At boot a stored policy whose sshTests no longer pass — for example because a referenced user was deleted while the server was offline — logs a warning and the server keeps running. Fix the policy and reload. This feature is **beta** while behavioural coverage against Tailscale SaaS broadens. [#3263](https://github.com/juanfont/headscale/pull/3263) ###### SSH rule validation SSH rule parsing now trims surrounding whitespace on `action`, `users`, `src`, and `dst`, rejects empty or wildcard entries in `users`, rejects empty `acceptEnv`, and rejects negative `checkPeriod`. `hosts:` aliases are rejected as SSH destinations, non-ASCII tag names are rejected at parse time, and the wording for group-nesting cycles matches Tailscale SaaS. [#3263](https://github.com/juanfont/headscale/pull/3263) ###### Grants We now support [Tailscale grants](https://tailscale.com/docs/features/access-control/grants) alongside ACLs. Grants extend what you can express in a policy beyond packet filtering: the `app` field controls application-level features like Taildrive file sharin _[Truncated at 4000 characters — full notes: https://github.com/juanfont/headscale/releases/tag/v0.29.0-beta.1]_ ### v0.28.0 - Date: 2026-02-04 - Version: v0.28.0 - Original notes: https://github.com/juanfont/headscale/releases/tag/v0.28.0 - Permalink: https://whatsnew.fyi/product/headscale/releases/v0.28.0 - **added** — Tags are now implemented following the Tailscale model where tags and user ownership are mutually exclusive, with devices able to be either user-owned or tagged - **added** — User-owned nodes can now request tags during registration using --advertise-tags - **added** — Tagged nodes can return to user-owned by re-authenticating with tailscale up --advertise-tags= --force-reauth - **added** — Add oidc.email_verified_required config option to control email verification requirement - **changed** — The map update system has been rewritten to send smaller, partial updates instead of full network maps to reduce bandwidth usage and improve performance - **changed** — The map update system now properly tracks peer changes and can send removal notifications when nodes are removed due to policy changes - **changed** — Pre-authentication keys now use bcrypt hashing for improved security with keys stored as a prefix and bcrypt hash instead of plaintext - **changed** — Pre-authentication key full key is only displayed once at creation time, with only the prefix shown when listing keys - **changed** — All new pre-authentication keys use the format hskey-auth-{prefix}-{secret} - **changed** — The OIDC callback and device registration web pages have been updated to use the Material for MkDocs design system - **changed** — The Node message in the gRPC/REST API has been simplified with ForcedTags, InvalidTags, and ValidTags fields replaced with a single Tags field - **changed** — PreAuthKey CLI commands now use ID-based operations instead of user and key combinations - **changed** — The gRPC SetTags endpoint now allows converting user-owned nodes to tagged nodes by setting tags - **changed** — Tags are now resolved from the node's stored Tags field only and --advertise-tags is processed during registration, not on every policy evaluation - **removed** — Database migration support removed for databases created before version 0.25.0 - **removed** — The headscale nodes move CLI command has been removed - **removed** — The MoveNode API endpoint has been removed **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](https://tailscale.com/kb/1068/tags) 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. Tagged nodes can return to user-owned by re-authenticating with `tailscale up --advertise-tags= --force-reauth`. A one-time migration will validate and migrate any `RequestTags` (stored in hostinfo) to the tags column. Tags are validated against your policy's `tagOwners` rules during migration. [#3011](https://github.com/juanfont/headscale/pull/3011) ##### 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](https://github.com/juanfont/headscale/pull/2856) [#2961](https://github.com/juanfont/headscale/pull/2961) ##### Pre-authentication key security improvements Pre-authentication keys now use bcrypt hashing for improved security [#2853](https://github.com/juanfont/headscale/pull/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 - **API**: The Node message in the gRPC/REST API has been simplified - the `ForcedTags`, `InvalidTags`, and `ValidTags` fields have been removed and replaced with a single `Tags` field that contains the node's applied tags [#2993](https://github.com/juanfont/headscale/pull/2993) - API clients should use the `Tags` field instead of `ValidTags` - The `headscale nodes list` CLI command now always shows a Tags column and the `--tags` flag has been removed - **PreAuthKey CLI**: Commands now use ID-based operations instead of user+key combinations [#2992](https://github.com/juanfont/headscale/pull/2992) - `headscale preauthkeys create` no longer requires `--user` flag (optional for tracking creation) - `headscale preauthkeys list` lists all keys (no longer filtered by user) - `headscale preauthkeys expire --id ` replaces `--user ` - `headscale preauthkeys delete --id ` replaces `--user ` **Before:** ```bash headscale preauthkeys create --user 1 --reusable --tags tag:server headscale preauthkeys list --user 1 headscale preauthkeys expire --user 1 headscale preauthkeys delete --use _[Truncated at 4000 characters — full notes: https://github.com/juanfont/headscale/releases/tag/v0.28.0]_ ### v0.28.0-beta.2 - Date: 2026-01-22 - Version: v0.28.0-beta.2 - Original notes: https://github.com/juanfont/headscale/releases/tag/v0.28.0-beta.2 - Permalink: https://whatsnew.fyi/product/headscale/releases/v0.28.0-beta.2 - Labels: Pre-release - **added** — Tags are now implemented following the Tailscale model where tags and user ownership are mutually exclusive, with devices able to be authenticated via tagged PreAuthKeys and receiving identity from tags - **added** — User-owned nodes can now request tags during registration using --advertise-tags with validation against tagOwners policy - **added** — Tagged nodes can return to user-owned by re-authenticating with tailscale up --advertise-tags= --force-reauth - **added** — Pre-authentication keys now use bcrypt hashing for improved security with keys stored as prefix and bcrypt hash instead of plaintext - **added** — OIDC callback and device registration web pages have been updated to use the Material for MkDocs design system - **added** — Add oidc.email_verified_required config option to control email verification requirement - **changed** — Map update system has been rewritten to send smaller partial updates instead of full network maps to reduce bandwidth usage and improve performance - **changed** — Map update system now properly tracks peer changes and can send removal notifications when nodes are removed due to policy changes - **changed** — Pre-authentication key CLI commands now use ID-based operations instead of user and key combinations - **changed** — headscale preauthkeys create no longer requires --user flag and is optional for tracking creation - **changed** — headscale preauthkeys list now lists all keys instead of being filtered by user - **changed** — headscale preauthkeys expire and delete commands now use --id parameter instead of --user and key combination - **changed** — gRPC SetTags endpoint now allows converting user-owned nodes to tagged nodes by setting tags - **changed** — Tags are now resolved from the node's stored Tags field only, with --advertise-tags processed during registration rather than on every policy evaluation - **changed** — Full pre-authentication key is only displayed once at creation time, with only the prefix shown when listing keys - **removed** — The ForcedTags, InvalidTags, and ValidTags fields have been removed from the Node message in gRPC and REST API and replaced with a single Tags field - **removed** — The --tags flag has been removed from headscale nodes list CLI command - **removed** — Database migration support removed for pre-0.25.0 databases - **removed** — The headscale nodes move CLI command has been removed - **removed** — The MoveNode API endpoint has been removed **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](https://tailscale.com/kb/1068/tags) 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. Tagged nodes can return to user-owned by re-authenticating with `tailscale up --advertise-tags= --force-reauth`. A one-time migration will validate and migrate any `RequestTags` (stored in hostinfo) to the tags column. Tags are validated against your policy's `tagOwners` rules during migration. [#3011](https://github.com/juanfont/headscale/pull/3011) ###### 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](https://github.com/juanfont/headscale/pull/2856) [#2961](https://github.com/juanfont/headscale/pull/2961) ###### Pre-authentication key security improvements Pre-authentication keys now use bcrypt hashing for improved security [#2853](https://github.com/juanfont/headscale/pull/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 - **API**: The Node message in the gRPC/REST API has been simplified - the `ForcedTags`, `InvalidTags`, and `ValidTags` fields have been removed and replaced with a single `Tags` field that contains the node's applied tags [#2993](https://github.com/juanfont/headscale/pull/2993) - API clients should use the `Tags` field instead of `ValidTags` - The `headscale nodes list` CLI command now always shows a Tags column and the `--tags` flag has been removed - **PreAuthKey CLI**: Commands now use ID-based operations instead of user+key combinations [#2992](https://github.com/juanfont/headscale/pull/2992) - `headscale preauthkeys create` no longer requires `--user` flag (optional for tracking creation) - `headscale preauthkeys list` lists all keys (no longer filtered by user) - `headscale preauthkeys expire --id ` replaces `--user ` - `headscale preauthkeys delete --id ` replaces `--user ` **Before:** ```bash headscale preauthkeys create --user 1 --reusable --tags tag:server headscale preauthkeys list --user 1 headscale preauthkeys expire --user 1 headscale preauthkeys delete _[Truncated at 4000 characters — full notes: https://github.com/juanfont/headscale/releases/tag/v0.28.0-beta.2]_