NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
Go modules · #36 by repository stars
Last release 14 days ago
23 Sep 2026
Ships fairly regularly
a new release about every 3 weeks
Most releases are documented
notes for 38 of the last 60 stable releases
Nothing withdrawn
no release was ever pulled
6 years old
570 releases · first in 2020
One column per quarter.
Minimum supported Tailscale client version: v1.80.0
Minimum supported Tailscale client version: v1.80.0
server_url so they survive a TLS-terminating proxy, a callback state is single-use, and an invalid oidc.issuer or a missing oidc.client_id/oidc.client_secret now fails at startup #3334OPTIONS requests, so http_requests_total and http_request_duration_seconds now cover regular traffic #3414headscale users rename sending the raw --identifier flag value instead of the matched user's identifier, so renaming by name works again #3442headscale_mapper_changes_dropped_total and headscale_ha_health_updates_total #3417 #3450headscale_nodestore_snapshot_builds_total #3417 #3450badNonce reply, because the error logging middleware drained the response body the acme client needs to detect it #3461#-prefixed metadata fields being rejected outside acls, so policy editors can store metadata in grants, SSH rules and nodeAttrs #3481suggest-exit-node peer attribute rather than the advertised 0.0.0.0/0 routes #3487via grant; peer visibility now comes from the peer map alone #3409Please follow the steps outlined in the upgrade guide to update your existing Headscale installation.
Minimum supported Tailscale client version: v1.80.0
Minimum supported Tailscale client version: v1.80.0
tailscale logout, unable to re-authenticate #3394--advertise-tags being rejected when the authenticating user owns the tags #3394401 registration timed out when auth completes as the request context expires #3392/key requests below the supported capability version floor, matching /ts2021 #3391Please follow the steps outlined in the upgrade guide to update your existing Headscale installation.
Fix map generation serializing on the policy lock, so a mass reconnect on autogroup:self , via or relay policies no longer stalls clients into unexpec
autogroup:self, via or relay policies no longer stalls clients into unexpected EOF retry loops #3358/ts2021 rejecting the WebSocket GET upgrade with 405, which prevented Tailscale JS/WASM control clients from connecting #3359Please follow the steps outlined in the upgrade guide to update your existing Headscale installation.
Nothing published for this version
Minimum supported Tailscale client version: v1.80.0
Minimum supported Tailscale client version: v1.80.0
tags='null' losing their assigned user on upgrade #3325Please follow the steps outlined in the upgrade guide to update your existing Headscale installation.
Nothing published for this version
Nothing published for this version
headscale auth register --auth-id <id> --user <user> registers a node (replaces deprecated headscale nodes register )
Minimum supported Tailscale client version: v1.80.0
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 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 requestheadscale auth register --auth-id <id> --user <user> registers a node (replaces deprecated headscale nodes register)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.
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.
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
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.
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 *.
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.
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.
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 |
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
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*) 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
* means "any node in the tailnet" rather than "any IP address"autogroup:danger-all as a source, or explicit CIDR ranges as destinations #2180autogroup:danger-all can only be used as a source; it cannot be used as a destinationprefixes.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 *autogroup:self source restrictions matching Tailscale behavior - tags, hosts, and IPs are rejected as sources for autogroup:self destinations #3036
autogroup:self destinations will now fail validationproto:icmp protocol name now only includes ICMPv4 (protocol 1), matching Tailscale behavior #3036
proto:icmp included both ICMPv4 and ICMPv6proto:ipv6-icmp or protocol number 58 explicitly for ICMPv6The 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.
headscale nodes register is deprecated in favour of headscale auth register --auth-id <id> --user <user> #1850
*) source in ACLs now using actually-approved subnet routes instead of autoApprover policy prefixes #2180* in the same ACL rule #2180src=, dst=) and are more descriptive #2180user@ tokens match multiple DB users; rename the duplicate via headscale users rename to load #3160tests block on user-initiated writes across both acls and grants; reject policies whose tests fail (beta) #1803ip, app, and via fields #2180autogroup:danger-all as a source-only autogroup resolving to all IP addresses #2180cap/drive) and peer relay (cap/relay) with automatic companion capabilities #2180via tags control which subnet router or exit node handles traffic for each group of viewers #2180cap/drive grants #2180localpart:*@<domain> in SSH rule users field, mapping each matching user's email local-part as their OS username #3091check action support with OIDC and CLI-based approval flows #1850headscale auth register, headscale auth approve, and headscale auth reject CLI commands #1850headscale nodes register --key in favour of headscale auth register --auth-id #1850headscale policy check --bypass-grpc-and-access-database-directly validates user@ tokens against the live user database #3160--namespace flag from nodes list, nodes register, and debug create-node commands (use --user instead) #3093namespace/ns command aliases for users and machine/machines aliases for nodes #3093DestroyUser deleting all pre-auth keys in the database instead of only the target user's keys #3155headscale 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 #1803auth related routes. The auth/register endpoint now expects data as JSON #1850AuthSuccess and AuthWeb components #1850AuthVerdict type, supporting registration, reauthentication, and SSH checks #1850node.expiry configuration option to set a default node key expiry for nodes registered via auth key #3122
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_timeouttrusted_proxies to gate True-Client-IP / X-Real-IP / X-Forwarded-For (previously honoured from any client) #3268Pass, ClientSecret, APIKey) from /debug/config JSON output #3180statsviz through tsweb.Protected #3180config-example.yaml as example for the debian package #31860001-01-01 00:00:00 in the database instead of NULL #3199tailscaled 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 #3197nodes.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 #3284trusted_proxies configuration option #3292Please follow the steps outlined in the upgrade guide to update your existing Headscale installation.
headscale auth register --auth-id <id> --user <user> registers a node (replaces deprecated headscale nodes register )
Minimum supported Tailscale client version: v1.80.0
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 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 requestheadscale auth register --auth-id <id> --user <user> registers a node (replaces deprecated headscale nodes register)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.
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.
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
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.
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": ["alice@example.com"], "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.
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.
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 |
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
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*) 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
* means "any node in the tailnet" rather than "any IP address"autogroup:danger-all as a source, or explicit CIDR ranges as destinations #2180autogroup:danger-all can only be used as a source; it cannot be used as a destinationprefixes.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 *autogroup:self source restrictions matching Tailscale behavior - tags, hosts, and IPs are rejected as sources for autogroup:self destinations #3036
autogroup:self destinations will now fail validationproto:icmp protocol name now only includes ICMPv4 (protocol 1), matching Tailscale behavior #3036
proto:icmp included both ICMPv4 and ICMPv6proto:ipv6-icmp or protocol number 58 explicitly for ICMPv6The 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.
headscale nodes register is deprecated in favour of headscale auth register --auth-id <id> --user <user> #1850
*) source in ACLs now using actually-approved subnet routes instead of autoApprover policy prefixes #2180* in the same ACL rule #2180src=, dst=) and are more descriptive #2180user@ tokens match multiple DB users; rename the duplicate via headscale users rename to load #3160tests block on user-initiated writes across both acls and grants; reject policies whose tests fail (beta) #1803ip, app, and via fields #2180autogroup:danger-all as a source-only autogroup resolving to all IP addresses #2180cap/drive) and peer relay (cap/relay) with automatic companion capabilities #2180via tags control which subnet router or exit node handles traffic for each group of viewers #2180cap/drive grants #2180localpart:*@<domain> in SSH rule users field, mapping each matching user's email local-part as their OS username #3091check action support with OIDC and CLI-based approval flows #1850headscale auth register, headscale auth approve, and headscale auth reject CLI commands #1850headscale nodes register --key in favour of headscale auth register --auth-id #1850headscale policy check --bypass-grpc-and-access-database-directly validates user@ tokens against the live user database #3160--namespace flag from nodes list, nodes register, and debug create-node commands (use --user instead) #3093namespace/ns command aliases for users and machine/machines aliases for nodes #3093DestroyUser deleting all pre-auth keys in the database instead of only the target user's keys #3155headscale 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 #1803auth related routes. The auth/register endpoint now expects data as JSON #1850AuthSuccess and AuthWeb components #1850AuthVerdict type, supporting registration, reauthentication, and SSH checks #1850node.expiry configuration option to set a default node key expiry for nodes registered via auth key #3122
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_timeouttrusted_proxies to gate True-Client-IP / X-Real-IP / X-Forwarded-For (previously honoured from any client) #3268Pass, ClientSecret, APIKey) from /debug/config JSON output #3180statsviz through tsweb.Protected #3180config-example.yaml as example for the debian package #31860001-01-01 00:00:00 in the database instead of NULL #3199tailscaled 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 #3197nodes.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 #3284trusted_proxies configuration option #3292Please follow the steps outlined in the upgrade guide to update your existing Headscale installation.
Nothing published for this version
headscale auth register --auth-id <id> --user <user> registers a node (replaces deprecated headscale nodes register )
Minimum supported Tailscale client version: v1.80.0
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 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 requestheadscale auth register --auth-id <id> --user <user> registers a node (replaces deprecated headscale nodes register)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.
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.
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
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.
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": ["alice@example.com"], "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.
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.
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 |
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
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*) 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
* means "any node in the tailnet" rather than "any IP address"autogroup:danger-all as a source, or explicit CIDR ranges as destinations #2180autogroup:danger-all can only be used as a source; it cannot be used as a destinationprefixes.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 *autogroup:self source restrictions matching Tailscale behavior - tags, hosts, and IPs are rejected as sources for autogroup:self destinations #3036
autogroup:self destinations will now fail validationproto:icmp protocol name now only includes ICMPv4 (protocol 1), matching Tailscale behavior #3036
proto:icmp included both ICMPv4 and ICMPv6proto:ipv6-icmp or protocol number 58 explicitly for ICMPv6The 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.
headscale nodes register is deprecated in favour of headscale auth register --auth-id <id> --user <user> #1850
*) source in ACLs now using actually-approved subnet routes instead of autoApprover policy prefixes #2180* in the same ACL rule #2180src=, dst=) and are more descriptive #2180user@ tokens match multiple DB users; rename the duplicate via headscale users rename to load #3160tests block on user-initiated writes across both acls and grants; reject policies whose tests fail (beta) #1803ip, app, and via fields #2180autogroup:danger-all as a source-only autogroup resolving to all IP addresses #2180cap/drive) and peer relay (cap/relay) with automatic companion capabilities #2180via tags control which subnet router or exit node handles traffic for each group of viewers #2180cap/drive grants #2180localpart:*@<domain> in SSH rule users field, mapping each matching user's email local-part as their OS username #3091check action support with OIDC and CLI-based approval flows #1850headscale auth register, headscale auth approve, and headscale auth reject CLI commands #1850headscale nodes register --key in favour of headscale auth register --auth-id #1850headscale policy check --bypass-grpc-and-access-database-directly validates user@ tokens against the live user database #3160--namespace flag from nodes list, nodes register, and debug create-node commands (use --user instead) #3093namespace/ns command aliases for users and machine/machines aliases for nodes #3093DestroyUser deleting all pre-auth keys in the database instead of only the target user's keys #3155headscale 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 #1803auth related routes. The auth/register endpoint now expects data as JSON #1850AuthSuccess and AuthWeb components #1850AuthVerdict type, supporting registration, reauthentication, and SSH checks #1850node.expiry configuration option to set a default node key expiry for nodes registered via auth key #3122
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_timeouttrusted_proxies to gate True-Client-IP / X-Real-IP / X-Forwarded-For (previously honoured from any client) #3268Pass, ClientSecret, APIKey) from /debug/config JSON output #3180statsviz through tsweb.Protected #3180config-example.yaml as example for the debian package #31860001-01-01 00:00:00 in the database instead of NULL #3199tailscaled 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 #3197nodes.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 #3284trusted_proxies configuration option #3292Please follow the steps outlined in the upgrade guide to update your existing Headscale installation.
Nothing published for this version
headscale auth register --auth-id <id> --user <user> registers a node (replaces deprecated headscale nodes register )
Minimum supported Tailscale client version: v1.80.0
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 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 requestheadscale auth register --auth-id <id> --user <user> registers a node (replaces deprecated headscale nodes register)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.
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.
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
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.
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": ["alice@example.com"], "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.
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.
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 |
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
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*) 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
* means "any node in the tailnet" rather than "any IP address"autogroup:danger-all as a source, or explicit CIDR ranges as destinations #2180autogroup:danger-all can only be used as a source; it cannot be used as a destinationprefixes.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 *autogroup:self source restrictions matching Tailscale behavior - tags, hosts, and IPs are rejected as sources for autogroup:self destinations #3036
autogroup:self destinations will now fail validationproto:icmp protocol name now only includes ICMPv4 (protocol 1), matching Tailscale behavior #3036
proto:icmp included both ICMPv4 and ICMPv6proto:ipv6-icmp or protocol number 58 explicitly for ICMPv6The 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.
headscale nodes register is deprecated in favour of headscale auth register --auth-id <id> --user <user> #1850
*) source in ACLs now using actually-approved subnet routes instead of autoApprover policy prefixes #2180* in the same ACL rule #2180src=, dst=) and are more descriptive #2180user@ tokens match multiple DB users; rename the duplicate via headscale users rename to load #3160tests block on user-initiated writes across both acls and grants; reject policies whose tests fail (beta) #1803ip, app, and via fields #2180autogroup:danger-all as a source-only autogroup resolving to all IP addresses #2180cap/drive) and peer relay (cap/relay) with automatic companion capabilities #2180via tags control which subnet router or exit node handles traffic for each group of viewers #2180cap/drive grants #2180localpart:*@<domain> in SSH rule users field, mapping each matching user's email local-part as their OS username #3091check action support with OIDC and CLI-based approval flows #1850headscale auth register, headscale auth approve, and headscale auth reject CLI commands #1850headscale nodes register --key in favour of headscale auth register --auth-id #1850headscale policy check --bypass-grpc-and-access-database-directly validates user@ tokens against the live user database #3160--namespace flag from nodes list, nodes register, and debug create-node commands (use --user instead) #3093namespace/ns command aliases for users and machine/machines aliases for nodes #3093DestroyUser deleting all pre-auth keys in the database instead of only the target user's keys #3155headscale 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 #1803auth related routes. The auth/register endpoint now expects data as JSON #1850AuthSuccess and AuthWeb components #1850AuthVerdict type, supporting registration, reauthentication, and SSH checks #1850node.expiry configuration option to set a default node key expiry for nodes registered via auth key #3122
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_timeouttrusted_proxies to gate True-Client-IP / X-Real-IP / X-Forwarded-For (previously honoured from any client) #3268Pass, ClientSecret, APIKey) from /debug/config JSON output #3180statsviz through tsweb.Protected #3180config-example.yaml as example for the debian package #31860001-01-01 00:00:00 in the database instead of NULL #3199
0001-01-01 00:00:00 are not backfilled; they clear themselves the next time the node re-registerstailscaled 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 #3197Please follow the steps outlined in the upgrade guide to update your existing Headscale installation.
Nothing published for this version
Nothing published for this version
f27298c changelog: document wildcard CGNAT range change Add breaking change entry for the wildcard resolution change to use CGNAT/ULA ranges instead o…
Minimum supported Tailscale client version: v1.80.0
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 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 requestheadscale auth register --auth-id <id> --user <user> registers a node (replaces deprecated headscale nodes register)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.
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.
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
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.
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": ["alice@example.com"], "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.
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.
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 |
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
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*) 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
* means "any node in the tailnet" rather than "any IP address"autogroup:danger-all as a source, or explicit CIDR ranges as destinations #2180autogroup:danger-all can only be used as a source; it cannot be used as a destinationprefixes.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 *autogroup:self source restrictions matching Tailscale behavior - tags, hosts, and IPs are rejected as sources for autogroup:self destinations #3036
autogroup:self destinations will now fail validationproto:icmp protocol name now only includes ICMPv4 (protocol 1), matching Tailscale behavior #3036
proto:icmp included both ICMPv4 and ICMPv6proto:ipv6-icmp or protocol number 58 explicitly for ICMPv6The 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.
headscale nodes register is deprecated in favour of headscale auth register --auth-id <id> --user <user> #1850
*) source in ACLs now using actually-approved subnet routes instead of autoApprover policy prefixes #2180* in the same ACL rule #2180src=, dst=) and are more descriptive #2180user@ tokens match multiple DB users; rename the duplicate via headscale users rename to load #3160tests block on user-initiated writes across both acls and grants; reject policies whose tests fail (beta) #1803ip, app, and via fields #2180autogroup:danger-all as a source-only autogroup resolving to all IP addresses #2180cap/drive) and peer relay (cap/relay) with automatic companion capabilities #2180via tags control which subnet router or exit node handles traffic for each group of viewers #2180cap/drive grants #2180localpart:*@<domain> in SSH rule users field, mapping each matching user's email local-part as their OS username #3091check action support with OIDC and CLI-based approval flows #1850headscale auth register, headscale auth approve, and headscale auth reject CLI commands #1850headscale nodes register --key in favour of headscale auth register --auth-id #1850headscale policy check --bypass-grpc-and-access-database-directly validates user@ tokens against the live user database #3160--namespace flag from nodes list, nodes register, and debug create-node commands (use --user instead) #3093namespace/ns command aliases for users and machine/machines aliases for nodes #3093DestroyUser deleting all pre-auth keys in the database instead of only the target user's keys #3155headscale 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 #1803auth related routes. The auth/register endpoint now expects data as JSON #1850AuthSuccess and AuthWeb components #1850AuthVerdict type, supporting registration, reauthentication, and SSH checks #1850node.expiry configuration option to set a default node key expiry for nodes registered via auth key #3122
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_timeouttrusted_proxies to gate True-Client-IP / X-Real-IP / X-Forwarded-For (previously honoured from any client) #3268Pass, ClientSecret, APIKey) from /debug/config JSON output #3180statsviz through tsweb.Protected #3180config-example.yaml as example for the debian package #31860001-01-01 00:00:00 in the database instead of NULL #3199
0001-01-01 00:00:00 are not backfilled; they clear themselves the next time the node re-registerstailscaled 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 #3197Please follow the steps outlined in the upgrade guide to update your existing Headscale installation.
Note truncated.
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Minimum supported Tailscale client version: v1.74.0
Minimum supported Tailscale client version: v1.74.0
Tags are now implemented following the Tailscale model where tags and user ownership are mutually exclusive. Devices can be either user-owned (authenticated via web/OIDC) or tagged (authenticated via tagged PreAuthKeys). Tagged devices receive their identity from tags rather than users, making them suitable for servers and infrastructure. Applying a tag to a device removes user-based ownership. See the Tailscale tags documentation for details on how tags work.
User-owned nodes can now request tags during registration using --advertise-tags. Tags are validated against the tagOwners policy
and applied at registration time. Tags can be managed via the CLI or API after registration. Tagged nodes can return to user-owned
by re-authenticating with tailscale up --advertise-tags= --force-reauth.
A one-time migration will validate and migrate any RequestTags (stored in hostinfo) to the tags column. Tags are validated against
your policy's tagOwners rules during migration. #3011
The map update system has been rewritten to send smaller, partial updates instead of full network maps whenever possible. This reduces bandwidth usage and improves performance, especially for large networks. The system now properly tracks peer changes and can send removal notifications when nodes are removed due to policy changes. #2856 #2961
Pre-authentication keys now use bcrypt hashing for improved security #2853. Keys
are stored as a prefix and bcrypt hash instead of plaintext. The full key is only displayed once at creation time. When listing keys,
only the prefix is shown (e.g., hskey-auth-{prefix}-***). All new keys use the format hskey-auth-{prefix}-{secret}. Legacy plaintext keys in the format {secret} will continue to work for backwards compatibility.
The OIDC callback and device registration web pages have been updated to use the Material for MkDocs design system from the official documentation. The templates now use consistent typography, spacing, and colours across all registration flows.
Headscale no longer supports direct upgrades from databases created before version 0.25.0. Users on older versions must upgrade sequentially through each stable release, selecting the latest patch version available for each minor release.
API: The Node message in the gRPC/REST API has been simplified - the ForcedTags, InvalidTags, and ValidTags fields have been removed and replaced with a single Tags field that contains the node's applied tags #2993
Tags field instead of ValidTagsheadscale nodes list CLI command now always shows a Tags column and the --tags flag has been removedPreAuthKey CLI: Commands now use ID-based operations instead of user+key combinations #2992
headscale preauthkeys create no longer requires --user flag (optional for tracking creation)headscale preauthkeys list lists all keys (no longer filtered by user)headscale preauthkeys expire --id <ID> replaces --user <USER> <KEY>headscale preauthkeys delete --id <ID> replaces --user <USER> <KEY>Before:
headscale preauthkeys create --user 1 --reusable --tags tag:server
headscale preauthkeys list --user 1
headscale preauthkeys expire --user 1 <KEY>
headscale preauthkeys delete --user 1 <KEY>
After:
headscale preauthkeys create --reusable --tags tag:server
headscale preauthkeys list
headscale preauthkeys expire --id 123
headscale preauthkeys delete --id 123
Tags: The gRPC SetTags endpoint now allows converting user-owned nodes to tagged nodes by setting tags. #2885
Tags: Tags are now resolved from the node's stored Tags field only #2931
--advertise-tags is processed during registration, not on every policy evaluation--advertise-tags from clients--advertise-tags if authorized by tagOwners policyheadscale nodes tag) or the SetTags API after registrationDatabase migration support removed for pre-0.25.0 databases #2883
Remove ability to move nodes between users #2922
headscale nodes move CLI command has been removedMoveNode API endpoint has been removedAdd oidc.email_verified_required config option to control email verification requirement #2860
true (default), only verified emails can authenticate via OIDC in conjunction with oidc.allowed_domains or
oidc.allowed_users. Previous versions allowed to authenticate with an unverified email but did not store the email
address in the user profile. This is now rejected during authentication with an unverified email error.false, unverified emails are allowed for OIDC authentication and the email address is stored in the user
profile regardless of its verification state.SSH Policy: Wildcard (*) is no longer supported as an SSH destination #3009
autogroup:member for user-owned devicesautogroup:tagged for tagged devicestag:server) for targeted accessBefore:
{ "action": "accept", "src": ["group:admins"], "dst": ["*"], "users": ["root"] }
After:
{ "action": "accept", "src": ["group:admins"], "dst": ["autogroup:member", "autogroup:tagged"], "users": ["root"] }
SSH Policy: SSH source/destination validation now enforces Tailscale's security model #3010
Per Tailscale SSH documentation, the following rules are now enforced:
tag:* or autogroup:tagged as source cannot have username destinations (e.g., alice@) or autogroup:member/autogroup:self as destinationalice@), the source must be that exact same user only. Use autogroup:self for same-user SSH access insteadInvalid policies now rejected at load time:
// INVALID: tag source to user destination
{"src": ["tag:server"], "dst": ["alice@"], ...}
// INVALID: autogroup:tagged to autogroup:member
{"src": ["autogroup:tagged"], "dst": ["autogroup:member"], ...}
// INVALID: group to specific user (use autogroup:self instead)
{"src": ["group:admins"], "dst": ["alice@"], ...}
Valid patterns:
// Users/groups can SSH to their own devices via autogroup:self
{"src": ["group:admins"], "dst": ["autogroup:self"], ...}
// Users/groups can SSH to tagged devices
{"src": ["group:admins"], "dst": ["autogroup:tagged"], ...}
// Tagged devices can SSH to other tagged devices
{"src": ["autogroup:tagged"], "dst": ["autogroup:tagged"], ...}
// Same user can SSH to their own devices
{"src": ["alice@"], "dst": ["alice@"], ...}
hskey-api-{prefix}-{secret}) #2853hskey-reg-{random}) #2853taildrop.enabled configuration option to enable/disable Taildrop file sharing #2955metrics_listen_addr #2914--id flag to expire/delete commands as alternative to --prefix for API Keys #3016Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Minimum supported Tailscale client version: v1.64.0
Minimum supported Tailscale client version: v1.64.0
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Minimum supported Tailscale client version: v1.64.0
Minimum supported Tailscale client version: v1.64.0
This release includes a significant database migration that addresses
longstanding issues with the database schema and data integrity that has
accumulated over the years. The migration introduces a schema.sql file as the
source of truth for the expected database schema to ensure new migrations that
will cause divergence does not occur again.
These issues arose from a combination of factors discovered over time: SQLite foreign keys not being enforced for many early versions, all migrations being run in one large function until version 0.23.0, and inconsistent use of GORM's AutoMigrate feature. Moving forward, all new migrations will be explicit SQL operations rather than relying on GORM AutoMigrate, and foreign keys will be enforced throughout the migration process.
We are only improving SQLite databases with this change - PostgreSQL databases are not affected.
Please read the PR description for more technical details about the issues and solutions.
SQLite Database Backup Example:
# Stop headscale
systemctl stop headscale
# Backup sqlite database
cp /var/lib/headscale/db.sqlite /var/lib/headscale/db.sqlite.backup
# Backup sqlite WAL/SHM files (if they exist)
cp /var/lib/headscale/db.sqlite-wal /var/lib/headscale/db.sqlite-wal.backup
cp /var/lib/headscale/db.sqlite-shm /var/lib/headscale/db.sqlite-shm.backup
# Start headscale (migration will run automatically)
systemctl start headscale
The default DERPMap update frequency has been changed from 24 hours to 3 hours.
If you set the derp.update_frequency configuration option, it is recommended
to change it to 3h to ensure that the headscale instance gets the latest
DERPMap updates when upstream is changed.
This release adds support for the three missing autogroups: self
(experimental), member, and tagged. Please refer to the
documentation
for a detailed explanation.
autogroup:self is marked as experimental and should be used with caution, but
we need help testing it. Experimental here means two things; first, generating
the packet filter from policies that use autogroup:self is very expensive, and
it might perform, or straight up not work on Headscale installations with a
large number of nodes. Second, the implementation might have bugs or edge cases
we are not aware of, meaning that nodes or users might gain more access than
expected. Please report bugs.
Under the hood, we have added a new datastructure to store nodes in memory. This
datastructure is called NodeStore and aims to reduce the reading and writing
of nodes to the database layer. We have not benchmarked it, but expect it to
improve performance for read heavy workloads. We think of it as, "worst case" we
have moved the bottle neck somewhere else, and "best case" we should see a good
improvement in compute resource usage at the expense of memory usage. We are
quite excited for this change and think it will make it easier for us to improve
the code base over time and make it more correct and efficient.
invalid-XXXXXX format
my-laptop → accepted as-isMy-Laptop → my-laptop (lowercased)my_laptop → invalid-a1b2c3 (underscore not allowed)test@host → invalid-d4e5f6 (@ not allowed)laptop-🚀 → invalid-j1k2l3 (emoji not allowed)autogroup:member, autogroup:tagged #2572noise config #2658/robots.txt endpoint to avoid crawlers #2643autogroup:self #2789Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Ensure nodes are matching both node key and machine key when connecting. #2642
Nothing published for this version
Nothing published for this version
Nothing published for this version
Your coding agent can read these notes before it upgrades. Set up the MCP server →
{ "randomizeClientPort": true, "nodeAttrs": [ { "target": ["autogroup:tagged"], "attr": ["disable-captive-portal-detection"] }, { "target": ["alice@example.com"], "attr": ["nextdns:abc123"] }, ], }