Skip to content

Releases: juanfont/headscale

v0.29.1

18 Jun 14:20

Choose a tag to compare

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'

v0.29.0

17 Jun 09:21

Choose a tag to compare

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": ["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.

#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...
Read more

v0.29.0-beta.4

15 Jun 18:57

Choose a tag to compare

v0.29.0-beta.4 Pre-release
Pre-release

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": ["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.

#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...
Read more

v0.29.0-beta.3

11 Jun 14:45

Choose a tag to compare

v0.29.0-beta.3 Pre-release
Pre-release

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": ["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.

#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...
Read more

v0.29.0-beta.2

29 May 20:21

Choose a tag to compare

v0.29.0-beta.2 Pre-release
Pre-release

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": ["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.

#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...
Read more

v0.29.0-beta.1

22 May 09:53

Choose a tag to compare

v0.29.0-beta.1 Pre-release
Pre-release

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": ["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.

#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...
Read more

v0.28.0

04 Feb 20:40

Choose a tag to compare

Minimum supported Tailscale client version: v1.74.0

Tags as identity

Tags are now implemented following the Tailscale model where tags and user ownership are mutually exclusive. Devices can be either
user-owned (authenticated via web/OIDC) or tagged (authenticated via tagged PreAuthKeys). Tagged devices receive their identity from
tags rather than users, making them suitable for servers and infrastructure. Applying a tag to a device removes user-based
ownership. See the Tailscale tags documentation for details on how tags work.

User-owned nodes can now request tags during registration using --advertise-tags. Tags are validated against the tagOwners policy
and applied at registration time. Tags can be managed via the CLI or API after registration. 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

Smarter map updates

The map update system has been rewritten to send smaller, partial updates instead of full network maps whenever possible. This reduces bandwidth usage and improves performance, especially for large networks. The system now properly tracks peer
changes and can send removal notifications when nodes are removed due to policy changes.
#2856 #2961

Pre-authentication key security improvements

Pre-authentication keys now use bcrypt hashing for improved security #2853. Keys
are stored as a prefix and bcrypt hash instead of plaintext. The full key is only displayed once at creation time. When listing keys,
only the prefix is shown (e.g., hskey-auth-{prefix}-***). All new keys use the format hskey-auth-{prefix}-{secret}. Legacy plaintext keys in the format {secret} will continue to work for backwards compatibility.

Web registration templates redesign

The OIDC callback and device registration web pages have been updated to use the Material for MkDocs design system from the official
documentation. The templates now use consistent typography, spacing, and colours across all registration flows.

Database migration support removed for pre-0.25.0 databases

Headscale no longer supports direct upgrades from databases created before version 0.25.0. Users on older versions must upgrade
sequentially through each stable release, selecting the latest patch version available for each minor release.

BREAKING

  • 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

    • API clients should use the Tags field instead of ValidTags
    • The headscale nodes list CLI command now always shows a Tags column and the --tags flag has been removed
  • PreAuthKey CLI: Commands now use ID-based operations instead of user+key combinations #2992

    • 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
    • PreAuthKey tagged devices ignore --advertise-tags from clients
    • User-owned nodes can use --advertise-tags if authorized by tagOwners policy
    • Tags can be managed via CLI (headscale nodes tag) or the SetTags API after registration
  • Database migration support removed for pre-0.25.0 databases #2883

    • If you are running a version older than 0.25.0, you must upgrade to 0.25.1 first, then upgrade to this release
    • See the upgrade path documentation for detailed guidance
    • In version 0.29, all migrations before 0.28.0 will also be removed
  • Remove ability to move nodes between users #2922

    • The headscale nodes move CLI command has been removed
    • The MoveNode API endpoint has been removed
    • Nodes are permanently associated with their user or tag at registration time
  • Add oidc.email_verified_required config option to control email verification requirement #2860

    • When 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.
    • When 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

    • Use autogroup:member for user-owned devices
    • Use autogroup:tagged for tagged devices
    • Use specific tags (e.g., tag:server) for targeted access

    Before:

    { "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:

    1. Tags cannot SSH to user-owned devices: SSH rules with tag:* or autogroup:tagged as source cannot have username destinations (e.g., alice@) or autogroup:member/autogroup:self as destination
    2. Username destinations require same-user source: If destination is a specific username (e.g., alice@), the source must be that exact same user only. Use autogroup:self for same-user SSH access instead

    Invalid 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@"], ...}

Changes

  • Smarter change notifications send partial map updates and node removals instead of full maps #2961
    • Send lightweight endpoint and DERP region updates instead of full maps #2856
  • Add NixOS module in repository for faster iteration #2857
  • Add favicon to webpages #2858
  • Redesign OIDC callback and registration web templates #2832
  • Reclaim IPs from the IP allocator when nodes are deleted #2831
  • Add bcrypt hashing for pre-authentication keys #2853
  • Add prefix to API keys (hskey-api-{prefix}-{secret}) #2853
  • Add prefix to registration keys for web authentication tracking (hskey-reg-{random}) #2853
  • Tags can now be tagOwner of other tags #2930
  • Add taildrop.enabled configuration option to enable/disable Taildrop file sharing #2955
  • Allow disabling the metrics server by setting empty metrics_listen_addr #2914
  • Log ACME/autocert e...
Read more

v0.28.0-beta.2

22 Jan 09:10

Choose a tag to compare

v0.28.0-beta.2 Pre-release
Pre-release

Minimum supported Tailscale client version: v1.74.0

Tags as identity

Tags are now implemented following the Tailscale model where tags and user ownership are mutually exclusive. Devices can be either
user-owned (authenticated via web/OIDC) or tagged (authenticated via tagged PreAuthKeys). Tagged devices receive their identity from
tags rather than users, making them suitable for servers and infrastructure. Applying a tag to a device removes user-based
ownership. See the Tailscale tags documentation for details on how tags work.

User-owned nodes can now request tags during registration using --advertise-tags. Tags are validated against the tagOwners policy
and applied at registration time. Tags can be managed via the CLI or API after registration. 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

Smarter map updates

The map update system has been rewritten to send smaller, partial updates instead of full network maps whenever possible. This reduces bandwidth usage and improves performance, especially for large networks. The system now properly tracks peer
changes and can send removal notifications when nodes are removed due to policy changes.
#2856 #2961

Pre-authentication key security improvements

Pre-authentication keys now use bcrypt hashing for improved security #2853. Keys
are stored as a prefix and bcrypt hash instead of plaintext. The full key is only displayed once at creation time. When listing keys,
only the prefix is shown (e.g., hskey-auth-{prefix}-***). All new keys use the format hskey-auth-{prefix}-{secret}. Legacy plaintext keys in the format {secret} will continue to work for backwards compatibility.

Web registration templates redesign

The OIDC callback and device registration web pages have been updated to use the Material for MkDocs design system from the official
documentation. The templates now use consistent typography, spacing, and colours across all registration flows.

Database migration support removed for pre-0.25.0 databases

Headscale no longer supports direct upgrades from databases created before version 0.25.0. Users on older versions must upgrade
sequentially through each stable release, selecting the latest patch version available for each minor release.

BREAKING

  • 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

    • API clients should use the Tags field instead of ValidTags
    • The headscale nodes list CLI command now always shows a Tags column and the --tags flag has been removed
  • PreAuthKey CLI: Commands now use ID-based operations instead of user+key combinations #2992

    • 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
    • PreAuthKey tagged devices ignore --advertise-tags from clients
    • User-owned nodes can use --advertise-tags if authorized by tagOwners policy
    • Tags can be managed via CLI (headscale nodes tag) or the SetTags API after registration
  • Database migration support removed for pre-0.25.0 databases #2883

    • If you are running a version older than 0.25.0, you must upgrade to 0.25.1 first, then upgrade to this release
    • See the upgrade path documentation for detailed guidance
    • In version 0.29, all migrations before 0.28.0 will also be removed
  • Remove ability to move nodes between users #2922

    • The headscale nodes move CLI command has been removed
    • The MoveNode API endpoint has been removed
    • Nodes are permanently associated with their user or tag at registration time
  • Add oidc.email_verified_required config option to control email verification requirement #2860

    • When 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.
    • When 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

    • Use autogroup:member for user-owned devices
    • Use autogroup:tagged for tagged devices
    • Use specific tags (e.g., tag:server) for targeted access

    Before:

    { "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:

    1. Tags cannot SSH to user-owned devices: SSH rules with tag:* or autogroup:tagged as source cannot have username destinations (e.g., alice@) or autogroup:member/autogroup:self as destination
    2. Username destinations require same-user source: If destination is a specific username (e.g., alice@), the source must be that exact same user only. Use autogroup:self for same-user SSH access instead

    Invalid 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@"], ...}

Changes

  • Smarter change notifications send partial map updates and node removals instead of full maps #2961
    • Send lightweight endpoint and DERP region updates instead of full maps #2856
  • Add NixOS module in repository for faster iteration #2857
  • Add favicon to webpages #2858
  • Redesign OIDC callback and registration web templates #2832
  • Reclaim IPs from the IP allocator when nodes are deleted #2831
  • Add bcrypt hashing for pre-authentication keys #2853
  • Add prefix to API keys (hskey-api-{prefix}-{secret}) #2853
  • Add prefix to registration keys for web authentication tracking (hskey-reg-{random}) #2853
  • Tags can now be tagOwner of other tags #2930
  • Add taildrop.enabled configuration option to enable/disable Taildrop file sharing #2955
  • Allow disabling the metrics server by setting empty metrics_listen_addr #2914
  • Log ACME/autoc...
Read more

v0.28.0-beta.1

18 Dec 11:55
7be2091

Choose a tag to compare

v0.28.0-beta.1 Pre-release
Pre-release

Minimum supported Tailscale client version: v1.74.0

Tags as identity

Tags are now implemented following the Tailscale model where tags and user ownership are mutually exclusive. Devices can be either
user-owned (authenticated via web/OIDC) or tagged (authenticated via tagged PreAuthKeys). Tagged devices receive their identity from
tags rather than users, making them suitable for servers and infrastructure. Applying a tag to a device removes user-based
ownership. See the Tailscale tags documentation for details on how tags work.

User-owned nodes can now request tags during registration using --advertise-tags. Tags are validated against the tagOwners policy
and applied at registration time. Tags can be managed via the CLI or API after registration.

Smarter map updates

The map update system has been rewritten to send smaller, partial updates instead of full network maps whenever possible. This reduces bandwidth usage and improves performance, especially for large networks. The system now properly tracks peer
changes and can send removal notifications when nodes are removed due to policy changes.
#2856 #2961

Pre-authentication key security improvements

Pre-authentication keys now use bcrypt hashing for improved security #2853. Keys
are stored as a prefix and bcrypt hash instead of plaintext. The full key is only displayed once at creation time. When listing keys,
only the prefix is shown (e.g., hskey-auth-{prefix}-***). All new keys use the format hskey-auth-{prefix}-{secret}. Legacy plaintext keys in the format {secret} will continue to work for backwards compatibility.

Web registration templates redesign

The OIDC callback and device registration web pages have been updated to use the Material for MkDocs design system from the official
documentation. The templates now use consistent typography, spacing, and colours across all registration flows.

Database migration support removed for pre-0.25.0 databases

Headscale no longer supports direct upgrades from databases created before version 0.25.0. Users on older versions must upgrade
sequentially through each stable release, selecting the latest patch version available for each minor release.

BREAKING

  • Tags: The gRPC SetTags endpoint now allows converting user-owned nodes to tagged nodes by setting tags. Once a node is tagged, it cannot be converted back to a user-owned node. #2885
  • Tags: Tags are now resolved from the node's stored Tags field only #2931
    • --advertise-tags is processed during registration, not on every policy evaluation
    • PreAuthKey tagged devices ignore --advertise-tags from clients
    • User-owned nodes can use --advertise-tags if authorized by tagOwners policy
    • Tags can be managed via CLI (headscale nodes tag) or the SetTags API after registration
  • Database migration support removed for pre-0.25.0 databases #2883
    • If you are running a version older than 0.25.0, you must upgrade to 0.25.1 first, then upgrade to this release
    • See the upgrade path documentation for detailed guidance
    • In version 0.29, all migrations before 0.28.0 will also be removed
  • Remove ability to move nodes between users #2922
    • The headscale nodes move CLI command has been removed
    • The MoveNode API endpoint has been removed
    • Nodes are permanently associated with their user at registration time

Changes

  • Smarter change notifications send partial map updates and node removals instead of full maps #2961
    • Send lightweight endpoint and DERP region updates instead of full maps #2856
  • Add oidc.email_verified_required config option to control email verification requirement #2860
    • When true (default), only verified emails can authenticate via OIDC with allowed_domains or allowed_users
    • When false, unverified emails are allowed for OIDC authentication
  • Add NixOS module in repository for faster iteration #2857
  • Add favicon to webpages #2858
  • Redesign OIDC callback and registration web templates #2832
  • Reclaim IPs from the IP allocator when nodes are deleted #2831
  • Add bcrypt hashing for pre-authentication keys #2853
  • Add prefix to API keys (hskey-api-{prefix}-{secret}) #2853
  • Add prefix to registration keys for web authentication tracking (hskey-reg-{random}) #2853
  • Tags can now be tagOwner of other tags #2930
  • Add taildrop.enabled configuration option to enable/disable Taildrop file sharing #2955
  • Allow disabling the metrics server by setting empty metrics_listen_addr #2914
  • Log ACME/autocert errors for easier debugging #2933
  • Improve CLI list output formatting #2951
  • Use Debian 13 distroless base images for containers #2944
  • Fix ACL policy not applied to new OIDC nodes until client restart #2890
  • Fix autogroup:self preventing visibility of nodes matched by other ACL rules #2882
  • Fix nodes being rejected after pre-authentication key expiration #2917
  • Fix list-routes command respecting identifier filter with JSON output #2927

Upgrade

Please follow the steps outlined in the upgrade guide to update your existing Headscale installation.

It's best to update from one stable version to the next (e.g., 0.24.0 → 0.25.1 → 0.26.1) in case you are multiple releases behind. You should always pick the latest available patch release.

Be sure to check the changelog above for version-specific upgrade instructions and breaking changes.

Backup Your Database

Always backup your database before upgrading. Here's how to backup a SQLite database:

# Stop headscale
systemctl stop headscale

# Backup sqlite database
cp /var/lib/headscale/db.sqlite /var/lib/headscale/db.sqlite.backup

# Backup sqlite WAL/SHM files (if they exist)
cp /var/lib/headscale/db.sqlite-wal /var/lib/headscale/db.sqlite-wal.backup
cp /var/lib/headscale/db.sqlite-shm /var/lib/headscale/db.sqlite-shm.backup

# Start headscale (migration will run automatically)
systemctl start headscale

Changelog

  • c460034 .github/workflows: prebuilt integration test artifacts (#2954)
  • 2c3c943 .github/workflows: split long TestAutoApproveMultiNetwork into multiple jobs
  • 5655ef8 AGENTS: golangci-lint from main, no "full matrix"
  • 249630b Add API documentation
  • 14af9b3 Add docs to manage headscale from another local user
  • 21af106 Containers should be read-only
  • a288f04 Dockerfile: align packages
  • 9c33cbf Exclude docs/ only for prettier pre-commit hook
  • 665cc44 Explicitly drop apt-get clean and use dist-clean
  • c5133ee Fix trailing whitespace
  • 5c6cd62 Legacy preauthkeys must be used as-is
  • e86d063 Mention /health instead of /windows
  • f00c412 Move static doc assets into docs/assets
  • 2010805 Provide Headscale's favicon at its expected place
  • 72d5fd0 Remove duplicated documentation and link to getting started instead
  • e0c9e18 Update OIDC documentation for allowed groups filter
  • 9b327f6 Update pre-commit-hooks
  • 6359511 Use debian13 distroless images
  • bba91a8 Use lists for integration docs
  • 218a8db add favicon to webpages (#2858)
  • 6d24afb add pre-commit hooks, move claude to agents. (#2877)
  • 0e16730 all: remove deadcode (#2952)
  • 56bec66 app: only wire up debug server if set
  • 3cf2d71 auth: ensure machines are allowed in when pak change (#2917)
  • f3767dd batcher: ensure removal from batcher
  • 616c0e8 batcher: fix closed panic
  • 7fb0f9a batcher: send endpoint and derp only updates. (#2856)
  • e875361...
Read more

v0.27.2-rc.1

30 Nov 18:41
c6d399a

Choose a tag to compare

v0.27.2-rc.1 Pre-release
Pre-release

Changes

  • Fix ACL policy not applied to new OIDC nodes until client restart
    #2890
  • Fix autogroup:self preventing visibility of nodes matched by other ACL rules
    #2882
  • Fix nodes being rejected after pre-authentication key expiration
    #2917

Upgrade

Please follow the steps outlined in the upgrade guide to update your existing Headscale installation.

It's best to update from one stable version to the next (e.g., 0.24.0 → 0.25.1 → 0.26.1) in case you are multiple releases behind. You should always pick the latest available patch release.

Be sure to check the changelog above for version-specific upgrade instructions and breaking changes.

Backup Your Database

Always backup your database before upgrading. Here's how to backup a SQLite database:

# Stop headscale
systemctl stop headscale

# Backup sqlite database
cp /var/lib/headscale/db.sqlite /var/lib/headscale/db.sqlite.backup

# Backup sqlite WAL/SHM files (if they exist)
cp /var/lib/headscale/db.sqlite-wal /var/lib/headscale/db.sqlite-wal.backup
cp /var/lib/headscale/db.sqlite-shm /var/lib/headscale/db.sqlite-shm.backup

# Start headscale (migration will run automatically)
systemctl start headscale

Changelog

  • 7f1631c auth: ensure machines are allowed in when pak change (#2917)
  • c6d399a changelog: prep for 0.27.2 rc
  • 7e8cee6 chore: fix filterHash to work with autogroup:self in the acls (#2882)
  • 4fe5cbe hscontrol/oidc: fix ACL policy not applied to new OIDC nodes (#2890)