juanfont Release Notes
8 release notes curated from 1 source by the Releasebot Team. Last updated: Aug 7, 2026
juanfont Products
- Jul 29, 2026
- Date parsed from source:Jul 29, 2026
- First seen by Releasebot:Aug 7, 2026
v0.29.3
headscale fixes tagged node re-authentication, registration, and reconnect issues, improving auth tag handling, ephemeral node cleanup, and follow-up registration security. It also raises the minimum supported Tailscale client version to v1.80.0.
Minimum supported Tailscale client version
v1.80.0
Changes
- Fix tagged node stuck expired after tailscale logout, unable to re-authenticate #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
- Fix re-authenticating an already-tagged node with --advertise-tags being rejected when the authenticating user owns the tags #3394
- Fix ephemeral nodes lingering as disconnected after reconnect churn #3383
- Fix node registration falsely returning 401 registration timed out when auth completes as the request context expires #3392
- Check the machine key on the followup registration poll so a leaked auth ID cannot return the registering user's identity #3393
- Reject /key requests below the supported capability version floor, matching /ts2021 #3391
Upgrade
Please follow the steps outlined in the upgrade guide to update your existing Headscale installation.
Changelog
- 235a57e CHANGELOG: add 0.29.3
- 089d6c4 Explicitly select lunr as search provider
- fba84ca auth: check machine key on the followup registration path
- 1292841 build: bump Go toolchain to 1.26.5
- 9609a0b hscontrol: gate /key on supported capability version
- bdc3e99 hscontrol: prefer completed auth over expired ctx in followup wait
- 5aff68b mkdocs: bump version
- 4a1e773 policy,state: authorize reauth tags against the authenticating user
- 5fb514e poll: do not cancel ephemeral GC until Connect succeeds
- 1fccdb1 state: apply a new pre-auth key's tags on re-registration
- d202883 state: do not expire tagged nodes on logout
- Jul 1, 2026
- Date parsed from source:Jul 1, 2026
- First seen by Releasebot:Aug 7, 2026
v0.29.2
headscale fixes map delivery and WebSocket connectivity, including safer handling of invalid node FQDNs, concurrent policy reads during reconnect storms, and /ts2021 GET upgrades for Tailscale JS/WASM control clients.
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
- Fix /ts2021 rejecting the WebSocket GET upgrade with 405, which prevented Tailscale JS/WASM control clients from connecting #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
Upgrade
Please follow the steps outlined in the upgrade guide to update your existing Headscale installation.
Changelog
8eea894 CHANGELOG: drop unreleased 0.30.0 stub
Original source
f708c5b CHANGELOG: note /ts2021 WebSocket GET fix
735742e CHANGELOG: note 0.29.2 invalid-name map fix
3ac33cf CHANGELOG: shorten 0.29.2 invalid-name entry, set date
f885d87 Fix invalid ip syntax
1ec7b7f hscontrol: register /ts2021 for WebSocket GET
8f4e69d integration: add TS2021 WebSocket tests to CI matrix
e7851ef integration: test /ts2021 WebSocket GET with a real WASM client
9d13274 mapper,policy: add reconnect-storm and lock-concurrency regression tests
ec67197 mapper: skip peers with invalid names instead of failing the map
d4f2acf policy: take RLock for reads so map generation runs concurrently
5fb76eb poll: return an HTTP error on long-poll setup failure
fd154fd state: log nodes with map-breaking data at startup
9c9206a state: reject renames whose FQDN exceeds the hostname limit All of your release notes in one feed
Join Releasebot and get updates from juanfont and hundreds of other software products.
- Jun 18, 2026
- Date parsed from source:Jun 18, 2026
- First seen by Releasebot:Aug 7, 2026
v0.29.1
headscale fixes tagged node user assignment loss and raises the minimum supported Tailscale client version to 1.80.0
Minimum supported Tailscale client version: v1.80.0
Changes
- Fix nodes with tags='null' losing their assigned user on upgrade #3325
Upgrade
Please follow the steps outlined in the upgrade guide to update your existing Headscale installation.
Changelog
636f660 db: preserve user_id on untagged nodes with tags='null'
Original source - Jun 17, 2026
- Date parsed from source:Jun 17, 2026
- First seen by Releasebot:Aug 7, 2026
v0.29.0
headscale adds major policy, SSH, and grants updates, with beta policy tests, SSH check approval flows, Taildrive and nodeAttrs support, stricter ACL validation, hostname handling aligned with Tailscale, and HA subnet router health probing, plus upgrade path and config changes.
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
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 <id> approves a pending authentication request (SSH check or web auth)
headscale auth reject --auth-id <id> rejects a pending authentication request
headscale auth register --auth-id <id> --user <user> registers a node (replaces deprecated headscale nodes register)#1850
#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
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
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
Grants
We now support Tailscale 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 sharing and peer relay, and the via
field steers traffic through specific tagged subnet routers or exit nodes. The ip field works like
an ACL rule. Grants can be mixed with ACLs in the same policy file.#2180
As part of this, we added autogroup:danger-all. It resolves to 0.0.0.0/0 and ::/0, all IP
addresses, including those outside the tailnet. This replaces the old behaviour where * matched
all IPs (see BREAKING below). The name is intentional: accepting traffic from the entire
internet is a security-sensitive choice. autogroup:danger-all can only be used as a source.Node attributes (nodeAttrs)
ACL policies now accept a nodeAttrs block. Each entry hands a list of
Tailscale node capabilities to every node matching target. The accepted
target forms are the same as acls.src and grants.src: users, groups,
tags, hosts, prefixes, autogroup:member, autogroup:tagged, and *.{ "randomizeClientPort": true, "nodeAttrs": [ { "target": ["autogroup:tagged"], "attr": ["disable-captive-portal-detection"] }, { "target": ["[email protected]"], "attr": ["nextdns:abc123"] }, ], }Frequently requested capabilities this unlocks include magicdns-aaaa,
disable-relay-server, disable-captive-portal-detection,
nextdns:<profile> / nextdns:no-device-info, randomize-client-port,
and the Taildrive drive:share / drive:access pair. The set is not
limited to these, any string-only cap an operator places in policy
reaches clients unchanged.randomizeClientPort also lands as a top-level policy field that toggles
the default for every node, replacing the old server-config knob.A new auto_update.enabled config option controls the tailnet-wide
default for client auto-update. When true, every node's CapMap carries
default-auto-update: [true] so fresh clients pick up the default
unless they make a local opt-in / opt-out choice.Policies that use the funnel cap, ipPool blocks, or
autogroup:admin / autogroup:owner targets are rejected at load —
those features depend on machinery headscale does not yet ship.#3251
Taildrive
Taildrive (file-sync between
nodes) is now
configurable through policy. Grant drive:share to the node that
hosts files and drive:access to nodes that read or write them; pair
with a tailscale.com/cap/drive grant to set the per-share access
mode:{ "nodeAttrs": [ { "target": ["tag:fileserver"], "attr": ["drive:share"] }, { "target": ["autogroup:member"], "attr": ["drive:access"] }, ], "grants": [ { "src": ["autogroup:member"], "dst": ["tag:fileserver"], "app": { "tailscale.com/cap/drive": [{ "shares": ["*"], "access": "rw" }], }, }, ], }A wildcard nodeAttrs ("target": ["*"]) hands the caps to every
node when fine-grained control is not needed.Hostname sanitisation
Hostnames are now santised using Tailscales magicdns sanitisation rules, matching Tailscale SaaS behavior. This means that hostnames with non-ASCII characters, special characters, or reserved DNS label characters are now transformed into valid DNS labels for MagicDNS. This improves our previously too strict sanitisation that rejected hostnames based on our guesswork and not based on the Tailscale upstream behaviour.
Examples that previously regressed and now work:
Input
Raw (Hostname)
DNS label (GivenName)
Joe's Mac mini
Joe's Mac mini
joes-mac-mini
Yuri's MacBook Pro
Yuri's MacBook Pro
yuris-macbook-pro
Test@Host
Test@Host
test-host
mail.server
mail.server
mail-server
My-PC!
My-PC!
my-pc
我的电脑
我的电脑
node#3202
HA subnet router health probing
Headscale now actively probes HA subnet routers to detect nodes that are connected but not
forwarding traffic. The control plane periodically pings HA subnet routers via the Noise
control channel and fails over to a healthy standby if the primary stops responding. This is
enabled by default (node.routes.ha.probe_interval: 10s, probe_timeout: 5s) and only
active when HA routes exist (2+ nodes advertising the same prefix). Set probe_interval to
0 to disable. This complements the existing disconnect-based failover, catching "zombie
connected" routers that maintain their control session but cannot route packets.#3194
BREAKING
Hostname handling
The GivenName collision policy changed from an 8-char random hash suffix (laptop-abc12xyz) to a monotonic numeric suffix (laptop, laptop-1, laptop-2, …), matching Tailscale SaaS. Empty / all-non-ASCII hostnames now fall back to the literal node instead of invalid-<rand>. MagicDNS names change on upgrade for any node whose previous label was a random-suffix form; the raw Hostname column is unchanged. #3202
ACL Policy
Wildcard (*) in ACL sources and destinations now resolves to Tailscale's CGNAT range (100.64.0.0/10) and ULA range (fd7a:115c:a1e0::/48) instead of all IPs (0.0.0.0/0 and ::/0) #3036
This better matches Tailscale's security model where * means "any node in the tailnet" rather than "any IP address"
Policies that need to match all IP addresses including non-Tailscale IPs should use autogroup:danger-all as a source, or explicit CIDR ranges as destinations #2180
autogroup:danger-all can only be used as a source; it cannot be used as a destination
Note: Users with non-standard IP ranges configured in prefixes.ipv4 or prefixes.ipv6 (which is unsupported and produces a warning) will need to explicitly specify their CIDR ranges in ACL rules instead of using *
Validate autogroup:self source restrictions matching Tailscale behavior - tags, hosts, and IPs are rejected as sources for autogroup:self destinations #3036
Policies using tags, hosts, or IP addresses as sources for autogroup:self destinations will now fail validation
The proto:icmp protocol name now only includes ICMPv4 (protocol 1), matching Tailscale behavior #3036
Previously, proto:icmp included both ICMPv4 and ICMPv6
Use proto:ipv6-icmp or protocol number 58 explicitly for ICMPv6
Upgrade Path
Headscale now enforces a strict version upgrade path #3083
Skipping minor versions (e.g. 0.27 → 0.29) is blocked; upgrade one minor version at a time
Downgrading to a previous minor version is blocked
Patch version changes within the same minor are always allowed
Configuration
The randomize_client_port server-config key was removed; the
toggle now lives in the policy file as a top-level
randomizeClientPort field, matching the Tailscale-hosted schema. #3251Headscale refuses to start when the old key is set. Move it to the
policy file referenced by policy.path:{ "randomizeClientPort": true, }If you do not have a policy file yet, create one with that minimal
content and point policy.path at it. The default carries over —
empty / absent policy means randomizeClientPort: false, matching
the previous behaviour for operators who never set the key. Per-node
opt-in via nodeAttrs is also supported and stacks on top of the
global default.CLI
headscale nodes register is deprecated in favour of headscale auth register --auth-id <id> --user <user> #1850
The old command continues to work but will be removed in a future release
Changes
ACL Policy
- Fix subnet-to-subnet peer visibility — subnet routers now correctly become peers when ACL rules reference only subnet CIDRs as sources, without requiring node IP rules #3175
- Fix filter rule reduction to use only approved subnet routes instead of all advertised routes, matching Tailscale SaaS behavior #3175
- Add ICMP and IPv6-ICMP protocols to default filter rules when no protocol is specified #3036
- Fix autogroup:self handling for tagged nodes - tagged nodes no longer incorrectly receive autogroup:self filter rules #3036
- Use CIDR format for autogroup:self destination IPs matching Tailscale behavior #3036
- Merge filter rules with identical SrcIPs and IPProto matching Tailscale behavior - multiple ACL rules with the same source now produce a single FilterRule with combined DstPorts #3036
- Fix exit nodes incorrectly receiving filter rules for destinations that only overlap via exit routes #3169 #3175
- Fix address-based aliases (hosts, raw IPs) incorrectly expanding to include the matching node's other address family #2180
- Fix identity-based aliases (tags, users, groups) resolving to IPv4 only; they now include both IPv4 and IPv6 matching Tailscale behavior #2180
- Fix wildcard (*) source in ACLs now using actually-approved subnet routes instead of autoApprover policy prefixes #2180
- Fix non-wildcard source IPs being dropped when combined with wildcard * in the same ACL rule #2180
- Fix exit node approval not triggering filter rule recalculation for peers #2180
- Policy validation error messages now include field context (e.g., src=, dst=) and are more descriptive #2180
- Reject policies whose user@ tokens match multiple DB users; rename the duplicate via headscale users rename to load #3160
- Evaluate the policy tests block on user-initiated writes across both acls and grants; reject policies whose tests fail (beta) #1803
Grants
- Add support for policy grants with ip, app, and via fields #2180
- Add autogroup:danger-all as a source-only autogroup resolving to all IP addresses #2180
- Add capability grants for Taildrive (cap/drive) and peer relay (cap/relay) with automatic companion capabilities #2180
- Add per-viewer via route steering — grants with via tags control which subnet router or exit node handles traffic for each group of viewers #2180
- Enable Taildrive node attributes on all nodes; actual access is controlled by cap/drive grants #2180
SSH Policy
- Add support for localpart:*@<domain> in SSH rule users field, mapping each matching user's email local-part as their OS username #3091
- Add SSH check action support with OIDC and CLI-based approval flows #1850
CLI
- Add headscale auth register, headscale auth approve, and headscale auth reject CLI commands #1850
- Deprecate headscale nodes register --key in favour of headscale auth register --auth-id #1850
- headscale policy check --bypass-grpc-and-access-database-directly validates user@ tokens against the live user database #3160
- Remove deprecated --namespace flag from nodes list, nodes register, and debug create-node commands (use --user instead) #3093
- Remove deprecated namespace/ns command aliases for users and machine/machines aliases for nodes #3093
- Fix DestroyUser deleting all pre-auth keys in the database instead of only the target user's keys #3155
- headscale policy check evaluates the tests block when invoked with --bypass-grpc-and-access-database-directly; without the flag it warns instead of running the tests against empty data #1803
API
- Add auth related routes. The auth/register endpoint now expects data as JSON #1850
- Remove gRPC reflection from the remote (TCP) server #3180
OIDC
- Add a confirmation page before completing node registration, showing the device hostname and machine key fingerprint #3180
- Generalise auth templates into reusable AuthSuccess and AuthWeb components #1850
- Unify auth pipeline with AuthVerdict type, supporting registration, reauthentication, and SSH checks #1850
Configuration
- Add node.expiry configuration option to set a default node key expiry for nodes registered via auth key #3122
- Tagged nodes (registered with tagged pre-auth keys) are exempt from default expiry
- oidc.expiry has been removed; use node.expiry instead (applies to all registration methods including OIDC)
- ephemeral_node_inactivity_timeout is deprecated in favour of node.ephemeral.inactivity_timeout
- Add trusted_proxies to gate True-Client-IP / X-Real-IP / X-Forwarded-For (previously honoured from any client) #3268
Debug
- Add node connectivity ping page for verifying control-plane reachability #3183
- Omit secret fields (Pass, ClientSecret, APIKey) from /debug/config JSON output #3180
- Route statsviz through tsweb.Protected #3180
Other
- Remove old migrations for the debian package #3185
- Install config-example.yaml as example for the debian package #3186
- Fix user-owned re-registration with zero client expiry and no default storing 0001-01-01 00:00:00 in the database instead of NULL #3199
- Fix tailscaled restart on a node with no expiry resetting NULL to 0001-01-01 00:00:00 in the database, affecting both tagged and untagged nodes #3197
- Backfill nodes.expiry rows persisted by older versions as 0001-01-01 00:00:00 to NULL, so nodes upgraded from <0.28 stop reporting as expired #3284
- Update reverse proxy documentation for trusted_proxies configuration option #3292
Upgrade
Please follow the steps outlined in the upgrade guide to update your existing Headscale installation.
Changelog
b0c221f changelog: set 0.29 date
Original source - Jun 15, 2026
- Date parsed from source:Jun 15, 2026
- First seen by Releasebot:Aug 7, 2026
v0.29.0-beta.4
headscale releases a major update with SSH check approvals, policy and SSH tests, grants and Taildrive support, tighter ACL validation, improved hostname handling, HA subnet router probing, and stricter upgrade and config controls to better match Tailscale behavior.
Minimum supported Tailscale client version: v1.80.0
Original source
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
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 <id> approves a pending authentication request (SSH check or web auth)
headscale auth reject --auth-id <id> rejects a pending authentication request
headscale auth register --auth-id <id> --user <user> registers a node (replaces deprecated headscale nodes register)
#1850
#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
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
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
Grants
We now support Tailscale 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 sharing and peer relay, and the via
field steers traffic through specific tagged subnet routers or exit nodes. The ip field works like
an ACL rule. Grants can be mixed with ACLs in the same policy file.
#2180
As part of this, we added autogroup:danger-all. It resolves to 0.0.0.0/0 and ::/0, all IP
addresses, including those outside the tailnet. This replaces the old behaviour where * matched
all IPs (see BREAKING below). The name is intentional: accepting traffic from the entire
internet is a security-sensitive choice. autogroup:danger-all can only be used as a source.
Node attributes (nodeAttrs)
ACL policies now accept a nodeAttrs block. Each entry hands a list of
Tailscale node capabilities to every node matching target. The accepted
target forms are the same as acls.src and grants.src: users, groups,
tags, hosts, prefixes, autogroup:member, autogroup:tagged, and .
{
"randomizeClientPort": true,
"nodeAttrs": [
{ "target": ["autogroup:tagged"], "attr": ["disable-captive-portal-detection"] },
{ "target": ["[email protected]"], "attr": ["nextdns:abc123"] },
],
}
Frequently requested capabilities this unlocks include magicdns-aaaa,
disable-relay-server, disable-captive-portal-detection,
nextdns:<profile> / nextdns:no-device-info, randomize-client-port,
and the Taildrive drive:share / drive:access pair. The set is not
limited to these, any string-only cap an operator places in policy
reaches clients unchanged.
randomizeClientPort also lands as a top-level policy field that toggles
the default for every node, replacing the old server-config knob.
A new auto_update.enabled config option controls the tailnet-wide
default for client auto-update. When true, every node's CapMap carries
default-auto-update: [true] so fresh clients pick up the default
unless they make a local opt-in / opt-out choice.
Policies that use the funnel cap, ipPool blocks, or
autogroup:admin / autogroup:owner targets are rejected at load —
those features depend on machinery headscale does not yet ship.
#3251
Taildrive
Taildrive (file-sync between
nodes) is now
configurable through policy. Grant drive:share to the node that
hosts files and drive:access to nodes that read or write them; pair
with a tailscale.com/cap/drive grant to set the per-share access
mode:
{
"nodeAttrs": [
{ "target": ["tag:fileserver"], "attr": ["drive:share"] },
{ "target": ["autogroup:member"], "attr": ["drive:access"] },
],
"grants": [
{
"src": ["autogroup:member"],
"dst": ["tag:fileserver"],
"app": {
"tailscale.com/cap/drive": [{ "shares": [""], "access": "rw" }],
},
},
],
}
A wildcard nodeAttrs ("target": [""]) hands the caps to every
node when fine-grained control is not needed.
Hostname sanitisation
Hostnames are now santised using Tailscales magicdns sanitisation rules, matching Tailscale SaaS behavior. This means that hostnames with non-ASCII characters, special characters, or reserved DNS label characters are now transformed into valid DNS labels for MagicDNS. This improves our previously too strict sanitisation that rejected hostnames based on our guesswork and not based on the Tailscale upstream behaviour.
Examples that previously regressed and now work:
Input
Raw (Hostname)
DNS label (GivenName)
Joe's Mac mini
Joe's Mac mini
joes-mac-mini
Yuri's MacBook Pro
Yuri's MacBook Pro
yuris-macbook-pro
Test@Host
Test@Host
test-host
mail.server
mail.server
mail-server
My-PC!
My-PC!
my-pc
我的电脑
我的电脑
node
#3202
HA subnet router health probing
Headscale now actively probes HA subnet routers to detect nodes that are connected but not
forwarding traffic. The control plane periodically pings HA subnet routers via the Noise
control channel and fails over to a healthy standby if the primary stops responding. This is
enabled by default (node.routes.ha.probe_interval: 10s, probe_timeout: 5s) and only
active when HA routes exist (2+ nodes advertising the same prefix). Set probe_interval to
0 to disable. This complements the existing disconnect-based failover, catching "zombie
connected" routers that maintain their control session but cannot route packets.
#3194
BREAKING
Hostname handling
The GivenName collision policy changed from an 8-char random hash suffix (laptop-abc12xyz) to a monotonic numeric suffix (laptop, laptop-1, laptop-2, …), matching Tailscale SaaS. Empty / all-non-ASCII hostnames now fall back to the literal node instead of invalid-<rand>. MagicDNS names change on upgrade for any node whose previous label was a random-suffix form; the raw Hostname column is unchanged. #3202
ACL Policy
Wildcard () in ACL sources and destinations now resolves to Tailscale's CGNAT range (100.64.0.0/10) and ULA range (fd7a:115c:a1e0::/48) instead of all IPs (0.0.0.0/0 and ::/0) #3036
This better matches Tailscale's security model where * means "any node in the tailnet" rather than "any IP address"
Policies that need to match all IP addresses including non-Tailscale IPs should use autogroup:danger-all as a source, or explicit CIDR ranges as destinations #2180
autogroup:danger-all can only be used as a source; it cannot be used as a destination
Note: Users with non-standard IP ranges configured in prefixes.ipv4 or prefixes.ipv6 (which is unsupported and produces a warning) will need to explicitly specify their CIDR ranges in ACL rules instead of using *
Validate autogroup:self source restrictions matching Tailscale behavior - tags, hosts, and IPs are rejected as sources for autogroup:self destinations #3036
Policies using tags, hosts, or IP addresses as sources for autogroup:self destinations will now fail validation
The proto:icmp protocol name now only includes ICMPv4 (protocol 1), matching Tailscale behavior #3036
Previously, proto:icmp included both ICMPv4 and ICMPv6
Use proto:ipv6-icmp or protocol number 58 explicitly for ICMPv6
Upgrade Path
Headscale now enforces a strict version upgrade path #3083
Skipping minor versions (e.g. 0.27 → 0.29) is blocked; upgrade one minor version at a time
Downgrading to a previous minor version is blocked
Patch version changes within the same minor are always allowed
Configuration
The randomize_client_port server-config key was removed; the
toggle now lives in the policy file as a top-level
randomizeClientPort field, matching the Tailscale-hosted schema. #3251
Headscale refuses to start when the old key is set. Move it to the
policy file referenced by policy.path:
{
"randomizeClientPort": true,
}
If you do not have a policy file yet, create one with that minimal
content and point policy.path at it. The default carries over —
empty / absent policy means randomizeClientPort: false, matching
the previous behaviour for operators who never set the key. Per-node
opt-in via nodeAttrs is also supported and stacks on top of the
global default.
CLI
headscale nodes register is deprecated in favour of headscale auth register --auth-id <id> --user <user> #1850
The old command continues to work but will be removed in a future release
Changes
ACL Policy
Fix subnet-to-subnet peer visibility — subnet routers now correctly become peers when ACL rules reference only subnet CIDRs as sources, without requiring node IP rules #3175
Fix filter rule reduction to use only approved subnet routes instead of all advertised routes, matching Tailscale SaaS behavior #3175
Add ICMP and IPv6-ICMP protocols to default filter rules when no protocol is specified #3036
Fix autogroup:self handling for tagged nodes - tagged nodes no longer incorrectly receive autogroup:self filter rules #3036
Use CIDR format for autogroup:self destination IPs matching Tailscale behavior #3036
Merge filter rules with identical SrcIPs and IPProto matching Tailscale behavior - multiple ACL rules with the same source now produce a single FilterRule with combined DstPorts #3036
Fix exit nodes incorrectly receiving filter rules for destinations that only overlap via exit routes #3169 #3175
Fix address-based aliases (hosts, raw IPs) incorrectly expanding to include the matching node's other address family #2180
Fix identity-based aliases (tags, users, groups) resolving to IPv4 only; they now include both IPv4 and IPv6 matching Tailscale behavior #2180
Fix wildcard () source in ACLs now using actually-approved subnet routes instead of autoApprover policy prefixes #2180
Fix non-wildcard source IPs being dropped when combined with wildcard * in the same ACL rule #2180
Fix exit node approval not triggering filter rule recalculation for peers #2180
Policy validation error messages now include field context (e.g., src=, dst=) and are more descriptive #2180
Reject policies whose user@ tokens match multiple DB users; rename the duplicate via headscale users rename to load #3160
Evaluate the policy tests block on user-initiated writes across both acls and grants; reject policies whose tests fail (beta) #1803
Grants
Add support for policy grants with ip, app, and via fields #2180
Add autogroup:danger-all as a source-only autogroup resolving to all IP addresses #2180
Add capability grants for Taildrive (cap/drive) and peer relay (cap/relay) with automatic companion capabilities #2180
Add per-viewer via route steering — grants with via tags control which subnet router or exit node handles traffic for each group of viewers #2180
Enable Taildrive node attributes on all nodes; actual access is controlled by cap/drive grants #2180
SSH Policy
Add support for localpart:@<domain> in SSH rule users field, mapping each matching user's email local-part as their OS username #3091
Add SSH check action support with OIDC and CLI-based approval flows #1850
CLI
Add headscale auth register, headscale auth approve, and headscale auth reject CLI commands #1850
Deprecate headscale nodes register --key in favour of headscale auth register --auth-id #1850
headscale policy check --bypass-grpc-and-access-database-directly validates user@ tokens against the live user database #3160
Remove deprecated --namespace flag from nodes list, nodes register, and debug create-node commands (use --user instead) #3093
Remove deprecated namespace/ns command aliases for users and machine/machines aliases for nodes #3093
Fix DestroyUser deleting all pre-auth keys in the database instead of only the target user's keys #3155
headscale policy check evaluates the tests block when invoked with --bypass-grpc-and-access-database-directly; without the flag it warns instead of running the tests against empty data #1803
API
Add auth related routes. The auth/register endpoint now expects data as JSON #1850
Remove gRPC reflection from the remote (TCP) server #3180
OIDC
Add a confirmation page before completing node registration, showing the device hostname and machine key fingerprint #3180
Generalise auth templates into reusable AuthSuccess and AuthWeb components #1850
Unify auth pipeline with AuthVerdict type, supporting registration, reauthentication, and SSH checks #1850
Configuration
Add node.expiry configuration option to set a default node key expiry for nodes registered via auth key #3122
Tagged nodes (registered with tagged pre-auth keys) are exempt from default expiry
oidc.expiry has been removed; use node.expiry instead (applies to all registration methods including OIDC)
ephemeral_node_inactivity_timeout is deprecated in favour of node.ephemeral.inactivity_timeout
Add trusted_proxies to gate True-Client-IP / X-Real-IP / X-Forwarded-For (previously honoured from any client) #3268
Debug
Add node connectivity ping page for verifying control-plane reachability #3183
Omit secret fields (Pass, ClientSecret, APIKey) from /debug/config JSON output #3180
Route statsviz through tsweb.Protected #3180
Other
Remove old migrations for the debian package #3185
Install config-example.yaml as example for the debian package #3186
Fix user-owned re-registration with zero client expiry and no default storing 0001-01-01 00:00:00 in the database instead of NULL #3199
Fix tailscaled restart on a node with no expiry resetting NULL to 0001-01-01 00:00:00 in the database, affecting both tagged and untagged nodes #3197
Backfill nodes.expiry rows persisted by older versions as 0001-01-01 00:00:00 to NULL, so nodes upgraded from <0.28 stop reporting as expired #3284
Update reverse proxy documentation for trusted_proxies configuration option #3292
Upgrade
Please follow the steps outlined in the upgrade guide to update your existing Headscale installation.
Changelog
e759d9f auth: re-validate key when an expired node re-registers
68a6d3c db: drop ambiguous machine-key getter, match precisely in test helper
9b89497 db: treat unknown pre-auth key as not found
a1d3e98 state: allow key expiry to be set on tagged nodes
fd08b8f state: make any-user machine-key lookup deterministic
a5ef3af state: patch relogins and gate endpoint broadcasts
0961e79 state: re-register converted tagged nodes with reused key
a73d38b state: reject re-registration claiming another node's key
1689478 state: return all nodes for a machine key, reject ambiguous ownership
96d2e6e state: roll back node store when re-registration write fails
b83bf3f state: serialise registration per machine key
bff216a state: update node in place on pre-auth-key re-registration
4da0692 types/change: add NodeKeyRotated for relogin peer patch Similar to juanfont with recent updates:
- Smokeball release notes144 release notes · Latest Sep 4, 2026
- Cosmolex release notes20 release notes · Latest Jul 30, 2025
- PracticePanther release notes36 release notes · Latest Aug 11, 2026
- Salesforce release notes71 release notes · Latest Sep 1, 2026
- Microsoft release notes820 release notes · Latest Sep 4, 2026
- Zoom release notes210 release notes · Latest Aug 31, 2026
- Jun 11, 2026
- Date parsed from source:Jun 11, 2026
- First seen by Releasebot:Aug 7, 2026
v0.29.0-beta.3
headscale ships a major release with Tailscale ACL and SSH compatibility improvements, policy tests in beta, grants and Taildrive support, node attributes, safer hostname handling, HA subnet router probing, stricter upgrades, and new auth and policy CLI flows.
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
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 <id> approves a pending authentication request (SSH check or web auth)
headscale auth reject --auth-id <id> rejects a pending authentication request
headscale auth register --auth-id <id> --user <user> registers a node (replaces deprecated headscale nodes register)#1850
#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
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
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
Grants
We now support Tailscale 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 sharing and peer relay, and the via field steers traffic through specific tagged subnet routers or exit nodes. The ip field works like an ACL rule. Grants can be mixed with ACLs in the same policy file.
#2180
As part of this, we added autogroup:danger-all. It resolves to 0.0.0.0/0 and ::/0, all IP addresses, including those outside the tailnet. This replaces the old behaviour where * matched all IPs (see BREAKING below). The name is intentional: accepting traffic from the entire internet is a security-sensitive choice. autogroup:danger-all can only be used as a source.
Node attributes (nodeAttrs)
ACL policies now accept a nodeAttrs block. Each entry hands a list of Tailscale node capabilities to every node matching target. The accepted target forms are the same as acls.src and grants.src: users, groups, tags, hosts, prefixes, autogroup:member, autogroup:tagged, and *.
{ "randomizeClientPort": true, "nodeAttrs": [ { "target": ["autogroup:tagged"], "attr": ["disable-captive-portal-detection"] }, { "target": ["[email protected]"], "attr": ["nextdns:abc123"] }, ], }Frequently requested capabilities this unlocks include magicdns-aaaa, disable-relay-server, disable-captive-portal-detection, nextdns:<profile> / nextdns:no-device-info, randomize-client-port, and the Taildrive drive:share / drive:access pair. The set is not limited to these, any string-only cap an operator places in policy reaches clients unchanged.
randomizeClientPort also lands as a top-level policy field that toggles the default for every node, replacing the old server-config knob.
A new auto_update.enabled config option controls the tailnet-wide default for client auto-update. When true, every node's CapMap carries default-auto-update: [true] so fresh clients pick up the default unless they make a local opt-in / opt-out choice.
Policies that use the funnel cap, ipPool blocks, or autogroup:admin / autogroup:owner targets are rejected at load — those features depend on machinery headscale does not yet ship.
#3251
Taildrive
Taildrive (file-sync between nodes) is now configurable through policy. Grant drive:share to the node that hosts files and drive:access to nodes that read or write them; pair with a tailscale.com/cap/drive grant to set the per-share access mode:
{ "nodeAttrs": [ { "target": ["tag:fileserver"], "attr": ["drive:share"] }, { "target": ["autogroup:member"], "attr": ["drive:access"] }, ], "grants": [ { "src": ["autogroup:member"], "dst": ["tag:fileserver"], "app": { "tailscale.com/cap/drive": [{ "shares": ["*"], "access": "rw" }], }, }, ], }A wildcard nodeAttrs ("target": ["*"]) hands the caps to every node when fine-grained control is not needed.
Hostname sanitisation
Hostnames are now santised using Tailscales magicdns sanitisation rules, matching Tailscale SaaS behavior. This means that hostnames with non-ASCII characters, special characters, or reserved DNS label characters are now transformed into valid DNS labels for MagicDNS. This improves our previously too strict sanitisation that rejected hostnames based on our guesswork and not based on the Tailscale upstream behaviour.
Examples that previously regressed and now work:
Input
Raw (Hostname)
DNS label (GivenName)
Joe's Mac mini
Joe's Mac mini
joes-mac-mini
Yuri's MacBook Pro
Yuri's MacBook Pro
yuris-macbook-pro
Test@Host
Test@Host
test-host
mail.server
mail.server
mail-server
My-PC!
My-PC!
my-pc
我的电脑
我的电脑
node#3202
HA subnet router health probing
Headscale now actively probes HA subnet routers to detect nodes that are connected but not forwarding traffic. The control plane periodically pings HA subnet routers via the Noise control channel and fails over to a healthy standby if the primary stops responding. This is enabled by default (node.routes.ha.probe_interval: 10s, probe_timeout: 5s) and only active when HA routes exist (2+ nodes advertising the same prefix). Set probe_interval to 0 to disable. This complements the existing disconnect-based failover, catching "zombie connected" routers that maintain their control session but cannot route packets.
#3194
BREAKING
Hostname handling
The GivenName collision policy changed from an 8-char random hash suffix (laptop-abc12xyz) to a monotonic numeric suffix (laptop, laptop-1, laptop-2, …), matching Tailscale SaaS. Empty / all-non-ASCII hostnames now fall back to the literal node instead of invalid-<rand>. MagicDNS names change on upgrade for any node whose previous label was a random-suffix form; the raw Hostname column is unchanged. #3202
ACL Policy
Wildcard (*) in ACL sources and destinations now resolves to Tailscale's CGNAT range (100.64.0.0/10) and ULA range (fd7a:115c:a1e0::/48) instead of all IPs (0.0.0.0/0 and ::/0) #3036
This better matches Tailscale's security model where * means "any node in the tailnet" rather than "any IP address"
Policies that need to match all IP addresses including non-Tailscale IPs should use autogroup:danger-all as a source, or explicit CIDR ranges as destinations #2180
autogroup:danger-all can only be used as a source; it cannot be used as a destination
Note: Users with non-standard IP ranges configured in prefixes.ipv4 or prefixes.ipv6 (which is unsupported and produces a warning) will need to explicitly specify their CIDR ranges in ACL rules instead of using *
Validate autogroup:self source restrictions matching Tailscale behavior - tags, hosts, and IPs are rejected as sources for autogroup:self destinations #3036
Policies using tags, hosts, or IP addresses as sources for autogroup:self destinations will now fail validation
The proto:icmp protocol name now only includes ICMPv4 (protocol 1), matching Tailscale behavior #3036
Previously, proto:icmp included both ICMPv4 and ICMPv6
Use proto:ipv6-icmp or protocol number 58 explicitly for ICMPv6
Upgrade Path
Headscale now enforces a strict version upgrade path #3083
Skipping minor versions (e.g. 0.27 → 0.29) is blocked; upgrade one minor version at a time
Downgrading to a previous minor version is blocked
Configuration
The randomize_client_port server-config key was removed; the toggle now lives in the policy file as a top-level randomizeClientPort field, matching the Tailscale-hosted schema. #3251
Headscale refuses to start when the old key is set. Move it to the policy file referenced by policy.path:
{ "randomizeClientPort": true, }If you do not have a policy file yet, create one with that minimal content and point policy.path at it. The default carries over — empty / absent policy means randomizeClientPort: false, matching the previous behaviour for operators who never set the key. Per-node opt-in via nodeAttrs is also supported and stacks on top of the global default.
CLI
headscale nodes register is deprecated in favour of headscale auth register --auth-id <id> --user <user> #1850
The old command continues to work but will be removed in a future release
Changes
ACL Policy
- Fix subnet-to-subnet peer visibility — subnet routers now correctly become peers when ACL rules reference only subnet CIDRs as sources, without requiring node IP rules #3175
- Fix filter rule reduction to use only approved subnet routes instead of all advertised routes, matching Tailscale SaaS behavior #3175
- Add ICMP and IPv6-ICMP protocols to default filter rules when no protocol is specified #3036
- Fix autogroup:self handling for tagged nodes - tagged nodes no longer incorrectly receive autogroup:self filter rules #3036
- Use CIDR format for autogroup:self destination IPs matching Tailscale behavior #3036
- Merge filter rules with identical SrcIPs and IPProto matching Tailscale behavior - multiple ACL rules with the same source now produce a single FilterRule with combined DstPorts #3036
- Fix exit nodes incorrectly receiving filter rules for destinations that only overlap via exit routes #3169 #3175
- Fix address-based aliases (hosts, raw IPs) incorrectly expanding to include the matching node's other address family #2180
- Fix identity-based aliases (tags, users, groups) resolving to IPv4 only; they now include both IPv4 and IPv6 matching Tailscale behavior #2180
- Fix wildcard (*) source in ACLs now using actually-approved subnet routes instead of autoApprover policy prefixes #2180
- Fix non-wildcard source IPs being dropped when combined with wildcard * in the same ACL rule #2180
- Fix exit node approval not triggering filter rule recalculation for peers #2180
- Policy validation error messages now include field context (e.g., src=, dst=) and are more descriptive #2180
- Reject policies whose user@ tokens match multiple DB users; rename the duplicate via headscale users rename to load #3160
- Evaluate the policy tests block on user-initiated writes across both acls and grants; reject policies whose tests fail (beta) #1803
Grants
- Add support for policy grants with ip, app, and via fields #2180
- Add autogroup:danger-all as a source-only autogroup resolving to all IP addresses #2180
- Add capability grants for Taildrive (cap/drive) and peer relay (cap/relay) with automatic companion capabilities #2180
- Add per-viewer via route steering — grants with via tags control which subnet router or exit node handles traffic for each group of viewers #2180
- Enable Taildrive node attributes on all nodes; actual access is controlled by cap/drive grants #2180
SSH Policy
- Add support for localpart:*@<domain> in SSH rule users field, mapping each matching user's email local-part as their OS username #3091
- Add SSH check action support with OIDC and CLI-based approval flows #1850
CLI
- Add headscale auth register, headscale auth approve, and headscale auth reject CLI commands #1850
- Deprecate headscale nodes register --key in favour of headscale auth register --auth-id #1850
- headscale policy check --bypass-grpc-and-access-database-directly validates user@ tokens against the live user database #3160
- Remove deprecated --namespace flag from nodes list, nodes register, and debug create-node commands (use --user instead) #3093
- Remove deprecated namespace/ns command aliases for users and machine/machines aliases for nodes #3093
- Fix DestroyUser deleting all pre-auth keys in the database instead of only the target user's keys #3155
- headscale policy check evaluates the tests block when invoked with --bypass-grpc-and-access-database-directly; without the flag it warns instead of running the tests against empty data #1803
API
- Add auth related routes. The auth/register endpoint now expects data as JSON #1850
- Remove gRPC reflection from the remote (TCP) server #3180
OIDC
- Add a confirmation page before completing node registration, showing the device hostname and machine key fingerprint #3180
- Generalise auth templates into reusable AuthSuccess and AuthWeb components #1850
- Unify auth pipeline with AuthVerdict type, supporting registration, reauthentication, and SSH checks #1850
Configuration
- Add node.expiry configuration option to set a default node key expiry for nodes registered via auth key #3122
- Tagged nodes (registered with tagged pre-auth keys) are exempt from default expiry
- oidc.expiry has been removed; use node.expiry instead (applies to all registration methods including OIDC)
- ephemeral_node_inactivity_timeout is deprecated in favour of node.ephemeral.inactivity_timeout
- Add trusted_proxies to gate True-Client-IP / X-Real-IP / X-Forwarded-For (previously honoured from any client) #3268
Debug
- Add node connectivity ping page for verifying control-plane reachability #3183
- Omit secret fields (Pass, ClientSecret, APIKey) from /debug/config JSON output #3180
- Route statsviz through tsweb.Protected #3180
Other
- Remove old migrations for the debian package #3185
- Install config-example.yaml as example for the debian package #3186
- Fix user-owned re-registration with zero client expiry and no default storing 0001-01-01 00:00:00 in the database instead of NULL #3199
- Fix tailscaled restart on a node with no expiry resetting NULL to 0001-01-01 00:00:00 in the database, affecting both tagged and untagged nodes #3197
- Backfill nodes.expiry rows persisted by older versions as 0001-01-01 00:00:00 to NULL, so nodes upgraded from <0.28 stop reporting as expired #3284
- Update reverse proxy documentation for trusted_proxies configuration option #3292
Upgrade
Please follow the steps outlined in the upgrade guide to update your existing Headscale installation.
Changelog
6777a82 Rewrite reverse proxy documentation
Original source
c5f3d5c auth: clamp logout expiry to now
5228cb1 change: drop subnet-router full update, use policy change
7d845ef db: advance allocator cursor under lock during IP backfill
f61753e db: bound IP allocation scan so exhausted prefixes error out
ec94573 db: drop superseded ephemeral deletions in the GC drain loop
99ad555 db: handle degenerate prefixes in random IP allocation
10696fa db: look up API keys by explicit primary key, not struct condition
4c165ae db: reap ephemeral GC watcher goroutine on cancel and reschedule
e413919 derp: clone regions when merging DERP maps
9f0c74e dns: release lock before extra-records channel send
8f75ee5 docker: head tailscale latest go
f585f8a flake, go.mod: move to Go 1.26.4 and refresh dependencies
21058d1 flake, go.mod: refresh dependencies
29f87e5 flakehashes: refresh vendor hash after dropping gorilla/mux
f61d21b go.mod: drop unused gorilla/mux dependency
b892b8f hscontrol: read Apple platform via chi.URLParam, not mux.Vars
88044f4 hscontrol: satisfy golangci-lint on changed lines
cffdb77 mapper, change: coalesce duplicate policy recomputes per tick
5e05652 mapper: derive incremental visibility from one shared filter
8237ac6 mapper: filter incremental UserProfiles by ACL visibility
cd1c208 mapper: filter peer-change patches by ACL visibility
f4eeb94 mapper: gate broadcast sends until a connection's initial map is delivered
c483beb mapper: guard nil Hostinfo in addNextDNSMetadata
efdd946 mapper: keep one batched bundle per node in flight to preserve order
ad2693f mapper: record initial-map peers only after delivery
0e7b154 mapper: register reconnecting node atomically against cleanup
fad8f2a mapper: test incremental visibility matches full map
9fc88e3 noise: drop write-only nodeKey field to fix data race
71a4ce3 noise: re-delegate SSH check when the auth session is missing (#3306)
0921972 oidc: avoid slice panic in getCookieName for short values
0fdff0c oidc: set SameSite=Lax on state/nonce CSRF cookies
bceac49 policy: add NodeNeedsPeerRecompute predicate
56cd3eb policy: guard SSHCheckParams autogroup:self against nil User
2c9164b policy: precompute node routes in the peer-map build
40ed210 policy: read pm.pol under the mutex
020560f policy: remove unused top-level BuildPeerMap
7918187 servertest: add logout/relogin storm repro with poll churn
f497b4e state, poll: refcount poll sessions, mark offline only on last release
a518a50 state: batch route auto-approval into one policy rebuild
7706552 state: gate reconnect PolicyChange on NodeNeedsPeerRecompute
06d6816 state: keep nil expiry for nodes that stay tagged on reauth
2e24018 state: persist live NodeStore node in persistNodeToDB
4914f9f state: reject re-auth claiming another machine's NodeKey
eb57a3a state: reject registration claiming another machine's NodeKey
017162d state: signal NodeStore shutdown without closing writeQueue
08f186f state: skip database persist for keepalive-only map requests
759381a types: add ActiveSessions poll session refcount to Node
4f67300 types: clone Hostinfo before applying DERP change
5a70a72 types: lock tailcfg DNS config access for extra-records updates
bb06b90 types: skip malformed derp.urls entries instead of panicking
84c9902 util: check RNG error before slicing url-safe random string
ba54349 util: handle single-address IPv4 prefix in reverse DNS generation - May 29, 2026
- Date parsed from source:May 29, 2026
- First seen by Releasebot:Aug 7, 2026
v0.29.0-beta.2
headscale adds major policy and access-control upgrades, including SSH check approvals, policy and SSH tests, Tailscale grants, Taildrive policy support, hostname sanitisation, HA subnet router health probing, and stricter upgrade and validation behavior.
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
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 <id> approves a pending authentication request (SSH check or web auth)
headscale auth reject --auth-id <id> rejects a pending authentication request
headscale auth register --auth-id <id> --user <user> registers a node (replaces deprecated headscale nodes register)
#1850
#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
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
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
Grants
We now support Tailscale 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 sharing and peer relay, and the via
field steers traffic through specific tagged subnet routers or exit nodes. The ip field works like
an ACL rule. Grants can be mixed with ACLs in the same policy file.#2180
As part of this, we added autogroup:danger-all. It resolves to 0.0.0.0/0 and ::/0, all IP
addresses, including those outside the tailnet. This replaces the old behaviour where * matched
all IPs (see BREAKING below). The name is intentional: accepting traffic from the entire
internet is a security-sensitive choice. autogroup:danger-all can only be used as a source.Node attributes (nodeAttrs)
ACL policies now accept a nodeAttrs block. Each entry hands a list of
Tailscale node capabilities to every node matching target. The accepted
target forms are the same as acls.src and grants.src: users, groups,
tags, hosts, prefixes, autogroup:member, autogroup:tagged, and *.{ "randomizeClientPort": true, "nodeAttrs": [ { "target": ["autogroup:tagged"], "attr": ["disable-captive-portal-detection"] }, { "target": ["[email protected]"], "attr": ["nextdns:abc123"] }, ], }Frequently requested capabilities this unlocks include magicdns-aaaa,
disable-relay-server, disable-captive-portal-detection,
nextdns:<profile> / nextdns:no-device-info, randomize-client-port,
and the Taildrive drive:share / drive:access pair. The set is not
limited to these, any string-only cap an operator places in policy
reaches clients unchanged.randomizeClientPort also lands as a top-level policy field that toggles
the default for every node, replacing the old server-config knob.A new auto_update.enabled config option controls the tailnet-wide
default for client auto-update. When true, every node's CapMap carries
default-auto-update: [true] so fresh clients pick up the default
unless they make a local opt-in / opt-out choice.Policies that use the funnel cap, ipPool blocks, or
autogroup:admin / autogroup:owner targets are rejected at load —
those features depend on machinery headscale does not yet ship.#3251
Taildrive
Taildrive (file-sync between
nodes) is now
configurable through policy. Grant drive:share to the node that
hosts files and drive:access to nodes that read or write them; pair
with a tailscale.com/cap/drive grant to set the per-share access
mode:{ "nodeAttrs": [ { "target": ["tag:fileserver"], "attr": ["drive:share"] }, { "target": ["autogroup:member"], "attr": ["drive:access"] }, ], "grants": [ { "src": ["autogroup:member"], "dst": ["tag:fileserver"], "app": { "tailscale.com/cap/drive": [{ "shares": ["*"], "access": "rw" }], }, }, ], }A wildcard nodeAttrs ("target": ["*"]) hands the caps to every
node when fine-grained control is not needed.Hostname sanitisation
Hostnames are now santised using Tailscales magicdns sanitisation rules, matching Tailscale SaaS behavior. This means that hostnames with non-ASCII characters, special characters, or reserved DNS label characters are now transformed into valid DNS labels for MagicDNS. This improves our previously too strict sanitisation that rejected hostnames based on our guesswork and not based on the Tailscale upstream behaviour.
Examples that previously regressed and now work:
Input
Raw (Hostname)
DNS label (GivenName)
Joe's Mac mini
Joe's Mac mini
joes-mac-mini
Yuri's MacBook Pro
Yuri's MacBook Pro
yuris-macbook-pro
Test@Host
Test@Host
test-host
mail.server
mail.server
mail-server
My-PC!
My-PC!
my-pc
我的电脑
我的电脑
node#3202
HA subnet router health probing
Headscale now actively probes HA subnet routers to detect nodes that are connected but not
forwarding traffic. The control plane periodically pings HA subnet routers via the Noise
control channel and fails over to a healthy standby if the primary stops responding. This is
enabled by default (node.routes.ha.probe_interval: 10s, probe_timeout: 5s) and only
active when HA routes exist (2+ nodes advertising the same prefix). Set probe_interval to
0 to disable. This complements the existing disconnect-based failover, catching "zombie
connected" routers that maintain their control session but cannot route packets.#3194
BREAKING
Hostname handling
The GivenName collision policy changed from an 8-char random hash suffix (laptop-abc12xyz) to a monotonic numeric suffix (laptop, laptop-1, laptop-2, …), matching Tailscale SaaS. Empty / all-non-ASCII hostnames now fall back to the literal node instead of invalid-<rand>. MagicDNS names change on upgrade for any node whose previous label was a random-suffix form; the raw Hostname column is unchanged. #3202
ACL Policy
Wildcard (*) in ACL sources and destinations now resolves to Tailscale's CGNAT range (100.64.0.0/10) and ULA range (fd7a:115c:a1e0::/48) instead of all IPs (0.0.0.0/0 and ::/0) #3036
This better matches Tailscale's security model where * means "any node in the tailnet" rather than "any IP address"
Policies that need to match all IP addresses including non-Tailscale IPs should use autogroup:danger-all as a source, or explicit CIDR ranges as destinations #2180
autogroup:danger-all can only be used as a source; it cannot be used as a destination
Note: Users with non-standard IP ranges configured in prefixes.ipv4 or prefixes.ipv6 (which is unsupported and produces a warning) will need to explicitly specify their CIDR ranges in ACL rules instead of using *
Validate autogroup:self source restrictions matching Tailscale behavior - tags, hosts, and IPs are rejected as sources for autogroup:self destinations #3036
Policies using tags, hosts, or IP addresses as sources for autogroup:self destinations will now fail validation
The proto:icmp protocol name now only includes ICMPv4 (protocol 1), matching Tailscale behavior #3036
Previously, proto:icmp included both ICMPv4 and ICMPv6
Use proto:ipv6-icmp or protocol number 58 explicitly for ICMPv6
Upgrade Path
Headscale now enforces a strict version upgrade path #3083
Skipping minor versions (e.g. 0.27 → 0.29) is blocked; upgrade one minor version at a time
Downgrading to a previous minor version is blocked
Configuration
The randomize_client_port server-config key was removed; the
toggle now lives in the policy file as a top-level
randomizeClientPort field, matching the Tailscale-hosted schema. #3251Headscale refuses to start when the old key is set. Move it to the
policy file referenced by policy.path:{ "randomizeClientPort": true, }If you do not have a policy file yet, create one with that minimal
content and point policy.path at it. The default carries over —
empty / absent policy means randomizeClientPort: false, matching
the previous behaviour for operators who never set the key. Per-node
opt-in via nodeAttrs is also supported and stacks on top of the
global default.CLI
headscale nodes register is deprecated in favour of headscale auth register --auth-id <id> --user <user> #1850
The old command continues to work but will be removed in a future release
Changes
ACL Policy
Fix subnet-to-subnet peer visibility — subnet routers now correctly become peers when ACL rules reference only subnet CIDRs as sources, without requiring node IP rules #3175
Fix filter rule reduction to use only approved subnet routes instead of all advertised routes, matching Tailscale SaaS behavior #3175
Add ICMP and IPv6-ICMP protocols to default filter rules when no protocol is specified #3036
Fix autogroup:self handling for tagged nodes - tagged nodes no longer incorrectly receive autogroup:self filter rules #3036
Use CIDR format for autogroup:self destination IPs matching Tailscale behavior #3036
Merge filter rules with identical SrcIPs and IPProto matching Tailscale behavior - multiple ACL rules with the same source now produce a single FilterRule with combined DstPorts #3036
Fix exit nodes incorrectly receiving filter rules for destinations that only overlap via exit routes #3169 #3175
Fix address-based aliases (hosts, raw IPs) incorrectly expanding to include the matching node's other address family #2180
Fix identity-based aliases (tags, users, groups) resolving to IPv4 only; they now include both IPv4 and IPv6 matching Tailscale behavior #2180
Fix wildcard (*) source in ACLs now using actually-approved subnet routes instead of autoApprover policy prefixes #2180
Fix non-wildcard source IPs being dropped when combined with wildcard * in the same ACL rule #2180
Fix exit node approval not triggering filter rule recalculation for peers #2180
Policy validation error messages now include field context (e.g., src=, dst=) and are more descriptive #2180
Reject policies whose user@ tokens match multiple DB users; rename the duplicate via headscale users rename to load #3160
Evaluate the policy tests block on user-initiated writes across both acls and grants; reject policies whose tests fail (beta) #1803
Grants
Add support for policy grants with ip, app, and via fields #2180
Add autogroup:danger-all as a source-only autogroup resolving to all IP addresses #2180
Add capability grants for Taildrive (cap/drive) and peer relay (cap/relay) with automatic companion capabilities #2180
Add per-viewer via route steering — grants with via tags control which subnet router or exit node handles traffic for each group of viewers #2180
Enable Taildrive node attributes on all nodes; actual access is controlled by cap/drive grants #2180
SSH Policy
Add support for localpart:*@<domain> in SSH rule users field, mapping each matching user's email local-part as their OS username #3091
Add SSH check action support with OIDC and CLI-based approval flows #1850
CLI
Add headscale auth register, headscale auth approve, and headscale auth reject CLI commands #1850
Deprecate headscale nodes register --key in favour of headscale auth register --auth-id #1850
headscale policy check --bypass-grpc-and-access-database-directly validates user@ tokens against the live user database #3160
Remove deprecated --namespace flag from nodes list, nodes register, and debug create-node commands (use --user instead) #3093
Remove deprecated namespace/ns command aliases for users and machine/machines aliases for nodes #3093
Fix DestroyUser deleting all pre-auth keys in the database instead of only the target user's keys #3155
headscale policy check evaluates the tests block when invoked with --bypass-grpc-and-access-database-directly; without the flag it warns instead of running the tests against empty data #1803
API
Add auth related routes. The auth/register endpoint now expects data as JSON #1850
Remove gRPC reflection from the remote (TCP) server #3180
OIDC
Add a confirmation page before completing node registration, showing the device hostname and machine key fingerprint #3180
Generalise auth templates into reusable AuthSuccess and AuthWeb components #1850
Unify auth pipeline with AuthVerdict type, supporting registration, reauthentication, and SSH checks #1850
Configuration
Add node.expiry configuration option to set a default node key expiry for nodes registered via auth key #3122
Tagged nodes (registered with tagged pre-auth keys) are exempt from default expiry
oidc.expiry has been removed; use node.expiry instead (applies to all registration methods including OIDC)
ephemeral_node_inactivity_timeout is deprecated in favour of node.ephemeral.inactivity_timeout
Add trusted_proxies to gate True-Client-IP / X-Real-IP / X-Forwarded-For (previously honoured from any client) #3268
Debug
Add node connectivity ping page for verifying control-plane reachability #3183
Omit secret fields (Pass, ClientSecret, APIKey) from /debug/config JSON output #3180
Route statsviz through tsweb.Protected #3180
Other
Remove old migrations for the debian package #3185
Install config-example.yaml as example for the debian package #3186
Fix user-owned re-registration with zero client expiry and no default storing 0001-01-01 00:00:00 in the database instead of NULL #3199
Pre-existing rows with 0001-01-01 00:00:00 are not backfilled; they clear themselves the next time the node re-registers
Fix tailscaled restart on a node with no expiry resetting NULL to 0001-01-01 00:00:00 in the database, affecting both tagged and untagged nodes #3197
Upgrade
Please follow the steps outlined in the upgrade guide to update your existing Headscale installation.
Changelog
ea8fc72 db: backfill zero-time node expiry to NULL
Original source
77ba225 db: treat Go module pseudo-versions as dev builds
66a5f99 gh: pre-pull released tailscale images for fork-PR CI
79562b9 hi: add list-versions subcommand
171fd7a policy: key autogroup:self invalidation on UserID not User view
4483fd0 tsic, gh: keep unstable on Docker Hub
2e49f3d tsic: pull tailscale images from ghcr.io - May 22, 2026
- Date parsed from source:May 22, 2026
- First seen by Releasebot:Aug 7, 2026
v0.29.0-beta.1
headscale ships major policy and SSH upgrades with grants, node attributes, Taildrive controls, HA subnet router health probing, stricter version upgrade rules, and better Tailscale compatibility across ACLs, hostname handling, and auth flows.
Minimum supported Tailscale client version: v1.80.0
Original source
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
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 <id> approves a pending authentication request (SSH check or web auth)
headscale auth reject --auth-id <id> rejects a pending authentication request
headscale auth register --auth-id <id> --user <user> registers a node (replaces deprecated headscale nodes register)
#1850
#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
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
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
Grants
We now support Tailscale 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 sharing and peer relay, and the via
field steers traffic through specific tagged subnet routers or exit nodes. The ip field works like
an ACL rule. Grants can be mixed with ACLs in the same policy file.
#2180
As part of this, we added autogroup:danger-all. It resolves to 0.0.0.0/0 and ::/0, all IP
addresses, including those outside the tailnet. This replaces the old behaviour where * matched
all IPs (see BREAKING below). The name is intentional: accepting traffic from the entire
internet is a security-sensitive choice. autogroup:danger-all can only be used as a source.
Node attributes (nodeAttrs)
ACL policies now accept a nodeAttrs block. Each entry hands a list of
Tailscale node capabilities to every node matching target. The accepted
target forms are the same as acls.src and grants.src: users, groups,
tags, hosts, prefixes, autogroup:member, autogroup:tagged, and .
{
"randomizeClientPort": true,
"nodeAttrs": [
{ "target": ["autogroup:tagged"], "attr": ["disable-captive-portal-detection"] },
{ "target": ["[email protected]"], "attr": ["nextdns:abc123"] },
],
}
Frequently requested capabilities this unlocks include magicdns-aaaa,
disable-relay-server, disable-captive-portal-detection,
nextdns:<profile> / nextdns:no-device-info, randomize-client-port,
and the Taildrive drive:share / drive:access pair. The set is not
limited to these, any string-only cap an operator places in policy
reaches clients unchanged.
randomizeClientPort also lands as a top-level policy field that toggles
the default for every node, replacing the old server-config knob.
A new auto_update.enabled config option controls the tailnet-wide
default for client auto-update. When true, every node's CapMap carries
default-auto-update: [true] so fresh clients pick up the default
unless they make a local opt-in / opt-out choice.
Policies that use the funnel cap, ipPool blocks, or
autogroup:admin / autogroup:owner targets are rejected at load —
those features depend on machinery headscale does not yet ship.
#3251
Taildrive
Taildrive (file-sync between
nodes) is now
configurable through policy. Grant drive:share to the node that
hosts files and drive:access to nodes that read or write them; pair
with a tailscale.com/cap/drive grant to set the per-share access
mode:
{
"nodeAttrs": [
{ "target": ["tag:fileserver"], "attr": ["drive:share"] },
{ "target": ["autogroup:member"], "attr": ["drive:access"] },
],
"grants": [
{
"src": ["autogroup:member"],
"dst": ["tag:fileserver"],
"app": {
"tailscale.com/cap/drive": [{ "shares": [""], "access": "rw" }],
},
},
],
}
A wildcard nodeAttrs ("target": [""]) hands the caps to every
node when fine-grained control is not needed.
Hostname sanitisation
Hostnames are now santised using Tailscales magicdns sanitisation rules, matching Tailscale SaaS behavior. This means that hostnames with non-ASCII characters, special characters, or reserved DNS label characters are now transformed into valid DNS labels for MagicDNS. This improves our previously too strict sanitisation that rejected hostnames based on our guesswork and not based on the Tailscale upstream behaviour.
Examples that previously regressed and now work:
Input
Raw (Hostname)
DNS label (GivenName)
Joe's Mac mini
Joe's Mac mini
joes-mac-mini
Yuri's MacBook Pro
Yuri's MacBook Pro
yuris-macbook-pro
Test@Host
Test@Host
test-host
mail.server
mail.server
mail-server
My-PC!
My-PC!
my-pc
我的电脑
我的电脑
node
#3202
HA subnet router health probing
Headscale now actively probes HA subnet routers to detect nodes that are connected but not
forwarding traffic. The control plane periodically pings HA subnet routers via the Noise
control channel and fails over to a healthy standby if the primary stops responding. This is
enabled by default (node.routes.ha.probe_interval: 10s, probe_timeout: 5s) and only
active when HA routes exist (2+ nodes advertising the same prefix). Set probe_interval to
0 to disable. This complements the existing disconnect-based failover, catching "zombie
connected" routers that maintain their control session but cannot route packets.
#3194
BREAKING
Hostname handling
The GivenName collision policy changed from an 8-char random hash suffix (laptop-abc12xyz) to a monotonic numeric suffix (laptop, laptop-1, laptop-2, …), matching Tailscale SaaS. Empty / all-non-ASCII hostnames now fall back to the literal node instead of invalid-<rand>. MagicDNS names change on upgrade for any node whose previous label was a random-suffix form; the raw Hostname column is unchanged. #3202
ACL Policy
Wildcard () in ACL sources and destinations now resolves to Tailscale's CGNAT range (100.64.0.0/10) and ULA range (fd7a:115c:a1e0::/48) instead of all IPs (0.0.0.0/0 and ::/0) #3036
This better matches Tailscale's security model where * means "any node in the tailnet" rather than "any IP address"
Policies that need to match all IP addresses including non-Tailscale IPs should use autogroup:danger-all as a source, or explicit CIDR ranges as destinations #2180
autogroup:danger-all can only be used as a source; it cannot be used as a destination
Note: Users with non-standard IP ranges configured in prefixes.ipv4 or prefixes.ipv6 (which is unsupported and produces a warning) will need to explicitly specify their CIDR ranges in ACL rules instead of using *
Validate autogroup:self source restrictions matching Tailscale behavior - tags, hosts, and IPs are rejected as sources for autogroup:self destinations #3036
Policies using tags, hosts, or IP addresses as sources for autogroup:self destinations will now fail validation
The proto:icmp protocol name now only includes ICMPv4 (protocol 1), matching Tailscale behavior #3036
Previously, proto:icmp included both ICMPv4 and ICMPv6
Use proto:ipv6-icmp or protocol number 58 explicitly for ICMPv6
Upgrade Path
Headscale now enforces a strict version upgrade path #3083
Skipping minor versions (e.g. 0.27 → 0.29) is blocked; upgrade one minor version at a time
Downgrading to a previous minor version is blocked
Patch version changes within the same minor are always allowed
Configuration
The randomize_client_port server-config key was removed; the
toggle now lives in the policy file as a top-level
randomizeClientPort field, matching the Tailscale-hosted schema. #3251
Headscale refuses to start when the old key is set. Move it to the
policy file referenced by policy.path:
{
"randomizeClientPort": true,
}
If you do not have a policy file yet, create one with that minimal
content and point policy.path at it. The default carries over —
empty / absent policy means randomizeClientPort: false, matching
the previous behaviour for operators who never set the key. Per-node
opt-in via nodeAttrs is also supported and stacks on top of the
global default.
CLI
headscale nodes register is deprecated in favour of headscale auth register --auth-id <id> --user <user> #1850
The old command continues to work but will be removed in a future release
Changes
ACL Policy
Fix subnet-to-subnet peer visibility — subnet routers now correctly become peers when ACL rules reference only subnet CIDRs as sources, without requiring node IP rules #3175
Fix filter rule reduction to use only approved subnet routes instead of all advertised routes, matching Tailscale SaaS behavior #3175
Add ICMP and IPv6-ICMP protocols to default filter rules when no protocol is specified #3036
Fix autogroup:self handling for tagged nodes - tagged nodes no longer incorrectly receive autogroup:self filter rules #3036
Use CIDR format for autogroup:self destination IPs matching Tailscale behavior #3036
Merge filter rules with identical SrcIPs and IPProto matching Tailscale behavior - multiple ACL rules with the same source now produce a single FilterRule with combined DstPorts #3036
Fix exit nodes incorrectly receiving filter rules for destinations that only overlap via exit routes #3169 #3175
Fix address-based aliases (hosts, raw IPs) incorrectly expanding to include the matching node's other address family #2180
Fix identity-based aliases (tags, users, groups) resolving to IPv4 only; they now include both IPv4 and IPv6 matching Tailscale behavior #2180
Fix wildcard () source in ACLs now using actually-approved subnet routes instead of autoApprover policy prefixes #2180
Fix non-wildcard source IPs being dropped when combined with wildcard * in the same ACL rule #2180
Fix exit node approval not triggering filter rule recalculation for peers #2180
Policy validation error messages now include field context (e.g., src=, dst=) and are more descriptive #2180
Reject policies whose user@ tokens match multiple DB users; rename the duplicate via headscale users rename to load #3160
Evaluate the policy tests block on user-initiated writes across both acls and grants; reject policies whose tests fail (beta) #1803
Grants
Add support for policy grants with ip, app, and via fields #2180
Add autogroup:danger-all as a source-only autogroup resolving to all IP addresses #2180
Add capability grants for Taildrive (cap/drive) and peer relay (cap/relay) with automatic companion capabilities #2180
Add per-viewer via route steering — grants with via tags control which subnet router or exit node handles traffic for each group of viewers #2180
Enable Taildrive node attributes on all nodes; actual access is controlled by cap/drive grants #2180
SSH Policy
Add support for localpart:@<domain> in SSH rule users field, mapping each matching user's email local-part as their OS username #3091
Add SSH check action support with OIDC and CLI-based approval flows #1850
CLI
Add headscale auth register, headscale auth approve, and headscale auth reject CLI commands #1850
Deprecate headscale nodes register --key in favour of headscale auth register --auth-id #1850
headscale policy check --bypass-grpc-and-access-database-directly validates user@ tokens against the live user database #3160
Remove deprecated --namespace flag from nodes list, nodes register, and debug create-node commands (use --user instead) #3093
Remove deprecated namespace/ns command aliases for users and machine/machines aliases for nodes #3093
Fix DestroyUser deleting all pre-auth keys in the database instead of only the target user's keys #3155
headscale policy check evaluates the tests block when invoked with --bypass-grpc-and-access-database-directly; without the flag it warns instead of running the tests against empty data #1803
API
Add auth related routes. The auth/register endpoint now expects data as JSON #1850
Remove gRPC reflection from the remote (TCP) server #3180
OIDC
Add a confirmation page before completing node registration, showing the device hostname and machine key fingerprint #3180
Generalise auth templates into reusable AuthSuccess and AuthWeb components #1850
Unify auth pipeline with AuthVerdict type, supporting registration, reauthentication, and SSH checks #1850
Configuration
Add node.expiry configuration option to set a default node key expiry for nodes registered via auth key #3122
Tagged nodes (registered with tagged pre-auth keys) are exempt from default expiry
oidc.expiry has been removed; use node.expiry instead (applies to all registration methods including OIDC)
ephemeral_node_inactivity_timeout is deprecated in favour of node.ephemeral.inactivity_timeout
Add trusted_proxies to gate True-Client-IP / X-Real-IP / X-Forwarded-For (previously honoured from any client) #3268
Debug
Add node connectivity ping page for verifying control-plane reachability #3183
Omit secret fields (Pass, ClientSecret, APIKey) from /debug/config JSON output #3180
Route statsviz through tsweb.Protected #3180
Other
Remove old migrations for the debian package #3185
Install config-example.yaml as example for the debian package #3186
Fix user-owned re-registration with zero client expiry and no default storing 0001-01-01 00:00:00 in the database instead of NULL #3199
Pre-existing rows with 0001-01-01 00:00:00 are not backfilled; they clear themselves the next time the node re-registers
Fix tailscaled restart on a node with no expiry resetting NULL to 0001-01-01 00:00:00 in the database, affecting both tagged and untagged nodes #3197
Upgrade
Please follow the steps outlined in the upgrade guide to update your existing Headscale installation.
Changelog
7f02210 .golangci: ignore tests for goconst, raise occurrence threshold
157e3a3 AGENTS.md: trim to behavioural guidance, drop deprecated sub-agent
cfb308b Add FAQ entry to migrate back to default IP prefixes
e597f4c Add Headscale UI to web UI documentation
df339cd Add a link to Authentik's integration guide
f3f84a5 Add docs for policy-wide options and node attributes
890a044 Add more UIs
4eb5899 Add taildrive, tests, sshTests as supported features
542091e Add unit test
20dff82 CHANGELOG: add minimum Tailscale version for 0.29.0
58a85b6 CHANGELOG: bump 0.29.0 minimum tailscale client to v1.80.0
f693cc0 CHANGELOG: document grants support for 0.29.0
4e1d83e CHANGELOG: document hostname cleanroom rewrite
b52f8cb CHANGELOG: document node.expiry and oidc.expiry deprecation
408f402 CHANGELOG: document nodeAttrs feature and migrations
f03d41e CHANGELOG: document policy tests (beta)
e78a24b CHANGELOG: document sshTests evaluation (beta)
30d1857 CHANGELOG: document strict version upgrade path
ec48f34 CHANGELOG: document subnet-to-subnet ACL fixes
fd10741 CHANGELOG: document user-facing changes from #3180
bcfaf6a CHANGELOG: note nil expiry preservation fix
e3323b6 Describe how to set username instead of SPN for Kanidm
dc73376 Dockerfile.tailscale-HEAD,Dockerfile.derper: bump golang to 1.26.3
78570c7 Dockerfile: bump base images
e40dbe3 Dockerfile: bump tailscale DERPer builder to Go 1.26.2
6390fce Dockerfile: bump tailscale HEAD builder to Go 1.26.2
7e6c792 Document availability of autgroup:internet
faf55f5 Document how to use the provider identifier in the policy
1a64d95 Document supported autogroups once
0f12e41 Explain one approach to update OIDC provider info
efd83da Explicitly mention that a headscale username should not end with @
d556df1 Extend upgrade guide with backup instructions
4460055 Fix invisible selected menu item
a0d6802 Fix minor formatting issue in FAQ
c7f221d Fix typo and wording
3672a2d Fix typo in API key creation help text
414d3bb Fix typo in comment about fsnotify behavior
c907b0d Fix version in mkdocs
32e1d77 Install config-example.yaml as example for the debian package
9e50071 Link Fosdem 2026 talk
84c7f0d Link to development builds
8028fa5 No longer consider autogroup:self experimental
e07b391 Quote autogroup:self in the CHANGELOG
acddd73 Reformat docs with mdformat
109bfc4 Refresh docs for Grants
c4ab267 Refresh features page
8f60b81 Refresh update path
c29bcd2 Release planning happens in milestones
9ea09ea Remove changelog section for 0.28.1
14ce7e9 Remove link to Arch AUR headscale-git
4844628 Remove link to sqlite
892ffff Remove misleading comment
61c9ae8 Remove old migrations for the debian package
e13f045 Remove redundant prefix
4bb0241 Require to update from one version to the next
edb7ad0 Rewrite ACL docs as policy
45b698d Shorten container introduction
513544c Simplify upgrade snippet with a link to the upgrade guide
8423af2 Swap favicon for updated version
47307d1 Switch to mdformat to format docs
f3512d5 Switch to mkdocs-materialx
e285f3c The headscale service is enabled by default
3557333 Update config-example links
9baa795 Update docs for auth-id changes
813eb2d Update docs for new HA tracking
f1494a3 Update links to Tailscale documentation
fda72ad Update main.md
68b0014 Use distroless without quotes
163363a Use docs instead of KB
23a5f1b Use pymdownx.magiclink with its default configuration
97778c9 all: add tests for PingRequest implementation
17236fd all: annotate complex functions with gocyclo rationale
3e2aa58 all: annotate gosec false positives with rationale
93860a5 all: apply formatter changes
4cca631 all: apply godoc [Name] link conventions across comments
43afeed all: apply golangci-lint 2.9.0 fixes
ce580f8 all: fix golangci-lint issues (#3064)
eccf64e all: fix staticcheck SA4006 in types_test.go
3e0a96e all: fix test flakiness and improve test infrastructure
b113655 all: implement PingRequest for node connectivity checking
f905d58 all: mechanical lint fixes
742878d all: regenerate generated files for new tool versions
010a556 all: rephrase prose to fit codebase voice
36a73f8 all: update Go dependencies
542cdb2 all: update Go to 1.26.1
70f8141 all: upgrade from Go 1.26rc2 to Go 1.26.0
0f6d312 all: upgrade to Go 1.26rc2 and modernize codebase
4a9a329 all: use lowercase log messages
0567cb6 app: add security headers middleware
f7d8bb8 app: remove gRPC reflection from remote server
3033844 app: switch from gorilla to chi mux
8a97dd1 app: wire HA health prober into scheduled tasks
f066d12 assets: fix logo alignment and error icon centering
41d70fe auth: check machine key on tailscaled-restart fast path
cb3b694 auth: generalise auth flow and introduce AuthVerdict
25ccb5a build: update golangci-lint and gopls in flake
eb23c12 capver, types: bump to tailscale v1.98, drop LegacyDERPString
442fcdb capver: regenerate for tailscale v1.96
31c4331 capver: regenerate from docker tags
2530d86 change: document PingRequest merge first-wins foot-gun
4a4032a changelog: document filter rule merging
f27298c changelog: document wildcard CGNAT range change Add breaking change entry for the wildcard resolution change to use CGNAT/ULA ranges instead of all IPs. Updates #3036
575d8ec changelog: normalise 0.29.0 BREAKING and Changes sections
9621a97 ci, pre-commit: validate vendor hash via vendorhash check
e171d30 ci: add build workflow for main branch
99a93c1 ci: add rolling development tag to container builds
795a1ef ci: fetch full history in golangci-lint job
d15ec28 ci: pin Docker to v28 to avoid v29 breaking changes
1b6ab52 ci: regenerate integration test workflow
0f97294 ci: regenerate integration test workflow
5c449db ci: regenerate test-integration.yaml for TestSSHLocalpart
a7d405a ci: regenerate test-integration.yaml for TestTailscaleRustAxum
1f9635c ci: restrict test generator to .go files
a76b4bd ci: switch integration tests to ARM runners
4d3b567 ci: use overlay2 storage driver instead of pinning docker v28
e00c899 cmd, templates, integration: extract shared production constants
461a0e2 cmd/dev: add local development server tool
1a58b77 cmd/dev: validate --port fits the derived-port range
6c08b49 cmd/headscale/cli: add confirmAction helper for force/prompt patterns
aae2f7d cmd/headscale/cli: add grpcRun wrapper for gRPC client lifecycle
7b7b270 cmd/headscale/cli: add mustMarkRequired helper for init-time flag validation
d6c39e6 cmd/headscale/cli: add printListOutput to centralise table-vs-JSON branching
095106f cmd/headscale/cli: convert remaining commands to RunE
22fccae cmd/headscale/cli: deduplicate expiration parsing and api-key flag validation
2765fd3 cmd/headscale/cli: drop dead flag-read error checks
af777f4 cmd/headscale/cli: extract bypassDatabase helper and simplify policy file reads
92a9acc cmd/headscale/cli: mention sshTests in policy check help
7460bec cmd/headscale/cli: move errMissingParameter and Error type to their users
8891ec9 cmd/headscale/cli: remove deprecated output, SuccessOutput, ErrorOutput
d72a06c cmd/headscale/cli: remove legacy namespace and machine aliases
13ebea1 cmd/headscale/cli: remove nil resp guards and unexport HasMachineOutputFlag
e816397 cmd/headscale/cli: remove no-op Args functions from serveCmd and dumpConfigCmd
e6546b2 cmd/headscale/cli: silence cobra error/usage output and centralise error formatting
e4fe216 cmd/headscale/cli: switch to RunE with grpcRunE and error returns
ca321d3 cmd/headscale/cli: use HeadscaleDateTimeFormat and util.Base10 consistently
4e0c2b8 cmd/headscale/cli: validate users in policy check
e470774 cmd/vendorhash: track vendor SRI in flakehashes.json
b5090a0 cmd: use zf constants for zerolog field names
3f73ed5 config, types: move randomize_client_port from server config to policy file
8295883 db: enforce strict version upgrade path
73613d7 db: fix database_versions table creation for PostgreSQL
3037e5e db: fix slice aliasing in migration tag merge
0641771 db: guard UsePreAuthKey with WHERE used=false
af7e7a4 db: remove unused SetApprovedRoutes and SetTags helpers
7c756b8 db: scope DestroyUser to only delete the target user's pre-auth keys
93e8c72 debug: explain URLIsNoise choice in ping callback
d5a4e6e debug: route statsviz through tsweb.Protected
84adda2 doc: add CHANGELOG entries for SSH check and auth commands
585d0c0 docs(config): fix typo in config-example.yaml
01eb540 docs(setup): fix typo in requirements.md
3acce2d errors: rewrite errors to follow go best practices
5105033 feat: add prominent warning banner for non-standard IP prefixes
568baf3 fix: align banner right-side border to consistent 64-char width
25adfaf flake.nix, flake.lock: bump nixpkgs and pinned tools
980622e flake.nix, go.mod: bump tailscale.com to v1.97.0-pre
9c3a3c5 flake: upgrade golangci-lint to 2.9.0 and update nixpkgs
61a14bb gen: regenerate from auth proto changes
570735f gen: regenerate grpc stubs with protoc-gen-go-grpc v1.6.2
64f2313 github: add needs-more-info automation workflow
900f4b7 github: add support-request automation workflow
174e409 github: drop nu flatten in needs-more-info timer
e0d8c3c github: fix needs-more-info label race condition
a7f981e github: fix needs-more-info label race condition
ce5d1ba github: split nu where in needs-more-info timer
c1b468f github: update issue template contact links
be90910 go.mod, go.sum: bump dependencies for v0.29.0
1f48ebb go.mod: add github.com/realclientip/realclientip-go
2f94b80 go.mod: add stress tool dependency
0cf27eb go.mod: add tstest/mts tool dependency
27f5641 golangci: add forbidigo rule for zerolog field constants
0c6b9f5 goreleaser: remove unused ts2019 build tag
48cc98b hscontrol, cli: add auth register and approve commands
52d454d hscontrol/db: add migration to clear user_id on tagged nodes
7148a69 hscontrol/grpcv1: use EmbedObject and zf constants
53cdeff hscontrol/mapper: use sub-loggers and zf constants
f74ea5b hscontrol/policy/v2: add Grant policy format support
bc9fb6d hscontrol/policy/v2: reject ambiguous user references at load time
b09af38 hscontrol/poll,state: fix grace period disconnect TOCTOU race
4f87241 hscontrol/poll: use sub-logger pattern for mapSession
4e73133 hscontrol/routes: use sub-logger and zf constants
ca7362e hscontrol/servertest: add control plane lifecycle and consistency tests
f87b086 hscontrol/servertest: add policy, route, ephemeral, and content tests
00c41b6 hscontrol/servertest: add race, stress, and poll race tests
ab4e205 hscontrol/servertest: expand issue tests to 24 scenarios, surface 4 issues
3d53f97 hscontrol/servertest: fix test expectations for eventual consistency
dd16567 hscontrol/state,db: use zf constants for logging
8048f10 hscontrol/state: extract findExistingNodeForPAK to reduce complexity
1053fbb hscontrol/state: fix online status reset during re-registration
2f907ed hscontrol/types: regenerate types_clone.go for viewer bump
894e694 hscontrol/types: regenerate types_view.go
1059c67 hscontrol/types: silence zerolog by default in tests
e0a436c hscontrol/util/zlog/zf: add tag, authkey, and route constants
0288614 hscontrol: add servertest harness for in-process control plane testing
580dcad hscontrol: add tests for SetTags user_id database persistence
7e8930c hscontrol: add tests for default node key expiry
1e4fc3f hscontrol: add tests for deleting users with tagged nodes
75e56df hscontrol: enforce that tagged nodes never have user_id
c6c29c0 hscontrol: gate proxy header trust on trusted_proxies
42b8c77 hscontrol: limit /verify request body size
4ad200a hscontrol: preserve nil expiry on tailscaled restart
d6dfdc1 hscontrol: route hostname handling through dnsname and NodeStore
91730e2 hscontrol: use EmbedObject for node logging
99767cf hscontrol: validate machine key and bind src/dst in SSH check handler
f1e5f13 integration/acl: add tag verification step to TestACLTagPropagationPortSpecific
ebc57d9 integration/acl: fix TestACLPolicyPropagationOverTime infrastructure
81b871c integration/acl: replace custom entrypoints with WithPackages
a147b0c integration/acl: use CurlFailFast for all negative curl assertions
eec3844 integration/dockertestutil: wait for libnetwork settle on reconnect
e638cbc integration/tsic: accept via peer-relay in non-direct ping check
ea968e2 integration/tsric: add TailscaleRustInContainer package
7bb86f2 integration: HA cable-pull lifecycle test
a7edcf3 integration: add CI-scaled timeouts and curl helpers for flaky ACL tests
af26bab integration: add HA ping failover test
3db0a48 integration: add SSH check mode tests
2be94ce integration: add TestSSHLocalpart integration test
775bc3a integration: add TestTailscaleRustAxum for tailscale-rs
9b1a6b6 integration: add cap/relay grant peer relay lifecycle test
bca6e63 integration: add custom subnet support and fix exit node tests
a739862 integration: add via grant route steering tests
98e9ff4 integration: authenticate Docker Hub pulls and retry transient errors
ba251e7 integration: cover exit nodes via autogroup:internet ACL (#3212)
ecaf56e integration: drop Force flag on docker network disconnect
51eed41 integration: fix ACL tests for address-family-specific resolve
9db5fb6 integration: fix error message assertion for invalid ACL action
be4fd9f integration: fix tag tests for tagged nodes with nil user_id
bfb6fd8 integration: fixup test
dfcc96d integration: harden ACL test ergonomics
acb8cfc integration: make docker execute and ping timeouts CI-aware
27c9113 integration: regenerate workflow for HA docker disconnect test
574a618 integration: reject failing sshTests at headscale policy set
3a4af8c integration: remove --accept-routes from via steering routers
b762e4c integration: remove exit node via grant tests
78fd6ef integration: replace ad-hoc test timeouts with named constants
155e42f integration: retry transient docker network ops
a9a2001 integration: scale remaining hardcoded timeouts and replace pingAllHelper
d1443a4 integration: skip subpackage tests in workflow generator
e5ebe32 integration: standardize test infrastructure options
e44b402 integration: update TestSubnetRouteACL for filter merging and IPProto
210f58f integration: use CI-scaled timeouts for all EventuallyWithT assertions
a345a22 mapper, app: ship MagicDNS Routes as empty slices, not nil
b3f795f mapper, policy/v2: stamp suggest-exit-node on Peer.CapMap when exit routes approved
6fcff9e mapper, state: deliver nodeAttrs through MapResponse and harden nextdns DoH rewrite
2d549e5 mapper/batcher: add regression tests for M1, M3, M7 fixes
8e26651 mapper/batcher: add regression tests for timer leak and Close lifecycle
9b24a39 mapper/batcher: add scale benchmarks
21e02e5 mapper/batcher: add unit tests and benchmarks
feaf85b mapper/batcher: clean up test constants and output
50e8b21 mapper/batcher: fix pointer retention, done-channel init, and connected-map races
da33795 mapper/batcher: fix race conditions in cleanup and lookups
86e2798 mapper/batcher: minor production code cleanup
57a38b5 mapper/batcher: reduce hot-path log verbosity
3ebe4d9 mapper/batcher: reduce lock contention with two-phase send
afd3a6a mapper/batcher: remove disabled X-prefixed test functions
87b8507 mapper/batcher: replace connected map with per-node disconnectedAt
3276bda mapper/batcher: replace time.After with NewTimer to avoid timer leak
5707068 mapper/batcher: restructure internals for correctness
82c7efc mapper/batcher: serialize per-node work to prevent out-of-order delivery
6031706 mapper/batcher: serialize per-node work to prevent out-of-order delivery
051a38a mapper/batcher: track worker goroutines and stop ticker on Close
3daf45e mapper: close stale map channels after send timeouts
7881f65 mapper: extract node connection types to node_conn.go
9371b4e mapper: fix empty Peers list not clearing client peer state
3587225 mapper: fix phantom updateSentPeers on disconnected nodes
b81d6c7 mapper: handle RemoveNode after channel cleanup
6cd919d mapper: include UserProfiles in policy-change MapResponses
2058343 mapper: remove Batcher interface, rename to Batcher struct
427b2f1 matcher: clarify DestsIsTheInternet single-family semantics
64c398f metrics, policy/v2: drop unused scaffolding + nil-error returns
65880ec nix: disable external DERP URL fetch in VM test
37c6a9e nix: sync module options and descriptions with upstream nixpkgs
5e33259 nix: update flake inputs
2109674 nix: update flake inputs and dev shell tool versions
c5ef1d3 nix: upgrade dev shell to Python 3.14
f20bd0c node: implement disable key expiry via CLI and API
4d427cf noise: limit request body size to prevent unauthenticated OOM
8c6cb05 noise: pass context to sshActionFollowUp
e4e742c noise: pin outer RemoteAddr onto tunnel requests
5a7cafd noise: reject non-HEAD on PingResponseHandler
d66d3a4 oidc: add confirmation page for node registration
3d0f597 oidc: handle groups claim as string or array (FlexibleStringSlice)
7899049 oidc: render HTML error pages for browser-facing failures
adb9467 oidc: validate state parameter length in callback
107c2f2 policy, noise: implement SSH check action
c3df84e policy/matcher: include CapGrant.Dsts in match destinations
8358017 policy/v2,state,mapper: implement per-viewer via route steering
078b9e3 policy/v2: SaaS-derived compat tests for nodeAttrs
49744cd policy/v2: accept RFC 3986 bracketed IPv6 in ACL destinations
6c59d3e policy/v2: add SSH compatibility testdata from Tailscale SaaS
2cb914d policy/v2: add SaaS goldens for via-grant prefix containment
995ed01 policy/v2: add advertised routes to compat test topologies
0fa9dca policy/v2: add data-driven grants compatibility test with Tailscale SaaS captures
0acf09b policy/v2: add localpart:@Domain SSH user compilation
b668c7a policy/v2: add policy unmarshal tests for bracketed IPv6
c0774a7 policy/v2: add policytester captures recorded from Tailscale SaaS
7bc7011 policy/v2: add policytester compat test runner
26eebce policy/v2: add sshtester compat runner
834ac27 policy/v2: add subnet routes and exit node compatibility tests
5cd5e5d policy/v2: add unit tests for ViaRoutesForPeer
08d26e5 policy/v2: add unit tests for grant filter compilation helpers
6a55f7d policy/v2: add via exit steering golden captures and tests
affaa1a policy/v2: align SSH check action with SaaS wire format
d600090 policy/v2: align SSH rule validation with Tailscale
e4e209f policy/v2: canonicalize Protocol form during unmarshal
abd2b15 policy/v2: clean up dead error variables, stale TODO, and test skip reasons
2fb7169 policy/v2: convert ACL compat tests to data-driven format with Tailscale SaaS captures
500442c policy/v2: convert routes compat tests to data-driven format with Tailscale SaaS captures
013dea4 policy/v2: evaluate sshTests at write boundary
b29ae25 policy/v2: evaluate the tests block on user-initiated writes
f95b254 policy/v2: exclude exit routes from ReduceFilterRules
2e1a716 policy/v2: fix empty grants/acls returning FilterAllowAll
8573ff9 policy/v2: fix grant-only policies returning FilterAllowAll
c36cedc policy/v2: fix via grants in BuildPeerMap, MatchersForNode, and ViaRoutesForPeer
28be15f policy/v2: handle autogroup:internet in via grant compilation
0e3acdd policy/v2: implement CapGrant compilation with companion capabilities
687cf08 policy/v2: implement autogroup:danger-all support
4f040de policy/v2: implement grant validation rules matching Tailscale SaaS
54db47b policy/v2: implement via route compilation for grants
d5b2837 policy/v2: match default proto set for tests with no proto
e5fcd01 policy/v2: match via-grant destinations by prefix overlap
a7c9721 policy/v2: overhaul compat test infrastructure
a4f05b0 policy/v2: parse, validate, and compile nodeAttrs
ebe0f40 policy/v2: preserve non-wildcard source IPs alongside wildcard ranges
9f7aa55 policy/v2: refactor alias resolution to use ResolvedAddresses
dda3584 policy/v2: reorder ACL self grants to match Tailscale rule ordering
2b7f15a policy/v2: surface autogroup:internet via grants on exit nodes
e05f45c policy/v2: use approved node routes in wildcard SrcIPs
927ce41 policy/v2: use bare IPs in autogroup:self DstPorts
ccd284c policy/v2: use per-node filter compilation for via grants
6a0a297 policy/v2: validate sshTests at parse
f172dba policy/v2: validate tests block at parse boundary
b051e7b policy/v2: wire PolicyManager through compiledGrant
f735502 policy: add ICMP protocols to default and export constants When ACL rules don't specify a protocol, Headscale now defaults to [TCP, UDP, ICMP, ICMPv6] instead of just [TCP, UDP], matching Tailscale's behavior. Also export protocol number constants (ProtocolTCP, ProtocolUDP, etc.) for use in external test packages, renaming the string protocol constants to ProtoNameTCP, ProtoNameUDP, etc. to avoid conflicts. This resolves 78 ICMP-related TODOs in the Tailscale compatibility tests, reducing the total from 165 to 87.
53d17aa policy: add comprehensive Tailscale ACL compatibility tests Add extensive test coverage verifying Headscale's ACL policy behavior matches Tailscale's coordination server. Tests cover: - Source/destination resolution for users, groups, tags, hosts, IPs - autogroup:member, autogroup:tagged, autogroup:self behavior - Filter rule deduplication and merging semantics - Multi-rule interaction patterns - Error case validation Key behavioral differences documented: - Headscale creates separate filter entries per ACL rule; Tailscale merges rules with identical sources - Headscale deduplicates Dsts within a rule; Tailscale does not - Headscale does not validate autogroup:self source restrictions for ACL rules (only SSH rules); Tailscale rejects invalid sources Tests are based on real Tailscale coordination server responses captured from a test environment with 5 nodes (1 user-owned, 4 tagged).
835b7eb policy: autogroup:internet does not generate packet filters
14f833b policy: fix autogroup:self handling for tagged nodes Skip autogroup:self destination processing for tagged nodes since they can never match autogroup:self (which only applies to user-owned nodes). Also reorder the IsTagged() check to short-circuit before accessing User() to avoid potential nil pointer access on tagged nodes.
95b1fd6 policy: fix wildcard DstPorts format and proto:icmp handling
93d79d8 policy: include IPv6 in identity-based alias resolution
0b1727c policy: merge filter rules with identical SrcIPs and IPProto
c7a0ca7 policy: surface exit nodes via autogroup:internet (#3212)
29aa08d policy: update test expectations for merged filter rules
8baa14e policy: use CGNAT/ULA ranges for wildcard resolution Change Asterix.Resolve() to use Tailscale's CGNAT range (100.64.0.0/10) and ULA range (fd7a:115c:a1e0::/48) instead of all IPs (0.0.0.0/0 and ::/0). This better matches Tailscale's security model where wildcard () means "any node in the tailnet" rather than literally "any IP address on the internet". Updates #3036
08fe2e4 policy: use CIDR format for autogroup:self destinations
ebdbe03 policy: validate autogroup:self sources in ACL rules Tailscale validates that autogroup:self destinations in ACL rules can only be used when ALL sources are users, groups, autogroup:member, or wildcard (). Previously, Headscale only performed this validation for SSH rules. Add validateACLSrcDstCombination() to enforce that tags, autogroup:tagged, hosts, and raw IPs cannot be used as sources with autogroup:self destinations. Invalid policies like tag:client → autogroup:self:* are now rejected at validation time, matching Tailscale behavior. Wildcard () is allowed because autogroup:self evaluation narrows it per-node to only the node's own IPs.
ded51a4 policyutil: fix reduceCapGrantRule and add route reduction
fffc58b poll: fix poll test linter violations
4aca9d6 poll: stop stale map sessions through an explicit teardown hook
dc0e52a proto: add AuthRegister and AuthApprove RPCs
56146de proto: add CheckPolicy RPC
a8f7fed proto: add disable_expiry field to ExpireNodeRequest
786ce2d routes: add health dimension to HA primary route election
863fa2f servertest, integration: cover HA both-offline recovery
0378e2d servertest: add HA health probing tests
164d659 servertest: add TestViaGrantHACompat for via+HA compat tests
436d3db servertest: add dynamic HA failover tests
0431039 servertest: add regression tests for via grant filter rules
7d104b8 servertest: add via grant map compat tests
b5b786f servertest: cover broader-dst via grant in filter test
76ee293 servertest: cover via-grant exit-node visibility end-to-end
53b8a81 servertest: support tagged pre-auth keys in test clients
ff29af6 servertest: use memnet networking and add WithNodeExpiry option
7bab8da state, policy, noise: implement SSH check period auto-approval
e2f2f92 state, servertest: property-test HA election + invariant catalogue
3ca4ff8 state,servertest: add grant control plane tests and fix via route ReduceRoutes filtering
90e65cc state: add HA health prober
b1196ba state: add regression test for Node slice persistence
6337a3d state: apply default node key expiry on registration
a2c3ac0 state: auto-bump GivenName on collision and add SetGivenName
01e548e state: avoid nil deref in registration handlers when old user is missing
de6be71 state: batch HA probe results so dual-disconnect cannot flap primary
9f7c8e9 state: clear Unhealthy when node leaves HA candidate set
da927eb state: compute primary routes inside NodeStore snapshot
fb8eeca state: defer HA failover when probe target reconnected mid-cycle
66ac785 state: delete routes package, port primary route tests
842f362 state: drain pending pings on Close
ccddece state: fix GORM not persisting user_id=NULL on tagged node conversion
6ae1826 state: fix policy change race in UpdateNodeFromMapRequest
82bb433 state: fix routesChanged mutating input Hostinfo
c7630b5 state: leave prefix unmapped when all primary candidates unhealthy
de60982 state: note tagged-path coverage and self-healing behaviour for #3199
94ec607 state: per-goroutine deadline in HA probe cycle
0e10ca4 state: preserve nil expiry on user owned registration when no default is configured
3d5c0af state: preserve previous primary when all HA advertisers unhealthy
0d4f229 state: replace zcache with bounded LRU for auth cache
437754a state: switch consumers to NodeStore primary routes
978f1e3 state: tie-break ResolveNode by GivenName then lowest NodeID
380f531 state: trigger PolicyChange on every Connect and Disconnect
0e5569c templates: add detailsBox collapsible component
c15caff templates: add error box component and error page template
f3eb9a7 templates: escape query value in ping page
4a7e147 templates: generalise auth templates for web and OIDC
814226f templates: improve accessibility, dark mode, and typography
c9dbea5 templates: improve ping page spacing and design system usage
3918020 templates: use CSS variables in all shared components
de5b1ea templates: use table layout for registration confirm details
f34dec2 testcapture: add typed capture format package
f49c42e testdata: add SaaS captures for compat tests
30dce30 testdata: convert .json to .hujson with header comments
9482cdf testdata: drop unused uppercase SSH-*.hujson fixtures
835db97 testdata: strip unused fields from all test data files (23MB -> 4MB)
d243ada types,mapper,integration: enable Taildrive and add cap/drive grant lifecycle test
2a2d5c8 types/change: fix slice aliasing in Change.Merge
cef5338 types/change: panic on Merge with conflicting TargetNode values
64d13f7 types/config, types/node: model default-auto-update from auto_update.enabled
5ebc53c types/node, mapper, policy/v2: assemble self CapMap inside TailNode
5d502bf types/node, mapper: strip own IPv4 from emission when node has disable-ipv4 cap
8ea4cd3 types/node, policy/v2: drop taildrive caps from baseline emission
cf3d30b types: add MarshalZerologObject to domain types
1fe682b types: add Unhealthy and SessionEpoch fields to Node
4d0b273 types: add node.expiry config, deprecate oidc.expiry
610c1da types: avoid NodeView clone in CanAccess
b01e67e types: consider subnet routes as source identity in ACL matching
3529fe0 types: fix OIDC identifier path traversal dropping subject
4064f13 types: fix nil panics in Owner() and TailscaleUserID() for orphaned nodes
15c1cfd types: include ExitRoutes in HasNetworkChanges
942313a types: move DebugRoutes from routes to types
a3c4ad2 types: omit secret fields from JSON marshalling
7a20db9 types: persist Node JSON slices via named IsZero types
5802069 zlog: add utility package for safe and consistent logging
This is the end. You've seen all the release notes in this feed!
Curated by the Releasebot team
Releasebot is an aggregator of official release notes from hundreds of software vendors and thousands of sources.
Our editorial process involves the manual review and audit of release notes procured with the help of automated systems.