Headscale

Developer Tools

An open-source implementation of the Tailscale control server.

Latest v0.29.3 · by Juan FontWebsitejuanfont/headscale

Release activity

Release activity — 10 releases across 10 days since Jan 22, 2026. Each cell is one day; darker means more releases that day. Nothing is recorded before Jan 22, 2026. Older weeks are hidden at this screen width.
MayJunJulAug
SundayNo releases on Apr 19, 2026No releases on Apr 26, 2026No releases on May 3, 2026No releases on May 10, 2026No releases on May 17, 2026No releases on May 24, 2026No releases on May 31, 2026No releases on Jun 7, 2026No releases on Jun 14, 2026No releases on Jun 21, 2026No releases on Jun 28, 2026No releases on Jul 5, 2026No releases on Jul 12, 2026No releases on Jul 19, 2026No releases on Jul 26, 2026No releases on Aug 2, 2026
MondayNo releases on Apr 20, 2026No releases on Apr 27, 2026No releases on May 4, 2026No releases on May 11, 2026No releases on May 18, 2026No releases on May 25, 2026No releases on Jun 1, 2026No releases on Jun 8, 20261 release on Jun 15, 2026No releases on Jun 22, 2026No releases on Jun 29, 2026No releases on Jul 6, 2026No releases on Jul 13, 2026No releases on Jul 20, 2026No releases on Jul 27, 2026No releases on Aug 3, 2026
TuesdayNo releases on Apr 21, 2026No releases on Apr 28, 2026No releases on May 5, 2026No releases on May 12, 2026No releases on May 19, 2026No releases on May 26, 2026No releases on Jun 2, 2026No releases on Jun 9, 2026No releases on Jun 16, 2026No releases on Jun 23, 2026No releases on Jun 30, 2026No releases on Jul 7, 2026No releases on Jul 14, 2026No releases on Jul 21, 2026No releases on Jul 28, 2026No releases on Aug 4, 2026
WednesdayNo releases on Apr 22, 2026No releases on Apr 29, 2026No releases on May 6, 2026No releases on May 13, 2026No releases on May 20, 2026No releases on May 27, 2026No releases on Jun 3, 2026No releases on Jun 10, 20261 release on Jun 17, 2026No releases on Jun 24, 20261 release on Jul 1, 2026No releases on Jul 8, 2026No releases on Jul 15, 2026No releases on Jul 22, 20261 release on Jul 29, 2026No releases on Aug 5, 2026
ThursdayNo releases on Apr 23, 2026No releases on Apr 30, 2026No releases on May 7, 2026No releases on May 14, 2026No releases on May 21, 2026No releases on May 28, 2026No releases on Jun 4, 20261 release on Jun 11, 20261 release on Jun 18, 2026No releases on Jun 25, 2026No releases on Jul 2, 2026No releases on Jul 9, 2026No releases on Jul 16, 2026No releases on Jul 23, 2026No releases on Jul 30, 2026No releases on Aug 6, 2026
FridayNo releases on Apr 24, 2026No releases on May 1, 2026No releases on May 8, 2026No releases on May 15, 20261 release on May 22, 20261 release on May 29, 2026No releases on Jun 5, 2026No releases on Jun 12, 2026No releases on Jun 19, 2026No releases on Jun 26, 2026No releases on Jul 3, 2026No releases on Jul 10, 2026No releases on Jul 17, 2026No releases on Jul 24, 2026No releases on Jul 31, 2026No releases on Aug 7, 2026
SaturdayNo releases on Apr 25, 2026No releases on May 2, 2026No releases on May 9, 2026No releases on May 16, 2026No releases on May 23, 2026No releases on May 30, 2026No releases on Jun 6, 2026No releases on Jun 13, 2026No releases on Jun 20, 2026No releases on Jun 27, 2026No releases on Jul 4, 2026No releases on Jul 11, 2026No releases on Jul 18, 2026No releases on Jul 25, 2026No releases on Aug 1, 2026

10 releases since Jan 22, 2026

Changelog

v0.29.3

Changed 2
  • Re-registering a tagged node with a different pre-auth key now applies the new key's tags instead of silently keeping the old ones
  • Reject /key requests below the supported capability version floor, matching /ts2021
Fixed 4
  • Fix tagged node stuck expired after tailscale logout, unable to re-authenticate
  • Fix re-authenticating an already-tagged node with --advertise-tags being rejected when the authenticating user owns the tags
  • Fix ephemeral nodes lingering as disconnected after reconnect churn
  • Fix node registration falsely returning 401 registration timed out when auth completes as the request context expires
Security 1
  • Check the machine key on the followup registration poll so a leaked auth ID cannot return the registering user's identity

Minimum supported Tailscale client version: v1.80.0

Changes
  • Fix tagged node stuck expired after tailscale logout, unable to re-authenticate #3394
  • Re-registering a tagged node with a different pre-auth key now applies the new key's tags instead of silently keeping the old ones #3394
  • Fix re-authenticating an already-tagged node with --advertise-tags being rejected when the authenticating user owns the tags #3394
  • Fix ephemeral nodes lingering as disconnected after reconnect churn #3383
  • Fix node registration falsely returning 401 registration timed out when auth completes as the request context expires #3392
  • Check the machine key on the followup registration poll so a leaked auth ID cannot return the registering user's identity #3393
  • Reject /key requests below the supported capability version floor, matching /ts2021 #3391
Upgrade

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

Changelog
  • 235a57ec31388901a78d054b1adb9dff900658cc CHANGELOG: add 0.29.3
  • 089d6c41092c1c9da6c22a1493407747b9da0e15 Explicitly select lunr as search provider
  • fba84ca2328330780433ce0ed45fa90e92559eac auth: check machine key on the followup registration path
  • 12928418b8330a9ed3024045eb3199a90d01e9d1 build: bump Go toolchain to 1.26.5
  • 9609a0b87ddc9cb28ec85244148bfed3b42f041a hscontrol: gate /key on supported capability version
  • bdc3e996de162fb15149085b448d931e96c05eb9 hscontrol: prefer completed auth over expired ctx in followup wait
  • 5aff68b5b9921db5ccb88013bb1740077ab872fb mkdocs: bump version
  • 4a1e77359d288f94dccfc63ea2d1e869bd8e1daf policy,state: authorize reauth tags against the authenticating user
  • 5fb514e6e09648676c8bd850201b0f84a6123694 poll: do not cancel ephemeral GC until Connect succeeds
  • 1fccdb18bda259e92b439a447191643d33e52277 state: apply a new pre-auth key's tags on re-registration
  • d2028832007b6b4544b119967db9658c9be24c6a state: do not expire tagged nodes on logout
View originalPermalink
How v0.29.3 went

v0.29.2

Fixed 3
  • Fix map generation serializing on the policy lock, so a mass reconnect on autogroup:self, via or relay policies no longer stalls clients into unexpected EOF retry loops
  • Fix /ts2021 rejecting the WebSocket GET upgrade with 405, which prevented Tailscale JS/WASM control clients from connecting
  • Gracefully handle nodes with an invalid FQDN (empty or too long) instead of failing map delivery; offending names are logged at startup with the fix command
Changes
  • Fix map generation serializing on the policy lock, so a mass reconnect on autogroup:self, via or relay policies no longer stalls clients into unexpected EOF retry loops #3358
  • Fix /ts2021 rejecting the WebSocket GET upgrade with 405, which prevented Tailscale JS/WASM control clients from connecting #3359
  • Gracefully handle nodes with an invalid FQDN (empty or too long) instead of failing map delivery; offending names are logged at startup with the fix command #3349
Upgrade

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

Changelog
  • 8eea89488c642f3d5f617fab5493d5f51f6f4ad0 CHANGELOG: drop unreleased 0.30.0 stub
  • f708c5b0108ac2ba5ff384ae9b85bdef488e1ffc CHANGELOG: note /ts2021 WebSocket GET fix
  • 735742e3eea9be93c42c2bd76c9641a74dc11e20 CHANGELOG: note 0.29.2 invalid-name map fix
  • 3ac33cf1d5fa66bafcdf11fd0919d10f118ee8ea CHANGELOG: shorten 0.29.2 invalid-name entry, set date
  • f885d87827bcae30a07063f2723cd03458144a00 Fix invalid ip syntax
  • 1ec7b7fb726ea6270a1eb459534753ef27eda736 hscontrol: register /ts2021 for WebSocket GET
  • 8f4e69d2a643f41378275c1b33395448ba8a596d integration: add TS2021 WebSocket tests to CI matrix
  • e7851ef8815248699833ac82fc7bd4d127a05713 integration: test /ts2021 WebSocket GET with a real WASM client
  • 9d1327458fb21b240ea8078e64aa09ed30b59a96 mapper,policy: add reconnect-storm and lock-concurrency regression tests
  • ec6719736806e53f1aad1e219670bb8b79476052 mapper: skip peers with invalid names instead of failing the map
  • d4f2acf3ab3cada875ab88eab4e0f81e4118cd12 policy: take RLock for reads so map generation runs concurrently
  • 5fb76eb231f105c67ff38316958a889ab4b1534b poll: return an HTTP error on long-poll setup failure
  • fd154fdb663d4992fb15cd95796c4791e48b096f state: log nodes with map-breaking data at startup
  • 9c9206a92b7b3ca416e955f472e7f002e9215b75 state: reject renames whose FQDN exceeds the hostname limit
View originalPermalink
How v0.29.2 went

v0.29.1

Fixed 1
  • Fix nodes with tags='null' losing their assigned user on upgrade

Minimum supported Tailscale client version: v1.80.0

Changes
  • Fix nodes with tags='null' losing their assigned user on upgrade #3325
Upgrade

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

Changelog
  • 636f660caf3ca995fad5a9ed6f1b6b0578637b55 db: preserve user_id on untagged nodes with tags='null'
View originalPermalink
How v0.29.1 went

v0.29.0

Added 9
  • Support for SSH rules with check action that prompt users to authenticate via OIDC or CLI approval before access is granted
  • New headscale auth CLI command group with approve, reject, and register subcommands for managing authentication requests
  • Policy tests (beta) that evaluate the tests block in a policy file to assert reachability between named sources and destinations
  • SSH policy tests (beta) that evaluate the sshTests block in a policy file with accept, deny, and check assertions
  • Support for Tailscale grants alongside ACLs to control application-level features like Taildrive file sharing and peer relay
  • autogroup:danger-all that resolves to all IP addresses including those outside the tailnet
Changed 2
  • Minimum supported Tailscale client version is now v1.80.0
  • SSH rule parsing now trims surrounding whitespace on action, users, src, and dst fields
Fixed 1
  • Tailscale ACL compatibility improved through extensive test cases generated using Tailscale clients and official SaaS
Removed 1
  • Wildcard * no longer matches all IPs in ACL rules, replaced by autogroup:danger-all

Minimum supported Tailscale client version: v1.80.0

Tailscale ACL compatibility improvements

Extensive test cases were systematically generated using Tailscale clients and the official SaaS to understand how the packet filter should be generated. We discovered a few differences, but overall our implementation was very close. #3036

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:

InputRaw (Hostname)DNS label (GivenName)
Joe's Mac miniJoe's Mac minijoes-mac-mini
Yuri's MacBook ProYuri's MacBook Proyuris-macbook-pro
Test@HostTest@Hosttest-host
mail.servermail.servermail-server
My-PC!My-PC!my-pc
我的电脑我的电脑node

#3202

HA subnet router health probing

Headscale now actively probes HA subnet routers to detect nodes that are connected but not forwarding traffic. The control plane periodically pings HA subnet routers via the Noise control channel and fails over to a healthy standby if the primary stops responding. This is enabled by default (node.routes.ha.probe_interval: 10s, probe_timeout: 5s) and only active when HA routes exist (2+ nodes advertising the same prefix). Set probe_interval to 0 to disable. This complements the existing disconnect-based failover, catching "zombie connected" routers that maintain their control session but cannot route packets. #3194

BREAKING
Hostname handling
  • The GivenName collision policy changed from an 8-char random hash suffix (laptop-abc12xyz) to a monotonic numeric suffix (laptop, laptop-1, laptop-2, …), matching Tailscale SaaS. Empty / all-non-ASCII hostnames now fall back to the literal node instead of invalid-<rand>. MagicDNS names change on upgrade for any node whose previous label was a random-suffix form; the raw Hostname column is unchanged. #3202
ACL Policy
  • Wildcard (*) in ACL sources and destinations now resolves to Tailscale's CGNAT range (100.64.0.0/10) and ULA range (fd7a:115c:a1e0::/48) instead of all IPs (0.0.0.0/0 and ::/0) #3036
    • This better matches Tailscale's security model where * means "any node in the tailnet" rather than "any IP address"
    • Policies that need to match all IP addresses including non-Tailscale IPs should use autogroup:danger-all as a source, or explicit CIDR ranges as destinations #2180
    • autogroup:danger-all can only be used as a source; it cannot be used as a destination
    • Note: Users with non-standard IP ranges configured in prefixes.ipv4 or prefixes.ipv6 (which is unsupported and produces a warning) will need to explicitly specify their CIDR ranges in ACL rules instead of using *
  • Validate autogroup:self source restrictions matching Tailscale behavior - tags, hosts, and IPs are rejected as sources for autogroup:self destinations #3036
    • Policies using tags, hosts, or IP addresses as sources for autogroup:self destinations will now fail validation
  • The proto:icmp protocol name now only includes ICMPv4 (protocol 1), matching Tailscale behavior #3036
    • Previously, proto:icmp included both ICMPv4 and ICMPv6
    • Use proto:ipv6-icmp or protocol number 58 explicitly for ICMPv6
Upgrade Path
  • Headscale now enforces a strict version upgrade path #3083
    • Skipping minor versions (e.g. 0.27 → 0.29) is blocked; upgrade one minor version at a time
    • Downgrading to a previous minor version is blocked
    • Patch version changes within the same minor are always allowed
Configuration
  • The randomize_client_port server-config key was removed; the toggle now lives in the policy file as a top-level randomizeClientPort field, matching the Tailscale-hosted schema. #3251 Headscale refuses to start when the old key is set. Move it to the policy file referenced by policy.path:

    {
      "randomizeClientPort": true,
    }
    

    If you do not have a policy file yet, create one with that minimal content and point policy.path at it. The default carries over — empty / absent policy means randomizeClientPort: false, matching the previous behaviour for operators who never set the key. Per-node opt-in via nodeAttrs is also supported and stacks on top of the global default.

CLI
  • headscale nodes register is deprecated in favour of headscale auth register --auth-id <id> --user <user> #1850
    • The old command continues to work but will be removed in a future release
Changes
ACL Policy
  • Fix subnet-to-subnet peer visibility — subnet routers now correctly become peers when ACL rules reference only subnet CIDRs as sources, without requiring node IP rules #3175
  • Fix filter rule reduction to use only approved subnet routes instead of all advertised routes, matching Tailscale SaaS behavior #3175
  • Add ICMP and IPv6-ICMP protocols to default filter rules when no protocol is specified #3036
  • Fix autogroup:self handling for tagged nodes - tagged nodes no longer incorrectly receive autogroup:self filter rules #3036
  • Use CIDR format for autogroup:self destination IPs matching Tailscale behavior #3036
  • Merge filter rules with identical SrcIPs and IPProto matching Tailscale behavior - multiple ACL rules with the same source now produce a single FilterRule with combined DstPorts #3036
  • Fix exit nodes incorrectly receiving filter rules for destinations that only overlap via exit routes #3169 #3175
  • Fix address-based aliases (hosts, raw IPs) incorrectly expanding to include the matching node's other address family #2180
  • Fix identity-based aliases (tags, users, groups) resolving to IPv4 only; they now include both IPv4 and IPv6 matching Tailscale behavior #2180
  • Fix wildcard (*) source in ACLs now using actually-approved subnet routes instead of autoApprover policy prefixes #2180
  • Fix non-wildcard source IPs being dropped when combined with wildcard * in the same ACL rule #2180
  • Fix exit node approval not triggering filter rule recalculation for peers #2180
  • Policy validation error messages now include field context (e.g., src=, dst=) and are more descriptive #2180
  • Reject policies whose user@ tokens match multiple DB users; rename the duplicate via headscale users rename to load #3160
  • Evaluate the policy tests block on user-initiated writes across both acls and grants; reject policies whose tests fail (beta) #1803
Grants
  • Add support for policy grants with ip, app, and via fields #2180
  • Add autogroup:danger-all as a source-only autogroup resolving to all IP addresses #2180
  • Add capability grants for Taildrive (cap/drive) and peer relay (cap/relay) with automatic companion capabilities #2180
  • Add per-viewer via route steering — grants with via tags control which subnet router or exit node handles traffic for each group of viewers #2180
  • Enable Taildrive node attributes on all nodes; actual access is controlled by cap/drive grants #2180
SSH Policy
  • Add support for localpart:*@<domain> in SSH rule users field, mapping each matching user's email local-part as their OS username #3091
  • Add SSH check action support with OIDC and CLI-based approval flows #1850
CLI
  • Add headscale auth register, headscale auth approve, and headscale auth reject CLI commands #1850
  • Deprecate headscale nodes register --key in favour of headscale auth register --auth-id #1850
  • headscale policy check --bypass-grpc-and-access-database-directly validates user@ tokens against the live user database #3160
  • Remove deprecated --namespace flag from nodes list, nodes register, and debug create-node commands (use --user instead) #3093
  • Remove deprecated namespace/ns command aliases for users and machine/machines aliases for nodes #3093
  • Fix DestroyUser deleting all pre-auth keys in the database instead of only the target user's keys #3155
  • headscale policy check evaluates the tests block when invoked with --bypass-grpc-and-access-database-directly; without the flag it warns instead of running the tests against empty data #1803
API
  • Add auth related routes. The auth/register endpoint now expects data as JSON #1850
  • Remove gRPC reflection from the remote (TCP) server #3180
OIDC
  • Add a confirmation page before completing node registration, showing the device hostname and machine key fingerprint #3180
  • Generalise auth templates into reusable AuthSuccess and AuthWeb components #1850
  • Unify auth pipeline with AuthVerdict type, supporting registration, reauthentication, and SSH checks #1850
Configuration
  • Add node.expiry configuration option to set a default node key expiry for nodes registered via auth key #3122
    • Tagged nodes (registered with tagged pre-auth keys) are exempt from default expiry
    • oidc.expiry has been removed; use node.expiry instead (applies to all registration methods including OIDC)
    • ephemeral_node_inactivity_timeout is deprecated in favour of node.ephemeral.inactivity_timeout
  • Add trusted_proxies to gate True-Client-IP / X-Real-IP / X-Forwarded-For (previously honoured from any client) #3268
Debug
  • Add node connectivity ping page for verifying control-plane reachability #3183
  • Omit secret fields (Pass, ClientSecret, APIKey) from /debug/config JSON output #3180
  • Route statsviz through tsweb.Protected #3180
Other
  • Remove old migrations for the debian package #3185
  • Install config-example.yaml as example for the debian package #3186
  • Fix user-owned re-registration with zero client expiry and no default storing 0001-01-01 00:00:00 in the database instead of NULL #3199
  • Fix tailscaled restart on a node with no expiry resetting NULL to 0001-01-01 00:00:00 in the database, affecting both tagged and untagged nodes #3197
  • Backfill nodes.expiry rows persisted by older versions as 0001-01-01 00:00:00 to NULL, so nodes upgraded from <0.28 stop reporting as expired #3284
  • Update reverse proxy documentation for trusted_proxies configuration option #3292
Upgrade

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

Changelog
  • b0c221f3a167505f4337522ba16958701283f69d changelog: set 0.29 date
View originalPermalink
How v0.29.0 went

v0.29.0-beta.4

Pre-release
Added 9
  • SSH rules with check action are now supported, prompting users to authenticate via OIDC or CLI approval before access is granted
  • New headscale auth CLI command group with approve, reject, and register subcommands for authentication workflows
  • Policy tests in the tests block now evaluated on policy writes, reloads, and checks, with failures preventing application
  • SSH policy tests in the sshTests block now evaluated on policy writes, reloads, and checks
  • Support for Tailscale grants alongside ACLs to control application-level features like Taildrive and peer relay
  • New autogroup:danger-all that resolves to all IP addresses including those outside the tailnet
Changed 3
  • Minimum supported Tailscale client version is now v1.80.0
  • Tailscale ACL compatibility improved through systematic test case generation against official SaaS
  • SSH rule parsing now trims whitespace on action, users, src, and dst fields and validates entries more strictly

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:

InputRaw (Hostname)DNS label (GivenName)
Joe's Mac miniJoe's Mac minijoes-mac-mini
Yuri's MacBook ProYuri's MacBook Proyuris-macbook-pro
Test@HostTest@Hosttest-host
mail.servermail.servermail-server
My-PC!My-PC!my-pc
我的电脑我的电脑node

#3202

HA subnet router health probing

Headscale now actively probes HA subnet routers to detect nodes that are connected but not forwarding traffic. The control plane periodically pings HA subnet routers via the Noise control channel and fails over to a healthy standby if the primary stops responding. This is enabled by default (node.routes.ha.probe_interval: 10s, probe_timeout: 5s) and only active when HA routes exist (2+ nodes advertising the same prefix). Set probe_interval to 0 to disable. This complements the existing disconnect-based failover, catching "zombie connected" routers that maintain their control session but cannot route packets. #3194

BREAKING
Hostname handling
  • The GivenName collision policy changed from an 8-char random hash suffix (laptop-abc12xyz) to a monotonic numeric suffix (laptop, laptop-1, laptop-2, …), matching Tailscale SaaS. Empty / all-non-ASCII hostnames now fall back to the literal node instead of invalid-<rand>. MagicDNS names change on upgrade for any node whose previous label was a random-suffix form; the raw Hostname column is unchanged. #3202
ACL Policy
  • Wildcard (*) in ACL sources and destinations now resolves to Tailscale's CGNAT range (100.64.0.0/10) and ULA range (fd7a:115c:a1e0::/48) instead of all IPs (0.0.0.0/0 and ::/0) #3036
    • This better matches Tailscale's security model where * means "any node in the tailnet" rather than "any IP address"
    • Policies that need to match all IP addresses including non-Tailscale IPs should use autogroup:danger-all as a source, or explicit CIDR ranges as destinations #2180
    • autogroup:danger-all can only be used as a source; it cannot be used as a destination
    • Note: Users with non-standard IP ranges configured in prefixes.ipv4 or prefixes.ipv6 (which is unsupported and produces a warning) will need to explicitly specify their CIDR ranges in ACL rules instead of using *
  • Validate autogroup:self source restrictions matching Tailscale behavior - tags, hosts, and IPs are rejected as sources for autogroup:self destinations #3036
    • Policies using tags, hosts, or IP addresses as sources for autogroup:self destinations will now fail validation
  • The proto:icmp protocol name now only includes ICMPv4 (protocol 1), matching Tailscale behavior #3036
    • Previously, proto:icmp included both ICMPv4 and ICMPv6
    • Use proto:ipv6-icmp or protocol number 58 explicitly for ICMPv6
Upgrade Path
  • Headscale now enforces a strict version upgrade path #3083
    • Skipping minor versions (e.g. 0.27 → 0.29) is blocked; upgrade one minor version at a time
    • Downgrading to a previous minor version is blocked
    • Patch version changes within the same minor are always allowed
Configuration
  • The randomize_client_port server-config key was removed; the toggle now lives in the policy file as a top-level randomizeClientPort field, matching the Tailscale-hosted schema. #3251 Headscale refuses to start when the old key is set. Move it to the policy file referenced by policy.path:

    {
      "randomizeClientPort": true,
    }
    

    If you do not have a policy file yet, create one with that minimal content and point policy.path at it. The default carries over — empty / absent policy means randomizeClientPort: false, matching the previous behaviour for operators who never set the key. Per-node opt-in via nodeAttrs is also supported and stacks on top of the global default.

CLI
  • headscale nodes register is deprecated in favour of headscale auth register --auth-id <id> --user <user> #1850
    • The old command continues to work but will be removed in a future release
Changes
ACL Policy
  • Fix subnet-to-subnet peer visibility — subnet routers now correctly become peers when ACL rules reference only subnet CIDRs as sources, without requiring node IP rules #3175
  • Fix filter rule reduction to use only approved subnet routes instead of all advertised routes, matching Tailscale SaaS behavior #3175
  • Add ICMP and IPv6-ICMP protocols to default filter rules when no protocol is specified #3036
  • Fix autogroup:self handling for tagged nodes - tagged nodes no longer incorrectly receive autogroup:self filter rules #3036
  • Use CIDR format for autogroup:self destination IPs matching Tailscale behavior #3036
  • Merge filter rules with identical SrcIPs and IPProto matching Tailscale behavior - multiple ACL rules with the same source now produce a single FilterRule with combined DstPorts #3036
  • Fix exit nodes incorrectly receiving filter rules for destinations that only overlap via exit routes #3169 #3175
  • Fix address-based aliases (hosts, raw IPs) incorrectly expanding to include the matching node's other address family #2180
  • Fix identity-based aliases (tags, users, groups) resolving to IPv4 only; they now include both IPv4 and IPv6 matching Tailscale behavior #2180
  • Fix wildcard (*) source in ACLs now using actually-approved subnet routes instead of autoApprover policy prefixes #2180
  • Fix non-wildcard source IPs being dropped when combined with wildcard * in the same ACL rule #2180
  • Fix exit node approval not triggering filter rule recalculation for peers #2180
  • Policy validation error messages now include field context (e.g., src=, dst=) and are more descriptive #2180
  • Reject policies whose user@ tokens match multiple DB users; rename the duplicate via headscale users rename to load #3160
  • Evaluate the policy tests block on user-initiated writes across both acls and grants; reject policies whose tests fail (beta) #1803
Grants
  • Add support for policy grants with ip, app, and via fields #2180
  • Add autogroup:danger-all as a source-only autogroup resolving to all IP addresses #2180
  • Add capability grants for Taildrive (cap/drive) and peer relay (cap/relay) with automatic companion capabilities #2180
  • Add per-viewer via route steering — grants with via tags control which subnet router or exit node handles traffic for each group of viewers #2180
  • Enable Taildrive node attributes on all nodes; actual access is controlled by cap/drive grants #2180
SSH Policy
  • Add support for localpart:*@<domain> in SSH rule users field, mapping each matching user's email local-part as their OS username #3091
  • Add SSH check action support with OIDC and CLI-based approval flows #1850
CLI
  • Add headscale auth register, headscale auth approve, and headscale auth reject CLI commands #1850
  • Deprecate headscale nodes register --key in favour of headscale auth register --auth-id #1850
  • headscale policy check --bypass-grpc-and-access-database-directly validates user@ tokens against the live user database #3160
  • Remove deprecated --namespace flag from nodes list, nodes register, and debug create-node commands (use --user instead) #3093
  • Remove deprecated namespace/ns command aliases for users and machine/machines aliases for nodes #3093
  • Fix DestroyUser deleting all pre-auth keys in the database instead of only the target user's keys #3155
  • headscale policy check evaluates the tests block when invoked with --bypass-grpc-and-access-database-directly; without the flag it warns instead of running the tests against empty data #1803
API
  • Add auth related routes. The auth/register endpoint now expects data as JSON #1850
  • Remove gRPC reflection from the remote (TCP) server #3180
OIDC
  • Add a confirmation page before completing node registration, showing the device hostname and machine key fingerprint #3180
  • Generalise auth templates into reusable AuthSuccess and AuthWeb components #1850
  • Unify auth pipeline with AuthVerdict type, supporting registration, reauthentication, and SSH checks #1850
Configuration
  • Add node.expiry configuration option to set a default node key expiry for nodes registered via auth key #3122
    • Tagged nodes (registered with tagged pre-auth keys) are exempt from default expiry
    • oidc.expiry has been removed; use node.expiry instead (applies to all registration methods including OIDC)
    • ephemeral_node_inactivity_timeout is deprecated in favour of node.ephemeral.inactivity_timeout
  • Add trusted_proxies to gate True-Client-IP / X-Real-IP / X-Forwarded-For (previously honoured from any client) #3268
Debug
  • Add node connectivity ping page for verifying control-plane reachability #3183
  • Omit secret fields (Pass, ClientSecret, APIKey) from /debug/config JSON output #3180
  • Route statsviz through tsweb.Protected #3180
Other
  • Remove old migrations for the debian package #3185
  • Install config-example.yaml as example for the debian package #3186
  • Fix user-owned re-registration with zero client expiry and no default storing 0001-01-01 00:00:00 in the database instead of NULL #3199
  • Fix tailscaled restart on a node with no expiry resetting NULL to 0001-01-01 00:00:00 in the database, affecting both tagged and untagged nodes #3197
  • Backfill nodes.expiry rows persisted by older versions as 0001-01-01 00:00:00 to NULL, so nodes upgraded from <0.28 stop reporting as expired #3284
  • Update reverse proxy documentation for trusted_proxies configuration option #3292
Upgrade

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

Changelog
  • e759d9fc9090ea97652348c084e39f4632dd4068 auth: re-validate key when an expired node re-registers
  • 68a6d3cf17f62002be1bca436ade474cb3551440 db: drop ambiguous machine-key getter, match precisely in test helper
  • 9b8949727d6da845a9f6fb80fce887ea2da36321 db: treat unknown pre-auth key as not found
  • a1d3e982554f8cc21bca0a80982cba005034b6c3 state: allow key expiry to be set on tagged nodes
  • fd08b8fa8c179fa4c7d2310ddee847d9e39e2e2c state: make any-user machine-key lookup deterministic
  • a5ef3aff15ab559e06f1553dcbe653a7b1e2d4f4 state: patch relogins and gate endpoint broadcasts
  • 0961e79e16746d0e60dc447a9929070cad82c986 state: re-register converted tagged nodes with reused key
  • a73d38bb3f3b0f9d9a1a090574c3e1774d88fc36 state: reject re-registration claiming another node's key
  • 168947848549280ededd48cd2529bfeb1d6c9f5a state: return all nodes for a machine key, reject ambiguous ownership
  • 96d2e6ed60f2d357c8b646eb5a490dd8de1b4b2b state: roll back node store when re-registration write fails
  • b83bf3f9936e61c55846adfa8091763d48f5d50b state: serialise registration per machine key
  • bff216a184eebaeb82baaabf50705e683185d899 state: update node in place on pre-auth-key re-registration
  • 4da06925d01727c11bafa633ade6aaf0d35d01d1 types/change: add NodeKeyRotated for relogin peer patch
View originalPermalink
How v0.29.0-beta.4 went

v0.29.0-beta.3

Pre-release
Added 9
  • SSH rules with "action": "check" are now supported, prompting users to authenticate via OIDC or CLI approval before access is granted
  • New headscale auth CLI command group with approve, reject, and register subcommands for managing authentication requests and node registration
  • Policy tests (beta) that evaluate the tests block in policy files to assert reachability between sources and destinations
  • SSH policy tests (beta) that evaluate the sshTests block in policy files with accept, deny, and check assertions
  • Support for Tailscale grants alongside ACLs, including app and via fields for application-level features and traffic steering
  • autogroup:danger-all autogroup that resolves to all IP addresses (0.0.0.0/0 and ::/0)
Changed 2
  • SSH rule parsing now trims surrounding whitespace on action, users, src, and dst fields
  • Tailscale ACL compatibility improved based on systematic testing with Tailscale clients and official SaaS
Fixed 2
  • SSH rule parsing now rejects empty or wildcard entries in users, empty acceptEnv, and negative checkPeriod
  • SSH rule parsing now rejects hosts: aliases as SSH destinations and non-ASCII tag names at parse time

Minimum supported Tailscale client version: v1.80.0

Tailscale ACL compatibility improvements

Extensive test cases were systematically generated using Tailscale clients and the official SaaS to understand how the packet filter should be generated. We discovered a few differences, but overall our implementation was very close. #3036

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:

InputRaw (Hostname)DNS label (GivenName)
Joe's Mac miniJoe's Mac minijoes-mac-mini
Yuri's MacBook ProYuri's MacBook Proyuris-macbook-pro
Test@HostTest@Hosttest-host
mail.servermail.servermail-server
My-PC!My-PC!my-pc
我的电脑我的电脑node

#3202

HA subnet router health probing

Headscale now actively probes HA subnet routers to detect nodes that are connected but not forwarding traffic. The control plane periodically pings HA subnet routers via the Noise control channel and fails over to a healthy standby if the primary stops responding. This is enabled by default (node.routes.ha.probe_interval: 10s, probe_timeout: 5s) and only active when HA routes exist (2+ nodes advertising the same prefix). Set probe_interval to 0 to disable. This complements the existing disconnect-based failover, catching "zombie connected" routers that maintain their control session but cannot route packets. #3194

BREAKING
Hostname handling
  • The GivenName collision policy changed from an 8-char random hash suffix (laptop-abc12xyz) to a monotonic numeric suffix (laptop, laptop-1, laptop-2, …), matching Tailscale SaaS. Empty / all-non-ASCII hostnames now fall back to the literal node instead of invalid-<rand>. MagicDNS names change on upgrade for any node whose previous label was a random-suffix form; the raw Hostname column is unchanged. #3202
ACL Policy
  • Wildcard (*) in ACL sources and destinations now resolves to Tailscale's CGNAT range (100.64.0.0/10) and ULA range (fd7a:115c:a1e0::/48) instead of all IPs (0.0.0.0/0 and ::/0) #3036
    • This better matches Tailscale's security model where * means "any node in the tailnet" rather than "any IP address"
    • Policies that need to match all IP addresses including non-Tailscale IPs should use autogroup:danger-all as a source, or explicit CIDR ranges as destinations #2180
    • autogroup:danger-all can only be used as a source; it cannot be used as a destination
    • Note: Users with non-standard IP ranges configured in prefixes.ipv4 or prefixes.ipv6 (which is unsupported and produces a warning) will need to explicitly specify their CIDR ranges in ACL rules instead of using *
  • Validate autogroup:self source restrictions matching Tailscale behavior - tags, hosts, and IPs are rejected as sources for autogroup:self destinations #3036
    • Policies using tags, hosts, or IP addresses as sources for autogroup:self destinations will now fail validation
  • The proto:icmp protocol name now only includes ICMPv4 (protocol 1), matching Tailscale behavior #3036
    • Previously, proto:icmp included both ICMPv4 and ICMPv6
    • Use proto:ipv6-icmp or protocol number 58 explicitly for ICMPv6
Upgrade Path
  • Headscale now enforces a strict version upgrade path #3083
    • Skipping minor versions (e.g. 0.27 → 0.29) is blocked; upgrade one minor version at a time
    • Downgrading to a previous minor version is blocked
    • Patch version changes within the same minor are always allowed
Configuration
  • The randomize_client_port server-config key was removed; the toggle now lives in the policy file as a top-level randomizeClientPort field, matching the Tailscale-hosted schema. #3251 Headscale refuses to start when the old key is set. Move it to the policy file referenced by policy.path:

    {
      "randomizeClientPort": true,
    }
    

    If you do not have a policy file yet, create one with that minimal content and point policy.path at it. The default carries over — empty / absent policy means randomizeClientPort: false, matching the previous behaviour for operators who never set the key. Per-node opt-in via nodeAttrs is also supported and stacks on top of the global default.

CLI
  • headscale nodes register is deprecated in favour of headscale auth register --auth-id <id> --user <user> #1850
    • The old command continues to work but will be removed in a future release
Changes
ACL Policy
  • Fix subnet-to-subnet peer visibility — subnet routers now correctly become peers when ACL rules reference only subnet CIDRs as sources, without requiring node IP rules #3175
  • Fix filter rule reduction to use only approved subnet routes instead of all advertised routes, matching Tailscale SaaS behavior #3175
  • Add ICMP and IPv6-ICMP protocols to default filter rules when no protocol is specified #3036
  • Fix autogroup:self handling for tagged nodes - tagged nodes no longer incorrectly receive autogroup:self filter rules #3036
  • Use CIDR format for autogroup:self destination IPs matching Tailscale behavior #3036
  • Merge filter rules with identical SrcIPs and IPProto matching Tailscale behavior - multiple ACL rules with the same source now produce a single FilterRule with combined DstPorts #3036
  • Fix exit nodes incorrectly receiving filter rules for destinations that only overlap via exit routes #3169 #3175
  • Fix address-based aliases (hosts, raw IPs) incorrectly expanding to include the matching node's other address family #2180
  • Fix identity-based aliases (tags, users, groups) resolving to IPv4 only; they now include both IPv4 and IPv6 matching Tailscale behavior #2180
  • Fix wildcard (*) source in ACLs now using actually-approved subnet routes instead of autoApprover policy prefixes #2180
  • Fix non-wildcard source IPs being dropped when combined with wildcard * in the same ACL rule #2180
  • Fix exit node approval not triggering filter rule recalculation for peers #2180
  • Policy validation error messages now include field context (e.g., src=, dst=) and are more descriptive #2180
  • Reject policies whose user@ tokens match multiple DB users; rename the duplicate via headscale users rename to load #3160
  • Evaluate the policy tests block on user-initiated writes across both acls and grants; reject policies whose tests fail (beta) #1803
Grants
  • Add support for policy grants with ip, app, and via fields #2180
  • Add autogroup:danger-all as a source-only autogroup resolving to all IP addresses #2180
  • Add capability grants for Taildrive (cap/drive) and peer relay (cap/relay) with automatic companion capabilities #2180
  • Add per-viewer via route steering — grants with via tags control which subnet router or exit node handles traffic for each group of viewers #2180
  • Enable Taildrive node attributes on all nodes; actual access is controlled by cap/drive grants #2180
SSH Policy
  • Add support for localpart:*@<domain> in SSH rule users field, mapping each matching user's email local-part as their OS username #3091
  • Add SSH check action support with OIDC and CLI-based approval flows #1850
CLI
  • Add headscale auth register, headscale auth approve, and headscale auth reject CLI commands #1850
  • Deprecate headscale nodes register --key in favour of headscale auth register --auth-id #1850
  • headscale policy check --bypass-grpc-and-access-database-directly validates user@ tokens against the live user database #3160
  • Remove deprecated --namespace flag from nodes list, nodes register, and debug create-node commands (use --user instead) #3093
  • Remove deprecated namespace/ns command aliases for users and machine/machines aliases for nodes #3093
  • Fix DestroyUser deleting all pre-auth keys in the database instead of only the target user's keys #3155
  • headscale policy check evaluates the tests block when invoked with --bypass-grpc-and-access-database-directly; without the flag it warns instead of running the tests against empty data #1803
API
  • Add auth related routes. The auth/register endpoint now expects data as JSON #1850
  • Remove gRPC reflection from the remote (TCP) server #3180
OIDC
  • Add a confirmation page before completing node registration, showing the device hostname and machine key fingerprint #3180
  • Generalise auth templates into reusable AuthSuccess and AuthWeb components #1850
  • Unify auth pipeline with AuthVerdict type, supporting registration, reauthentication, and SSH checks #1850
Configuration
  • Add node.expiry configuration option to set a default node key expiry for nodes registered via auth key #3122
    • Tagged nodes (registered with tagged pre-auth keys) are exempt from default expiry
    • oidc.expiry has been removed; use node.expiry instead (applies to all registration methods including OIDC)
    • ephemeral_node_inactivity_timeout is deprecated in favour of node.ephemeral.inactivity_timeout
  • Add trusted_proxies to gate True-Client-IP / X-Real-IP / X-Forwarded-For (previously honoured from any client) #3268
Debug
  • Add node connectivity ping page for verifying control-plane reachability #3183
  • Omit secret fields (Pass, ClientSecret, APIKey) from /debug/config JSON output #3180
  • Route statsviz through tsweb.Protected #3180
Other
  • Remove old migrations for the debian package #3185
  • Install config-example.yaml as example for the debian package #3186
  • Fix user-owned re-registration with zero client expiry and no default storing 0001-01-01 00:00:00 in the database instead of NULL #3199
  • Fix tailscaled restart on a node with no expiry resetting NULL to 0001-01-01 00:00:00 in the database, affecting both tagged and untagged nodes #3197
  • Backfill nodes.expiry rows persisted by older versions as 0001-01-01 00:00:00 to NULL, so nodes upgraded from <0.28 stop reporting as expired #3284
  • Update reverse proxy documentation for trusted_proxies configuration option #3292
Upgrade

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

Changelog
  • 6777a82ee6c16cbeff08a5b0c8aa72e481e80b0e Rewrite reverse proxy documentation
  • c5f3d5c28d628ef675210d172915751016f2b504 auth: clamp logout expiry to now
  • 5228cb1a408e29b67d72720894c80ac489f11daa change: drop subnet-router full update, use policy change
  • 7d845ef65ece7be591576dbe2eda45cf92f3aec7 db: advance allocator cursor under lock during IP backfill
  • f61753e73768ccc1d39c7dbba0003540566b67bf db: bound IP allocation scan so exhausted prefixes error out
  • ec945732584411c7566dcbe09d2333a4011971bf db: drop superseded ephemeral deletions in the GC drain loop
  • 99ad555d6452586caf4981fe877c6481f095e417 db: handle degenerate prefixes in random IP allocation
  • 10696fa6343a1c96de038b3d032e3663bc83aeb0 db: look up API keys by explicit primary key, not struct condition
  • 4c165ae5e7707967a9df941e0b45e5617dfbc1f3 db: reap ephemeral GC watcher goroutine on cancel and reschedule
  • e413919810fbe4da024970aa25b7cb0b4caec6da derp: clone regions when merging DERP maps
  • 9f0c74e73a50d0bc55c6ee72c7b98dd445f01a48 dns: release lock before extra-records channel send
  • 8f75ee56474f606d6926b80957019582d577d6fe docker: head tailscale latest go
  • f585f8a94d5c5a3e48f937f7efc71de90882b706 flake, go.mod: move to Go 1.26.4 and refresh dependencies
  • 21058d11424d5121dbc1eeb3ba0a39d2f462bfcc flake, go.mod: refresh dependencies
  • 29f87e5eaa7bd5509582d2f8626aee7f602db948 flakehashes: refresh vendor hash after dropping gorilla/mux
  • f61d21b4a74ff03baed0953ffef2a63e367a4a1a go.mod: drop unused gorilla/mux dependency
  • b892b8f254cb1d160f62138790923dd710c19a38 hscontrol: read Apple platform via chi.URLParam, not mux.Vars
  • 88044f43ffcfa246ed8692058c994f907f675c72 hscontrol: satisfy golangci-lint on changed lines
  • cffdb77c8ba4ba3e61230557fefd376f131d94bc mapper, change: coalesce duplicate policy recomputes per tick
  • 5e05652a78670f4374fc6988966ae0f19f4d28e6 mapper: derive incremental visibility from one shared filter
  • 8237ac662a85b8aff65dce57c539d8ad1d530ca1 mapper: filter incremental UserProfiles by ACL visibility
  • cd1c208980037425c4e6a440edccba2fe16ea85b mapper: filter peer-change patches by ACL visibility
  • f4eeb94b1c6932a4cee0f6b4e85ce2a83216548f mapper: gate broadcast sends until a connection's initial map is delivered
  • c483bebba8ceadd9ac9ca8e81d9c86c5c6d6d500 mapper: guard nil Hostinfo in addNextDNSMetadata
  • efdd9463e9eb9501439196bc2971940b1af48ce6 mapper: keep one batched bundle per node in flight to preserve order
  • ad2693ff1385ccd8a3c0c04b3cd9329eea797b22 mapper: record initial-map peers only after delivery
  • 0e7b15461776cfda52d562548de72539c985b744 mapper: register reconnecting node atomically against cleanup
  • fad8f2a729c941cab03d52bff9c7939b6512f921 mapper: test incremental visibility matches full map
  • 9fc88e308f7601a81604a29e9da2f68dcc25570e noise: drop write-only nodeKey field to fix data race
  • 71a4ce3c9fa12b9cb440392c0e9325c5a20950b5 noise: re-delegate SSH check when the auth session is missing (#3306)
  • 0921972f96c4b4400441ab98053dce772867ab8f oidc: avoid slice panic in getCookieName for short values
  • 0fdff0c79b51d1621150b8b353ca29d009d4d63b oidc: set SameSite=Lax on state/nonce CSRF cookies
  • bceac495f97f364e0f5a6c2ed67dfa44f52a68c8 policy: add NodeNeedsPeerRecompute predicate
  • 56cd3eb24d9cb75b7b14c25ac6fdb3b2c1008f0c policy: guard SSHCheckParams autogroup:self against nil User
  • 2c9164b1c4b151878f5159b25152486dec9c0fc8 policy: precompute node routes in the peer-map build
  • 40ed210521d880b46504a8d053f3112a7511fec0 policy: read pm.pol under the mutex
  • 020560fc5ffe62c4e1059369a71b8d36cc263090 policy: remove unused top-level BuildPeerMap
  • 7918187e7ac3eb2ae6b37bb1895c28ecdb531904 servertest: add logout/relogin storm repro with poll churn
  • f497b4efd701ae45ae503d93f8b2607a894849d9 state, poll: refcount poll sessions, mark offline only on last release
  • a518a5076aebcf0df55c0c2f5741dbc96d34f022 state: batch route auto-approval into one policy rebuild
  • 7706552c99b7469a3851cd435052413af5ba1bad state: gate reconnect PolicyChange on NodeNeedsPeerRecompute
  • 06d6816dc9511487a2b22a9ebda29b3d0ba0a837 state: keep nil expiry for nodes that stay tagged on reauth
  • 2e2401833b5e55b1a9360cb2bd18aaf3c062b056 state: persist live NodeStore node in persistNodeToDB
  • 4914f9f2fd054b6d55c291d80ce08c847a9437bd state: reject re-auth claiming another machine's NodeKey
  • eb57a3a62bbac3148db2533f2c093b93f34c6443 state: reject registration claiming another machine's NodeKey
  • 017162dac12bc0f7119721c8fa6b5c55f2a22ad7 state: signal NodeStore shutdown without closing writeQueue
  • 08f186f22acaac3ade69606c8cab4a018f53f694 state: skip database persist for keepalive-only map requests
  • 759381ad780b7dc255b9efcf4444eee05223d8f3 types: add ActiveSessions poll session refcount to Node
  • 4f67300005e61273573b50ea041c3ac840a1a7eb types: clone Hostinfo before applying DERP change
  • 5a70a72988658f9de925c68ccc0b55ede6bed606 types: lock tailcfg DNS config access for extra-records updates
  • bb06b905436326dca4887e00353f173a0b54ad4c types: skip malformed derp.urls entries instead of panicking
  • 84c99023e5894769954ef9319608935caf525fa0 util: check RNG error before slicing url-safe random string
  • ba5434917622a7a9402b6c675870685fb43df4d4 util: handle single-address IPv4 prefix in reverse DNS generation
View originalPermalink
How v0.29.0-beta.3 went

v0.29.0-beta.2

Pre-release
Added 9
  • SSH rules with check action are now supported, prompting users to authenticate via OIDC or CLI approval before access is granted
  • New headscale auth CLI command group with approve, reject, and register subcommands for managing authentication requests and node registration
  • Policy file tests block that asserts reachability between sources and destinations, running on policy writes, SIGHUP reload, and policy check commands
  • SSH policy tests block (sshTests) that validates SSH rule reachability with accept, deny, and check user lists
  • Support for Tailscale grants alongside ACLs, enabling app-level features like Taildrive file sharing and peer relay control via app and via fields
  • autogroup:danger-all that resolves to all IP addresses (0.0.0.0/0 and ::/0) replacing wildcard behavior for matching all IPs
Changed 2
  • Tailscale ACL compatibility improved through extensive test case generation and systematic comparison with official SaaS implementation
  • SSH rule parsing now trims whitespace on action, users, src, and dst fields, rejects empty or wildcard entries in users, empty acceptEnv, negative checkPeriod, and hosts aliases as SSH destinations

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:

InputRaw (Hostname)DNS label (GivenName)
Joe's Mac miniJoe's Mac minijoes-mac-mini
Yuri's MacBook ProYuri's MacBook Proyuris-macbook-pro
Test@HostTest@Hosttest-host
mail.servermail.servermail-server
My-PC!My-PC!my-pc
我的电脑我的电脑node

#3202

HA subnet router health probing

Headscale now actively probes HA subnet routers to detect nodes that are connected but not forwarding traffic. The control plane periodically pings HA subnet routers via the Noise control channel and fails over to a healthy standby if the primary stops responding. This is enabled by default (node.routes.ha.probe_interval: 10s, probe_timeout: 5s) and only active when HA routes exist (2+ nodes advertising the same prefix). Set probe_interval to 0 to disable. This complements the existing disconnect-based failover, catching "zombie connected" routers that maintain their control session but cannot route packets. #3194

BREAKING
Hostname handling
  • The GivenName collision policy changed from an 8-char random hash suffix (laptop-abc12xyz) to a monotonic numeric suffix (laptop, laptop-1, laptop-2, …), matching Tailscale SaaS. Empty / all-non-ASCII hostnames now fall back to the literal node instead of invalid-<rand>. MagicDNS names change on upgrade for any node whose previous label was a random-suffix form; the raw Hostname column is unchanged. #3202
ACL Policy
  • Wildcard (*) in ACL sources and destinations now resolves to Tailscale's CGNAT range (100.64.0.0/10) and ULA range (fd7a:115c:a1e0::/48) instead of all IPs (0.0.0.0/0 and ::/0) #3036
    • This better matches Tailscale's security model where * means "any node in the tailnet" rather than "any IP address"
    • Policies that need to match all IP addresses including non-Tailscale IPs should use autogroup:danger-all as a source, or explicit CIDR ranges as destinations #2180
    • autogroup:danger-all can only be used as a source; it cannot be used as a destination
    • Note: Users with non-standard IP ranges configured in prefixes.ipv4 or prefixes.ipv6 (which is unsupported and produces a warning) will need to explicitly specify their CIDR ranges in ACL rules instead of using *
  • Validate autogroup:self source restrictions matching Tailscale behavior - tags, hosts, and IPs are rejected as sources for autogroup:self destinations #3036
    • Policies using tags, hosts, or IP addresses as sources for autogroup:self destinations will now fail validation
  • The proto:icmp protocol name now only includes ICMPv4 (protocol 1), matching Tailscale behavior #3036
    • Previously, proto:icmp included both ICMPv4 and ICMPv6
    • Use proto:ipv6-icmp or protocol number 58 explicitly for ICMPv6
Upgrade Path
  • Headscale now enforces a strict version upgrade path #3083
    • Skipping minor versions (e.g. 0.27 → 0.29) is blocked; upgrade one minor version at a time
    • Downgrading to a previous minor version is blocked
    • Patch version changes within the same minor are always allowed
Configuration
  • The randomize_client_port server-config key was removed; the toggle now lives in the policy file as a top-level randomizeClientPort field, matching the Tailscale-hosted schema. #3251 Headscale refuses to start when the old key is set. Move it to the policy file referenced by policy.path:

    {
      "randomizeClientPort": true,
    }
    

    If you do not have a policy file yet, create one with that minimal content and point policy.path at it. The default carries over — empty / absent policy means randomizeClientPort: false, matching the previous behaviour for operators who never set the key. Per-node opt-in via nodeAttrs is also supported and stacks on top of the global default.

CLI
  • headscale nodes register is deprecated in favour of headscale auth register --auth-id <id> --user <user> #1850
    • The old command continues to work but will be removed in a future release
Changes
ACL Policy
  • Fix subnet-to-subnet peer visibility — subnet routers now correctly become peers when ACL rules reference only subnet CIDRs as sources, without requiring node IP rules #3175
  • Fix filter rule reduction to use only approved subnet routes instead of all advertised routes, matching Tailscale SaaS behavior #3175
  • Add ICMP and IPv6-ICMP protocols to default filter rules when no protocol is specified #3036
  • Fix autogroup:self handling for tagged nodes - tagged nodes no longer incorrectly receive autogroup:self filter rules #3036
  • Use CIDR format for autogroup:self destination IPs matching Tailscale behavior #3036
  • Merge filter rules with identical SrcIPs and IPProto matching Tailscale behavior - multiple ACL rules with the same source now produce a single FilterRule with combined DstPorts #3036
  • Fix exit nodes incorrectly receiving filter rules for destinations that only overlap via exit routes #3169 #3175
  • Fix address-based aliases (hosts, raw IPs) incorrectly expanding to include the matching node's other address family #2180
  • Fix identity-based aliases (tags, users, groups) resolving to IPv4 only; they now include both IPv4 and IPv6 matching Tailscale behavior #2180
  • Fix wildcard (*) source in ACLs now using actually-approved subnet routes instead of autoApprover policy prefixes #2180
  • Fix non-wildcard source IPs being dropped when combined with wildcard * in the same ACL rule #2180
  • Fix exit node approval not triggering filter rule recalculation for peers #2180
  • Policy validation error messages now include field context (e.g., src=, dst=) and are more descriptive #2180
  • Reject policies whose user@ tokens match multiple DB users; rename the duplicate via headscale users rename to load #3160
  • Evaluate the policy tests block on user-initiated writes across both acls and grants; reject policies whose tests fail (beta) #1803
Grants
  • Add support for policy grants with ip, app, and via fields #2180
  • Add autogroup:danger-all as a source-only autogroup resolving to all IP addresses #2180
  • Add capability grants for Taildrive (cap/drive) and peer relay (cap/relay) with automatic companion capabilities #2180
  • Add per-viewer via route steering — grants with via tags control which subnet router or exit node handles traffic for each group of viewers #2180
  • Enable Taildrive node attributes on all nodes; actual access is controlled by cap/drive grants #2180
SSH Policy
  • Add support for localpart:*@<domain> in SSH rule users field, mapping each matching user's email local-part as their OS username #3091
  • Add SSH check action support with OIDC and CLI-based approval flows #1850
CLI
  • Add headscale auth register, headscale auth approve, and headscale auth reject CLI commands #1850
  • Deprecate headscale nodes register --key in favour of headscale auth register --auth-id #1850
  • headscale policy check --bypass-grpc-and-access-database-directly validates user@ tokens against the live user database #3160
  • Remove deprecated --namespace flag from nodes list, nodes register, and debug create-node commands (use --user instead) #3093
  • Remove deprecated namespace/ns command aliases for users and machine/machines aliases for nodes #3093
  • Fix DestroyUser deleting all pre-auth keys in the database instead of only the target user's keys #3155
  • headscale policy check evaluates the tests block when invoked with --bypass-grpc-and-access-database-directly; without the flag it warns instead of running the tests against empty data #1803
API
  • Add auth related routes. The auth/register endpoint now expects data as JSON #1850
  • Remove gRPC reflection from the remote (TCP) server #3180
OIDC
  • Add a confirmation page before completing node registration, showing the device hostname and machine key fingerprint #3180
  • Generalise auth templates into reusable AuthSuccess and AuthWeb components #1850
  • Unify auth pipeline with AuthVerdict type, supporting registration, reauthentication, and SSH checks #1850
Configuration
  • Add node.expiry configuration option to set a default node key expiry for nodes registered via auth key #3122
    • Tagged nodes (registered with tagged pre-auth keys) are exempt from default expiry
    • oidc.expiry has been removed; use node.expiry instead (applies to all registration methods including OIDC)
    • ephemeral_node_inactivity_timeout is deprecated in favour of node.ephemeral.inactivity_timeout
  • Add trusted_proxies to gate True-Client-IP / X-Real-IP / X-Forwarded-For (previously honoured from any client) #3268
Debug
  • Add node connectivity ping page for verifying control-plane reachability #3183
  • Omit secret fields (Pass, ClientSecret, APIKey) from /debug/config JSON output #3180
  • Route statsviz through tsweb.Protected #3180
Other
  • Remove old migrations for the debian package #3185
  • Install config-example.yaml as example for the debian package #3186
  • Fix user-owned re-registration with zero client expiry and no default storing 0001-01-01 00:00:00 in the database instead of NULL #3199
    • Pre-existing rows with 0001-01-01 00:00:00 are not backfilled; they clear themselves the next time the node re-registers
  • Fix tailscaled restart on a node with no expiry resetting NULL to 0001-01-01 00:00:00 in the database, affecting both tagged and untagged nodes #3197
Upgrade

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

Changelog
  • ea8fc725700918a8aba59f10d6ba2469a73e82ad db: backfill zero-time node expiry to NULL
  • 77ba225cdbfbdca984adaaca56565158624c95d9 db: treat Go module pseudo-versions as dev builds
  • 66a5f99bfaa95fb42e28ac390a258207972621ea gh: pre-pull released tailscale images for fork-PR CI
  • 79562b9782a40a1926d7e8919d2355381f2ac1ef hi: add list-versions subcommand
  • 171fd7a3c54156965753a63639cdcafcd50c8d67 policy: key autogroup:self invalidation on UserID not User view
  • 4483fd0cad38717913e7509fc50f9d48c691b02b tsic, gh: keep unstable on Docker Hub
  • 2e49f3dc67218caa43a19174e9a85de3ed2b941b tsic: pull tailscale images from ghcr.io
View originalPermalink
How v0.29.0-beta.2 went

v0.29.0-beta.1

Pre-release
Added 9
  • SSH rules with check action are now supported, prompting users to authenticate via OIDC or CLI approval before access is granted
  • New headscale auth CLI command group with approve, reject, and register subcommands
  • Policy tests block in policy files now evaluated to assert reachability between named sources and destinations
  • SSH policy tests block (beta) now evaluated with accept, deny, and check assertions for SSH rule testing
  • Support for Tailscale grants alongside ACLs to control application-level features like Taildrive file sharing and peer relay
  • New autogroup:danger-all that resolves to all IP addresses including those outside the tailnet
Changed 3
  • Minimum supported Tailscale client version is now v1.80.0
  • Tailscale ACL compatibility improved through extensive test cases generated using Tailscale clients and official SaaS
  • SSH rule parsing now trims whitespace on action, users, src, and dst fields and validates against empty or wildcard user entries

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:

InputRaw (Hostname)DNS label (GivenName)
Joe's Mac miniJoe's Mac minijoes-mac-mini
Yuri's MacBook ProYuri's MacBook Proyuris-macbook-pro
Test@HostTest@Hosttest-host
mail.servermail.servermail-server
My-PC!My-PC!my-pc
我的电脑我的电脑node

#3202

HA subnet router health probing

Headscale now actively probes HA subnet routers to detect nodes that are connected but not forwarding traffic. The control plane periodically pings HA subnet routers via the Noise control channel and fails over to a healthy standby if the primary stops responding. This is enabled by default (node.routes.ha.probe_interval: 10s, probe_timeout: 5s) and only active when HA routes exist (2+ nodes advertising the same prefix). Set probe_interval to 0 to disable. This complements the existing disconnect-based failover, catching "zombie connected" routers that maintain their control session but cannot route packets. #3194

BREAKING
Hostname handling
  • The GivenName collision policy changed from an 8-char random hash suffix (laptop-abc12xyz) to a monotonic numeric suffix (laptop, laptop-1, laptop-2, …), matching Tailscale SaaS. Empty / all-non-ASCII hostnames now fall back to the literal node instead of invalid-<rand>. MagicDNS names change on upgrade for any node whose previous label was a random-suffix form; the raw Hostname column is unchanged. #3202
ACL Policy
  • Wildcard (*) in ACL sources and destinations now resolves to Tailscale's CGNAT range (100.64.0.0/10) and ULA range (fd7a:115c:a1e0::/48) instead of all IPs (0.0.0.0/0 and ::/0) #3036
    • This better matches Tailscale's security model where * means "any node in the tailnet" rather than "any IP address"
    • Policies that need to match all IP addresses including non-Tailscale IPs should use autogroup:danger-all as a source, or explicit CIDR ranges as destinations #2180
    • autogroup:danger-all can only be used as a source; it cannot be used as a destination
    • Note: Users with non-standard IP ranges configured in prefixes.ipv4 or prefixes.ipv6 (which is unsupported and produces a warning) will need to explicitly specify their CIDR ranges in ACL rules instead of using *
  • Validate autogroup:self source restrictions matching Tailscale behavior - tags, hosts, and IPs are rejected as sources for autogroup:self destinations #3036
    • Policies using tags, hosts, or IP addresses as sources for autogroup:self destinations will now fail validation
  • The proto:icmp protocol name now only includes ICMPv4 (protocol 1), matching Tailscale behavior #3036
    • Previously, proto:icmp included both ICMPv4 and ICMPv6
    • Use proto:ipv6-icmp or protocol number 58 explicitly for ICMPv6
Upgrade Path
  • Headscale now enforces a strict version upgrade path #3083
    • Skipping minor versions (e.g. 0.27 → 0.29) is blocked; upgrade one minor version at a time
    • Downgrading to a previous minor version is blocked
    • Patch version changes within the same minor are always allowed
Configuration
  • The randomize_client_port server-config key was removed; the toggle now lives in the policy file as a top-level randomizeClientPort field, matching the Tailscale-hosted schema. #3251 Headscale refuses to start when the old key is set. Move it to the policy file referenced by policy.path:

    {
      "randomizeClientPort": true,
    }
    

    If you do not have a policy file yet, create one with that minimal content and point policy.path at it. The default carries over — empty / absent policy means randomizeClientPort: false, matching the previous behaviour for operators who never set the key. Per-node opt-in via nodeAttrs is also supported and stacks on top of the global default.

CLI
  • headscale nodes register is deprecated in favour of headscale auth register --auth-id <id> --user <user> #1850
    • The old command continues to work but will be removed in a future release
Changes
ACL Policy
  • Fix subnet-to-subnet peer visibility — subnet routers now correctly become peers when ACL rules reference only subnet CIDRs as sources, without requiring node IP rules #3175
  • Fix filter rule reduction to use only approved subnet routes instead of all advertised routes, matching Tailscale SaaS behavior #3175
  • Add ICMP and IPv6-ICMP protocols to default filter rules when no protocol is specified #3036
  • Fix autogroup:self handling for tagged nodes - tagged nodes no longer incorrectly receive autogroup:self filter rules #3036
  • Use CIDR format for autogroup:self destination IPs matching Tailscale behavior #3036
  • Merge filter rules with identical SrcIPs and IPProto matching Tailscale behavior - multiple ACL rules with the same source now produce a single FilterRule with combined DstPorts #3036
  • Fix exit nodes incorrectly receiving filter rules for destinations that only overlap via exit routes #3169 #3175
  • Fix address-based aliases (hosts, raw IPs) incorrectly expanding to include the matching node's other address family #2180
  • Fix identity-based aliases (tags, users, groups) resolving to IPv4 only; they now include both IPv4 and IPv6 matching Tailscale behavior #2180
  • Fix wildcard (*) source in ACLs now using actually-approved subnet routes instead of autoApprover policy prefixes #2180
  • Fix non-wildcard source IPs being dropped when combined with wildcard * in the same ACL rule #2180
  • Fix exit node approval not triggering filter rule recalculation for peers #2180
  • Policy validation error messages now include field context (e.g., src=, dst=) and are more descriptive #2180
  • Reject policies whose user@ tokens match multiple DB users; rename the duplicate via headscale users rename to load #3160
  • Evaluate the policy tests block on user-initiated writes across both acls and grants; reject policies whose tests fail (beta) #1803
Grants
  • Add support for policy grants with ip, app, and via fields #2180
  • Add autogroup:danger-all as a source-only autogroup resolving to all IP addresses #2180
  • Add capability grants for Taildrive (cap/drive) and peer relay (cap/relay) with automatic companion capabilities #2180
  • Add per-viewer via route steering — grants with via tags control which subnet router or exit node handles traffic for each group of viewers #2180
  • Enable Taildrive node attributes on all nodes; actual access is controlled by cap/drive grants #2180
SSH Policy
  • Add support for localpart:*@<domain> in SSH rule users field, mapping each matching user's email local-part as their OS username #3091
  • Add SSH check action support with OIDC and CLI-based approval flows #1850
CLI
  • Add headscale auth register, headscale auth approve, and headscale auth reject CLI commands #1850
  • Deprecate headscale nodes register --key in favour of headscale auth register --auth-id #1850
  • headscale policy check --bypass-grpc-and-access-database-directly validates user@ tokens against the live user database #3160
  • Remove deprecated --namespace flag from nodes list, nodes register, and debug create-node commands (use --user instead) #3093
  • Remove deprecated namespace/ns command aliases for users and machine/machines aliases for nodes #3093
  • Fix DestroyUser deleting all pre-auth keys in the database instead of only the target user's keys #3155
  • headscale policy check evaluates the tests block when invoked with --bypass-grpc-and-access-database-directly; without the flag it warns instead of running the tests against empty data #1803
API
  • Add auth related routes. The auth/register endpoint now expects data as JSON #1850
  • Remove gRPC reflection from the remote (TCP) server #3180
OIDC
  • Add a confirmation page before completing node registration, showing the device hostname and machine key fingerprint #3180
  • Generalise auth templates into reusable AuthSuccess and AuthWeb components #1850
  • Unify auth pipeline with AuthVerdict type, supporting registration, reauthentication, and SSH checks #1850
Configuration
  • Add node.expiry configuration option to set a default node key expiry for nodes registered via auth key #3122
    • Tagged nodes (registered with tagged pre-auth keys) are exempt from default expiry
    • oidc.expiry has been removed; use node.expiry instead (applies to all registration methods including OIDC)
    • ephemeral_node_inactivity_timeout is deprecated in favour of node.ephemeral.inactivity_timeout
  • Add trusted_proxies to gate True-Client-IP / X-Real-IP / X-Forwarded-For (previously honoured from any client) #3268
Debug
  • Add node connectivity ping page for verifying control-plane reachability #3183
  • Omit secret fields (Pass, ClientSecret, APIKey) from /debug/config JSON output #3180
  • Route statsviz through tsweb.Protected #3180
Other
  • Remove old migrations for the debian package #3185
  • Install config-example.yaml as example for the debian package #3186
  • Fix user-owned re-registration with zero client expiry and no default storing 0001-01-01 00:00:00 in the database instead of NULL #3199
    • Pre-existing rows with 0001-01-01 00:00:00 are not backfilled; they clear themselves the next time the node re-registers
  • Fix tailscaled restart on a node with no expiry resetting NULL to 0001-01-01 00:00:00 in the database, affecting both tagged and untagged nodes #3197
Upgrade

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

Changelog
  • 7f02210863692ac1a5692d019125a02c82860b9a .golangci: ignore tests for goconst, raise occurrence threshold
  • 157e3a30fcbe6354ef42b627b071a82966840fe8 AGENTS.md: trim to behavioural guidance, drop deprecated sub-agent
  • cfb308b4a7599a22f3fc209c83dea406702422fd Add FAQ entry to migrate back to default IP prefixes
  • e597f4c8a04cccbed03186389d64e7d1bce65745 Add Headscale UI to web UI documentation
  • df339cd29003d74adf52fd7ae9917979efb56fa9 Add a link to Authentik's integration guide
  • f3f84a5a63ba69e31caee6bb5a4467dbbb47a63b Add docs for policy-wide options and node attributes
  • 890a044ef677314a649973c974c8b18f436af508 Add more UIs
  • 4eb589915436c88468ac55d346b542bda72e9586 Add taildrive, tests, sshTests as supported features
  • 542091e82b7541ab40d64f7e7d8d22f689594444 Add unit test
  • 20dff82f95e33316d52f0d0148f2b2ac00a8a464 CHANGELOG: add minimum Tailscale version for 0.29.0
  • 58a85b68b3d9571bd77d5ecc5ab4bcd12b69e156 CHANGELOG: bump 0.29.0 minimum tailscale client to v1.80.0
  • f693cc085165395b4e2d2256c8bc973b53f4653a CHANGELOG: document grants support for 0.29.0
  • 4e1d83ecefeb4b7c3dea7d153dfb46dde09e59e5 CHANGELOG: document hostname cleanroom rewrite
  • b52f8cb52fcb8c5d4c58c6583a7cdf7b9bbed5e6 CHANGELOG: document node.expiry and oidc.expiry deprecation
  • 408f4022e4dbadf2c0472affcee887bea721e8b6 CHANGELOG: document nodeAttrs feature and migrations
  • f03d41ea9a4bd5277ff70f10140bf3afa43a1ddc CHANGELOG: document policy tests (beta)
  • e78a24b8925f3fa51deb8f2d4885560fe75fef70 CHANGELOG: document sshTests evaluation (beta)
  • 30d18575befea6043e42bde004415f0c64a29a4a CHANGELOG: document strict version upgrade path
  • ec48f34e1cfd8781b33ddbf3f9921b03da81f240 CHANGELOG: document subnet-to-subnet ACL fixes
  • fd1074160e541a47935c11ca6ec31a087ec67d1e CHANGELOG: document user-facing changes from #3180
  • bcfaf6ad68516787065457d7a1adc5aa08ecc3e2 CHANGELOG: note nil expiry preservation fix
  • e3323b65e5395e4a7f02d93f9901562ef3495dd8 Describe how to set username instead of SPN for Kanidm
  • dc733767c40d6b97435ea1fe9f51214486fc7b93 Dockerfile.tailscale-HEAD,Dockerfile.derper: bump golang to 1.26.3
  • 78570c754f2bd2a155429de05fbe1985ae60675f Dockerfile: bump base images
  • e40dbe3b28ffb019e0e49b753efe7d634c69d1f9 Dockerfile: bump tailscale DERPer builder to Go 1.26.2
  • 6390fcee79aa594f61349754aac0d6d14ff7a8ac Dockerfile: bump tailscale HEAD builder to Go 1.26.2
  • 7e6c7924ad813e721b7ff2728febfbf50be4228c Document availability of autgroup:internet
  • faf55f5e8f955e610960346e7358302e5b1e9564 Document how to use the provider identifier in the policy
  • 1a64d950fd23a635a08d6188bc746606d15e3ed3 Document supported autogroups once
  • 0f12e414a67ad1afc846c189336890723e88a7a6 Explain one approach to update OIDC provider info
  • efd83da14e71a5cffee54d41edb86694ab14642d Explicitly mention that a headscale username should not end with @
  • d556df1c3649f5e3ac8d53e625385c36c8e059c1 Extend upgrade guide with backup instructions
  • 44600550c6ccd477ba70048c18c821a05ea083af Fix invisible selected menu item
  • a0d6802d5b3859586217d44b2fc2ce5a8fa0f08c Fix minor formatting issue in FAQ
  • c7f221dd0a99777957103e4c6c3303eede1c0552 Fix typo and wording
  • 3672a2df3a860bcb97f932d3dc7785406329b49e Fix typo in API key creation help text
  • 414d3bbbd830dc4d73b9200b09930cc1d9caa9d7 Fix typo in comment about fsnotify behavior
  • c907b0d32328ed822cca23ad0f238e7e6651fba4 Fix version in mkdocs
  • 32e1d77663e50ec607e512640d281857ce5fb3c8 Install config-example.yaml as example for the debian package
  • 9e50071df9ab70730a2921f3e6b3693bc0a60118 Link Fosdem 2026 talk
  • 84c7f0d450fe627676c498f3ec7c015667312f32 Link to development builds
  • 8028fa5483fed557e63d902647232e5f262f003c No longer consider autogroup:self experimental
  • e07b39108f4bad3045ee062aa3cca79a5884666a Quote autogroup:self in the CHANGELOG
  • acddd73183bfb53143c5cd3ce9808dcb9d5efe4c Reformat docs with mdformat
  • 109bfc404c3cf13116958c4dcb0eb8c7393a0f73 Refresh docs for Grants
  • c4ab267c36b91ee3e2da599f06abd9cb1a205292 Refresh features page
  • 8f60b819ec89b12775ba44e08587f55113b62b04 Refresh update path
  • c29bcd2eaff7b03213022ff19f372b2fb10a2d75 Release planning happens in milestones
  • 9ea09ea4b6f7513488f21e9cfec2cbf83ecaefff Remove changelog section for 0.28.1
  • 14ce7e9106c898b259ee22cbf5ab740b50ee09ba Remove link to Arch AUR headscale-git
  • 484462898b55cbc0e8e2f92e750b3b9b9f2f41a3 Remove link to sqlite
  • 892ffffc4a9541d3f0029e8e7336b62fd4a20dc7 Remove misleading comment
  • 61c9ae81e440e15492dac2b970387080525f6355 Remove old migrations for the debian package
  • e13f0458bb2f53360aa9d005cddf0c9848252df4 Remove redundant prefix
  • 4bb02412579aacb9cf11f8555aeb1593ea1d57db Require to update from one version to the next
  • edb7ad0f811d4cd63139135e355f9774f0501f48 Rewrite ACL docs as policy
  • 45b698dbac1f68a1be36e17507fd728326d1d95b Shorten container introduction
  • 513544cc11cb5abee0e7c20c193befb7ad97b2e1 Simplify upgrade snippet with a link to the upgrade guide
  • 8423af2732f2ef363c0a6fa53efc925a3ca12742 Swap favicon for updated version
  • 47307d19cfced913b22afa6e7c9383ecfb08f9a0 Switch to mdformat to format docs
  • f3512d50dfd2498e6a54e8bc6c651e8c0895a430 Switch to mkdocs-materialx
  • e285f3c9322f55e11f6133548e4cf7a836d89144 The headscale service is enabled by default
  • 355733342f511223277f4f737aea93b3716a68de Update config-example links
  • 9baa795ddbb4eda613142b9de2f1cd15e2596a96 Update docs for auth-id changes
  • 813eb2d733ff882165cc5a744adb1ab6eeb615c5 Update docs for new HA tracking
  • f1494a32ce3c6221d305ddbc3ecbd79b4572a725 Update links to Tailscale documentation
  • fda72ad1a322f1e2381ba1d863558a81bc08de47 Update main.md
  • 68b0014871c9336f766386f2d08179005de4b8c6 Use distroless without quotes
  • 163363a12a782f0226ea302a417c11f14cfcd8dd Use docs instead of KB
  • 23a5f1b628559d55e338e9d2a5e9c1c953cb0d7b Use pymdownx.magiclink with its default configuration
  • 97778c9930a3e19b07e75db6677730f6e9341655 all: add tests for PingRequest implementation
  • 17236fd28449da7994783bd153963de9dc6f3501 all: annotate complex functions with gocyclo rationale
  • 3e2aa5814edbb7c607a7c150de11bdc8310f8a21 all: annotate gosec false positives with rationale
  • 93860a5c063e20a51ceb1560c5404e4dd07ead18 all: apply formatter changes
  • 4cca63155d7a1bed3c806f876991a1a05e77bb73 all: apply godoc [Name] link conventions across comments
  • 43afeedde22dd6d57f3249af93bcf7970ce76f4f all: apply golangci-lint 2.9.0 fixes
  • ce580f824536f6c250dafd1c23e5558ca603130d all: fix golangci-lint issues (#3064)
  • eccf64eb58ca1187019a4c54d059fc7eed8fee8d all: fix staticcheck SA4006 in types_test.go
  • 3e0a96ec3abfdde94cfd6dea4845964e5543dce9 all: fix test flakiness and improve test infrastructure
  • b113655b7172982a97d2ebd44cc666ed93fba83f all: implement PingRequest for node connectivity checking
  • f905d58292866df651d0646b174cdfff4c4545c0 all: mechanical lint fixes
  • 742878d17211ffa7ca597a7312c2e6a026aba580 all: regenerate generated files for new tool versions
  • 010a5564c5fc65c09cc84777cad956821e2afa74 all: rephrase prose to fit codebase voice
  • 36a73f8c2257e486c94770f5702e34d2a01b2a78 all: update Go dependencies
  • 542cdb2cb2e53ea05e837810db48b956c1434d0b all: update Go to 1.26.1
  • 70f8141abd466898d72e2713db41c58a43fc0a73 all: upgrade from Go 1.26rc2 to Go 1.26.0
  • 0f6d312ada15911e3b373e6202df0974818f2cf2 all: upgrade to Go 1.26rc2 and modernize codebase
  • 4a9a329339c3e955bb85651733ded5bfc91794b1 all: use lowercase log messages
  • 0567cb6da3a294110bbd27f9b51c48e05938d57b app: add security headers middleware
  • f7d8bb8b3f37813f2a616e313a171721ccc31bf3 app: remove gRPC reflection from remote server
  • 30338441c1c95d4aa975d52b6f42a9ac7cc20b96 app: switch from gorilla to chi mux
  • 8a97dd134bf0b6d0717e3f1ab428c538eed1a5a7 app: wire HA health prober into scheduled tasks
  • f066d12153426c417d666ad392c5ebe78bee3d37 assets: fix logo alignment and error icon centering
  • 41d70fe87b6ae75235c6ad3beda32c5c15c477f2 auth: check machine key on tailscaled-restart fast path
  • cb3b6949ea0c72d45f25d5eb2e8f2ef9584af88a auth: generalise auth flow and introduce AuthVerdict
  • 25ccb5a161358503cdb6e01bc8e11364a3785d5e build: update golangci-lint and gopls in flake
  • eb23c125fab016b706c1bc5b7d4be6a47d3bac6d capver, types: bump to tailscale v1.98, drop LegacyDERPString
  • 442fcdbd33065f56650c40d7cbd455e73cb0c64e capver: regenerate for tailscale v1.96
  • 31c4331a9183cb3030041791bc42b0b5eec61480 capver: regenerate from docker tags
  • 2530d86f1b4d941fa49212273a1e3068a89c7757 change: document PingRequest merge first-wins foot-gun
  • 4a4032a4b0c108a43e931589ded088b1587f925d changelog: document filter rule merging
  • f27298c759ff82d1e71303d460b1ac8a91945c0c changelog: document wildcard CGNAT range change Add breaking change entry for the wildcard resolution change to use CGNAT/ULA ranges instead of all IPs. Updates #3036
  • 575d8ecbfde35c3d7c21f7da34d74a9f790c1c4b changelog: normalise 0.29.0 BREAKING and Changes sections
  • 9621a97ebe47490bd27e535aa5d21f12f0bc2a15 ci, pre-commit: validate vendor hash via vendorhash check
  • e171d30179d3a40befcf2376315aba5f819814aa ci: add build workflow for main branch
  • 99a93c126bf14c21a17712d29b6d64b8115b2fb3 ci: add rolling development tag to container builds
  • 795a1efe9bd9cb86310124aa6ba0a86b907381aa ci: fetch full history in golangci-lint job
  • d15ec28799c422e130876dbab042367355ff3d53 ci: pin Docker to v28 to avoid v29 breaking changes
  • 1b6ab52f9e2479627283a90c1626583ae3a53414 ci: regenerate integration test workflow
  • 0f9729466501a083ef734165e746762966fb9b6a ci: regenerate integration test workflow
  • 5c449db1258908dec8fad7ce8be7ed02faec6c6b ci: regenerate test-integration.yaml for TestSSHLocalpart
  • a7d405a25564efe2b09e83a79cd68c31a1ccd6cb ci: regenerate test-integration.yaml for TestTailscaleRustAxum
  • 1f9635c2ec20b362281daa9c24adcd7e008f765c ci: restrict test generator to .go files
  • a76b4bd46cc193b838bba4b3353ba649e9e7141f ci: switch integration tests to ARM runners
  • 4d3b56714921c5bad2e1cc6e8e14e93bed8fd02c ci: use overlay2 storage driver instead of pinning docker v28
  • e00c8992198ced5d43035ebeaba8d6f53b629c99 cmd, templates, integration: extract shared production constants
  • 461a0e2bea8ce5caab91dca3c4d64d604aecf7a7 cmd/dev: add local development server tool
  • 1a58b77271e437cd8f92001c214182b49e7ceb03 cmd/dev: validate --port fits the derived-port range
  • 6c08b49d6351aa3859e01818a7c77f2180393b9c cmd/headscale/cli: add confirmAction helper for force/prompt patterns
  • aae2f7de717a1c805623aee098fe8f54ee4bd1d7 cmd/headscale/cli: add grpcRun wrapper for gRPC client lifecycle
  • 7b7b270126d992e7f4c3e8e677523240726708d7 cmd/headscale/cli: add mustMarkRequired helper for init-time flag validation
  • d6c39e65a59eb4671b8bf15cb83ca208c5ed0706 cmd/headscale/cli: add printListOutput to centralise table-vs-JSON branching
  • 095106f49830050bd6321c2e40644e01808eb2d2 cmd/headscale/cli: convert remaining commands to RunE
  • 22fccae12566514f573283f0078a2b10fa7811af cmd/headscale/cli: deduplicate expiration parsing and api-key flag validation
  • 2765fd397f21844479218bf7827390b085206478 cmd/headscale/cli: drop dead flag-read error checks
  • af777f44f4f28ec099cdd77f4dfe9de3b4c967e5 cmd/headscale/cli: extract bypassDatabase helper and simplify policy file reads
  • 92a9accfcb7161a9c0b38f18bae05a4e15c6172e cmd/headscale/cli: mention sshTests in policy check help
  • 7460bec76717ab20c8df8932e2499255ffbae818 cmd/headscale/cli: move errMissingParameter and Error type to their users
  • 8891ec98358c4bd9a6c350d8cd7902199b42d9ef cmd/headscale/cli: remove deprecated output, SuccessOutput, ErrorOutput
  • d72a06c6c6ccbbcd65931c1038690bd834c65420 cmd/headscale/cli: remove legacy namespace and machine aliases
  • 13ebea192c280bb72334b7b8e3101ec803ea0c12 cmd/headscale/cli: remove nil resp guards and unexport HasMachineOutputFlag
  • e816397d547c3c65d203113ee5acc77fd3b62ed5 cmd/headscale/cli: remove no-op Args functions from serveCmd and dumpConfigCmd
  • e6546b2ceaa08ff9ece54a399b30fcf491df911f cmd/headscale/cli: silence cobra error/usage output and centralise error formatting
  • e4fe216e45037878ce2700387991c9e72b8466c0 cmd/headscale/cli: switch to RunE with grpcRunE and error returns
  • ca321d3c13327ff9a90e0af0235f4c924e193ec3 cmd/headscale/cli: use HeadscaleDateTimeFormat and util.Base10 consistently
  • 4e0c2b855616e08f3745d4db7f0264b402cee19f cmd/headscale/cli: validate users in policy check
  • e470774f6a362f66a7eed2f1c534941aea31f3a3 cmd/vendorhash: track vendor SRI in flakehashes.json
  • b5090a01ecf029351ee5b5ac8d655084f3633141 cmd: use zf constants for zerolog field names
  • 3f73ed5404a7b4f73ca4720bbd5686146acbb9a9 config, types: move randomize_client_port from server config to policy file
  • 82958835ceb26df8554c53c773044f98a1a93b43 db: enforce strict version upgrade path
  • 73613d7f5317f243b01adf4bf9e37cb041228207 db: fix database_versions table creation for PostgreSQL
  • 3037e5eee0285bfdc5f34cdee718331cdb315102 db: fix slice aliasing in migration tag merge
  • 0641771128601588d270b5d45cc52a67a012b5df db: guard UsePreAuthKey with WHERE used=false
  • af7e7a45604dd7fe5e0c64d5e2c907a3108c7c36 db: remove unused SetApprovedRoutes and SetTags helpers
  • 7c756b82019755ee8110557ff57d94a0db66a0ac db: scope DestroyUser to only delete the target user's pre-auth keys
  • 93e8c7285f6207cc561b659d2e902e81dc94d915 debug: explain URLIsNoise choice in ping callback
  • d5a4e6e36a8db6d2e15223d97023cc7a0034f812 debug: route statsviz through tsweb.Protected
  • 84adda226be25cc9e3240d39ef5b56627ade6a47 doc: add CHANGELOG entries for SSH check and auth commands
  • 585d0c01bca4814b76d1a220a2e427a36f404075 docs(config): fix typo in config-example.yaml
  • 01eb5402f9b91f3bd6c948e2e7b80e234b489e7b docs(setup): fix typo in requirements.md
  • 3acce2da87df7fa727027da293e7b689fc71abd1 errors: rewrite errors to follow go best practices
  • 510503322420aad6277b68ada8971fa088327df3 feat: add prominent warning banner for non-standard IP prefixes
  • 568baf3d021bd179ee3c230323984b81c7fc19a6 fix: align banner right-side border to consistent 64-char width
  • 25adfaf341960d915440cb94d88cea04ce81a6ae flake.nix, flake.lock: bump nixpkgs and pinned tools
  • 980622e9a597167ec800201e7bd6b9e3172050a4 flake.nix, go.mod: bump tailscale.com to v1.97.0-pre
  • 9c3a3c5837f5e67ffafc139e983e282b6d3b3465 flake: upgrade golangci-lint to 2.9.0 and update nixpkgs
  • 61a14bb0e44e784836094277d3cd243a4b64200d gen: regenerate from auth proto changes
  • 570735f204b6fb86ce94f1aa37a944c0cbb84c36 gen: regenerate grpc stubs with protoc-gen-go-grpc v1.6.2
  • 64f23136a29bc740a41dbf38d3ef8a3a73b78cb9 github: add needs-more-info automation workflow
  • 900f4b7b75b1334b5cf8b6755a6d2c3527038e62 github: add support-request automation workflow
  • 174e409da6f275904c34875c64ca3440117ce61b github: drop nu flatten in needs-more-info timer
  • e0d8c3c877df8ac493dccc8cd280b688293d79b5 github: fix needs-more-info label race condition
  • a7f981e30e353ccfca7fa8cb2a8452420b732fe7 github: fix needs-more-info label race condition
  • ce5d1ba8f8b1c0226a4fdd73ff11304fc78dd9ec github: split nu where in needs-more-info timer
  • c1b468f9f45867926e00fe7a7a700ed3ac2aad22 github: update issue template contact links
  • be90910d338e69ea0d51428c79ee2e666bd965af go.mod, go.sum: bump dependencies for v0.29.0
  • 1f48ebb376954aaf92796bc66c623332397eac83 go.mod: add github.com/realclientip/realclientip-go
  • 2f94b80e7057bf8c770b96c699c974af4ed96fc6 go.mod: add stress tool dependency
  • 0cf27eba7772d4f7a8b0f66eeb50918d7952c75f go.mod: add tstest/mts tool dependency
  • 27f56413411cc2ddcad928c67bbbf7c26f23ba92 golangci: add forbidigo rule for zerolog field constants
  • 0c6b9f5348f3a3523a25e404d7f574f23ebf34f6 goreleaser: remove unused ts2019 build tag
  • 48cc98b787f1155cc95e1d79f83d361099152987 hscontrol, cli: add auth register and approve commands
  • 52d454d0c84ca33550d5c7edb560e0fb7d16a4a0 hscontrol/db: add migration to clear user_id on tagged nodes
  • 7148a690d0e4f1d84e088294d072686879354fc3 hscontrol/grpcv1: use EmbedObject and zf constants
  • 53cdeff129492726dd3ce8cf2595a716d309c7d2 hscontrol/mapper: use sub-loggers and zf constants
  • f74ea5b8edc5181a1f5cbebaf66901a7dc78cd0b hscontrol/policy/v2: add Grant policy format support
  • bc9fb6d403f182e4441398371454edba27b088d0 hscontrol/policy/v2: reject ambiguous user references at load time
  • b09af3846b9362dd99f210d77e3afad38b6ef5a3 hscontrol/poll,state: fix grace period disconnect TOCTOU race
  • 4f8724151e5309e5dc2c43d5357bef8f88e6d30e hscontrol/poll: use sub-logger pattern for mapSession
  • 4e73133b9f81a55544a999bf413135e7d63f53b6 hscontrol/routes: use sub-logger and zf constants
  • ca7362e9aa240053cacce2dd505e3e74218d9fba hscontrol/servertest: add control plane lifecycle and consistency tests
  • f87b08676d7e28313f73ad9bfc0d36daa990be80 hscontrol/servertest: add policy, route, ephemeral, and content tests
  • 00c41b642227fdac7ebe174d34679073babafa6c hscontrol/servertest: add race, stress, and poll race tests
  • ab4e205ce70a05f5409692176a65467553193474 hscontrol/servertest: expand issue tests to 24 scenarios, surface 4 issues
  • 3d53f97c826c2f766025d611fcd3622815b15485 hscontrol/servertest: fix test expectations for eventual consistency
  • dd16567c525c2fe124c66923ecfd9742db479013 hscontrol/state,db: use zf constants for logging
  • 8048f10d1342c6b8e51f3ec85da3ae350f1b196b hscontrol/state: extract findExistingNodeForPAK to reduce complexity
  • 1053fbb16b2b6defcb2648814628702278903887 hscontrol/state: fix online status reset during re-registration
  • 2f907edf879d14daf883c6a3b45821c11a9fdaaa hscontrol/types: regenerate types_clone.go for viewer bump
  • 894e6946dc7ffa5ab366df9a92a62c622ba934ce hscontrol/types: regenerate types_view.go
  • 1059c678c47fa1a387323dcfbedafeee066d9a33 hscontrol/types: silence zerolog by default in tests
  • e0a436cefcb8864d60cfee2a0081cb101a9e2c87 hscontrol/util/zlog/zf: add tag, authkey, and route constants
  • 0288614bdfd3c7acd264060f65924880ca8cfa47 hscontrol: add servertest harness for in-process control plane testing
  • 580dcad683a38686a98a3df848282eb6d22f7b2f hscontrol: add tests for SetTags user_id database persistence
  • 7e8930c50748ed2ac431b36e29adcbccc0c2322e hscontrol: add tests for default node key expiry
  • 1e4fc3f1797400816311040158a00b6a84d05870 hscontrol: add tests for deleting users with tagged nodes
  • 75e56df9e44542b57953f4022a3023e49c1e2188 hscontrol: enforce that tagged nodes never have user_id
  • c6c29c05e56b1714a3b246a40afd551a74a09f3e hscontrol: gate proxy header trust on trusted_proxies
  • 42b8c779a02e5bfad6cef5967a7f2e2a107a2707 hscontrol: limit /verify request body size
  • 4ad200ab7352dd9690f112338f5a7a3fc25c8843 hscontrol: preserve nil expiry on tailscaled restart
  • d6dfdc100cf3f9d4bd9378b61284516478b437f8 hscontrol: route hostname handling through dnsname and NodeStore
  • 91730e2a1dfdb05da12d61dcd05b1548f93f67ce hscontrol: use EmbedObject for node logging
  • 99767cf805d13fd6a6a687ca966365fe0e98b747 hscontrol: validate machine key and bind src/dst in SSH check handler
  • f1e5f1346d1479cc30e7c65ef7400774712ef02b integration/acl: add tag verification step to TestACLTagPropagationPortSpecific
  • ebc57d9a38a278c40092a3dc3162a10c80d7a58e integration/acl: fix TestACLPolicyPropagationOverTime infrastructure
  • 81b871c9b50f3c7115d78a45af0b42d7406abc81 integration/acl: replace custom entrypoints with WithPackages
  • a147b0cd87e061883d774f5961cc003ad9ad6b9c integration/acl: use CurlFailFast for all negative curl assertions
  • eec3844f24a28ac5c831e55dc190b2f1f692b17a integration/dockertestutil: wait for libnetwork settle on reconnect
  • e638cbc9b958bd9cb63ccd22f79f904441592bae integration/tsic: accept via peer-relay in non-direct ping check
  • ea968e2f5df02b5ceb4f742957d2554a4b336f5c integration/tsric: add TailscaleRustInContainer package
  • 7bb86f2c162b0d77b5b370b4711f5e5ff8956642 integration: HA cable-pull lifecycle test
  • a7edcf3b0f73e0c77f5870319f541b38f780d34e integration: add CI-scaled timeouts and curl helpers for flaky ACL tests
  • af26bab17a080d91e2f26f6dea7004a1e4c9f021 integration: add HA ping failover test
  • 3db0a483edaa87d08e02b3213972c2283d1368a6 integration: add SSH check mode tests
  • 2be94ce19a040a3523c934387f0f5b6f22cd4523 integration: add TestSSHLocalpart integration test
  • 775bc3a7150f0bf6e7eedb7fb92b6ec7af00e7c1 integration: add TestTailscaleRustAxum for tailscale-rs
  • 9b1a6b6c05e3752b14f0ba4cfe6ecd73e6df9825 integration: add cap/relay grant peer relay lifecycle test
  • bca6e6334de6e14bb4532306ee1b9f4cfcb8d44b integration: add custom subnet support and fix exit node tests
  • a739862c653dc3ca0fbb5e5f302c72ed58fca222 integration: add via grant route steering tests
  • 98e9ff4d363f2db5d5e29d40d164944679c889e0 integration: authenticate Docker Hub pulls and retry transient errors
  • ba251e7b472ffabefdc1061eb00afc069d0ac562 integration: cover exit nodes via autogroup:internet ACL (#3212)
  • ecaf56e0a0b2717779b96e1f8300c60587461387 integration: drop Force flag on docker network disconnect
  • 51eed414b4a46f75d3f996ed389ce5e8cfb39995 integration: fix ACL tests for address-family-specific resolve
  • 9db5fb6393a0f4b08f3a253d728592e1699bf3a8 integration: fix error message assertion for invalid ACL action
  • be4fd9ff2dd80366e059b3cc3822be07c090515e integration: fix tag tests for tagged nodes with nil user_id
  • bfb6fd80dfa9c1f4946a3d4c87fc2aeec8c64cc1 integration: fixup test
  • dfcc96d808058a9b0819b626aa018b5531c7264f integration: harden ACL test ergonomics
  • acb8cfc7eed520ed0e98f2e71d7d906bd06aa47b integration: make docker execute and ping timeouts CI-aware
  • 27c9113af83548830b654cebcb6f8cf354081cff integration: regenerate workflow for HA docker disconnect test
  • 574a61852a3efc3f9cbed07d8e1fd63fde76d362 integration: reject failing sshTests at headscale policy set
  • 3a4af8cf874c77ef9fe264b3c13e7ad62d5b0c2b integration: remove --accept-routes from via steering routers
  • b762e4c350ca5106c1f4b4d78af010dbe1c5738c integration: remove exit node via grant tests
  • 78fd6efb38166d33c5f8cd82d321df56c68931d7 integration: replace ad-hoc test timeouts with named constants
  • 155e42f892f28d1c9f17e1c0c3209a4f74fb49dc integration: retry transient docker network ops
  • a9a2001ae7a716a3a21d6bb1aa6bcce5b932272a integration: scale remaining hardcoded timeouts and replace pingAllHelper
  • d1443a431cdcb4cfb160bc653eceaf0480aa1415 integration: skip subpackage tests in workflow generator
  • e5ebe3205a485f8575f46b4feac963373a69fb14 integration: standardize test infrastructure options
  • e44b402fe461b4bb90115b3751ee0ea39aa9952b integration: update TestSubnetRouteACL for filter merging and IPProto
  • 210f58f62e72db916a823ee4ff22a6630121ac95 integration: use CI-scaled timeouts for all EventuallyWithT assertions
  • a345a22a3be98893347dac9feddb6138c3cc5c62 mapper, app: ship MagicDNS Routes as empty slices, not nil
  • b3f795f0b4be7759136f2f045a05738b37319c11 mapper, policy/v2: stamp suggest-exit-node on Peer.CapMap when exit routes approved
  • 6fcff9e3527d20c6139898bb544ed40b65f173a9 mapper, state: deliver nodeAttrs through MapResponse and harden nextdns DoH rewrite
  • 2d549e579fe7e585b7f5ce3574dbc3434013b0e7 mapper/batcher: add regression tests for M1, M3, M7 fixes
  • 8e26651f2c0558b4d94b8c2d5a69a938538280a7 mapper/batcher: add regression tests for timer leak and Close lifecycle
  • 9b24a3994340658dd42e84a8da84c4f85c8412f0 mapper/batcher: add scale benchmarks
  • 21e02e5d1f4c9a45806cf80dcc40d09d741ce95e mapper/batcher: add unit tests and benchmarks
  • feaf85bfbca1bbb00990faa3feef51c2b420df32 mapper/batcher: clean up test constants and output
  • 50e8b21471fca399d5175d2264c5f5c2d7e59487 mapper/batcher: fix pointer retention, done-channel init, and connected-map races
  • da33795e79dd6da29609819d3835f6d63da45c03 mapper/batcher: fix race conditions in cleanup and lookups
  • 86e279869ec7f41105ac5f9182c2cc31a9804239 mapper/batcher: minor production code cleanup
  • 57a38b56789332887dd5b3ac40b07b9373e69f4a mapper/batcher: reduce hot-path log verbosity
  • 3ebe4d99c1abcbc0aceec7fa65d4022dcd9f0218 mapper/batcher: reduce lock contention with two-phase send
  • afd3a6acbcf5eb8c6079d181499b60aabe55df7e mapper/batcher: remove disabled X-prefixed test functions
  • 87b8507ac9d3510ee15074478e235ab62b1ba872 mapper/batcher: replace connected map with per-node disconnectedAt
  • 3276bda0c0abef446dd5956d285dfdd55a4c5c4d mapper/batcher: replace time.After with NewTimer to avoid timer leak
  • 57070680a588dd6971025d36eb5f623a2f3c2421 mapper/batcher: restructure internals for correctness
  • 82c7efccf8b0dccc2539293f5628eb9d21d44fe4 mapper/batcher: serialize per-node work to prevent out-of-order delivery
  • 60317064fd493b974a54387fea29b282b00cf585 mapper/batcher: serialize per-node work to prevent out-of-order delivery
  • 051a38a4c4807dc3a612ade8ecf0977def5dd5d6 mapper/batcher: track worker goroutines and stop ticker on Close
  • 3daf45e88aa2af8c0e46857f292a73d32551e506 mapper: close stale map channels after send timeouts
  • 7881f6535843c2caa2652a7cb7a687d5ebf9b8cd mapper: extract node connection types to node_conn.go
  • 9371b4ee2889fc4d605d89a299cec3cb6a925710 mapper: fix empty Peers list not clearing client peer state
  • 3587225a88d1ca85b1b7111430f8a301f423b291 mapper: fix phantom updateSentPeers on disconnected nodes
  • b81d6c734d071bdaf00039b717c31e3b73c0607b mapper: handle RemoveNode after channel cleanup
  • 6cd919d41144faf3373e989fb5fd89962f287800 mapper: include UserProfiles in policy-change MapResponses
  • 2058343ad6caea588617f9d119bb789927781e41 mapper: remove Batcher interface, rename to Batcher struct
  • 427b2f15eedb13136aaca7699d8e73b289b3f9fa matcher: clarify DestsIsTheInternet single-family semantics
  • 64c398f2c2050a79ed6bf87ebd44b9a8f27d3107 metrics, policy/v2: drop unused scaffolding + nil-error returns
  • 65880ecb5867da032674edf220c13ffd89b20861 nix: disable external DERP URL fetch in VM test
  • 37c6a9e3a6f1a8b2c2ee20529dc95b13c74bf182 nix: sync module options and descriptions with upstream nixpkgs
  • 5e332595508f77d706a53a86473084a4959b074c nix: update flake inputs
  • 2109674467791570c08aa44d1a23030d5ec2dc30 nix: update flake inputs and dev shell tool versions
  • c5ef1d3bb96f9f47e0a6723c08ebdca0fa900776 nix: upgrade dev shell to Python 3.14
  • f20bd0cf086423d29446ef8f02ffa4ce87f2b377 node: implement disable key expiry via CLI and API
  • 4d427cfe2af6bd9bb71d1e2abcc987d2b0906e20 noise: limit request body size to prevent unauthenticated OOM
  • 8c6cb05ab4247f479fc900857a9f7bbdabf88deb noise: pass context to sshActionFollowUp
  • e4e742c776eed9422c6a34a002ed5249d9922762 noise: pin outer RemoteAddr onto tunnel requests
  • 5a7cafdf855cba84d2641200ac8b70ac9f5937f3 noise: reject non-HEAD on PingResponseHandler
  • d66d3a4269a8514d8c6930cba7b731f004d7e930 oidc: add confirmation page for node registration
  • 3d0f597b237ce0227a233192f39fada79969d980 oidc: handle groups claim as string or array (FlexibleStringSlice)
  • 78990491da94d1136d78aa8ff05d37209186319a oidc: render HTML error pages for browser-facing failures
  • adb9467f605a69a787a964f11957bf240a58e683 oidc: validate state parameter length in callback
  • 107c2f2f70373c196db6c24bcd1cdb9f67a1e0eb policy, noise: implement SSH check action
  • c3df84e3547f0e558ac64f073ae4d59562e24226 policy/matcher: include CapGrant.Dsts in match destinations
  • 8358017dcfab48590dbc3f6a1b72f0b332911748 policy/v2,state,mapper: implement per-viewer via route steering
  • 078b9e308f1a4a35d9bb9ce6d1e36be9fe9bca09 policy/v2: SaaS-derived compat tests for nodeAttrs
  • 49744cd4678f5a9383b1333aeba75aca238209ec policy/v2: accept RFC 3986 bracketed IPv6 in ACL destinations
  • 6c59d3e60149f46473b9af45847c274c8bcd7de6 policy/v2: add SSH compatibility testdata from Tailscale SaaS
  • 2cb914df59b437b519416b648aff10d22f075b41 policy/v2: add SaaS goldens for via-grant prefix containment
  • 995ed0187c21f849512b323ae3512dbbc42e1a75 policy/v2: add advertised routes to compat test topologies
  • 0fa9dcaff842dfb0cf85e38f89b27452a748f898 policy/v2: add data-driven grants compatibility test with Tailscale SaaS captures
  • 0acf09bdd26879867e934e980b737e59e06a489f policy/v2: add localpart:*@domain SSH user compilation
  • b668c7a596164f913b24713b35db37673e363886 policy/v2: add policy unmarshal tests for bracketed IPv6
  • c0774a739bc4e00c7ceb4a01c912c17199e9499e policy/v2: add policytester captures recorded from Tailscale SaaS
  • 7bc701179b13b5b9413c277de5a89bdcfd2c81e7 policy/v2: add policytester compat test runner
  • 26eebcea5a0621523e1ef97adcbc1caa97b55270 policy/v2: add sshtester compat runner
  • 834ac27779553a5da09187ab8ac83613fcab68b0 policy/v2: add subnet routes and exit node compatibility tests
  • 5cd5e5de69c2a9c24af4126d4cfd476789b90971 policy/v2: add unit tests for ViaRoutesForPeer
  • 08d26e541ca9338c14c547b905dc175cd291b3b3 policy/v2: add unit tests for grant filter compilation helpers
  • 6a55f7d73199ea4879ca36c80ef12f289a3288dd policy/v2: add via exit steering golden captures and tests
  • affaa1a31d1bdb881b4029daf556e322dcd12a07 policy/v2: align SSH check action with SaaS wire format
  • d600090f2cd4e0f39e8c9a718abdb7c07026d325 policy/v2: align SSH rule validation with Tailscale
  • e4e209f9191a1f071b557fe6d0b9bdedbb380bbf policy/v2: canonicalize Protocol form during unmarshal
  • abd2b15db59a9e326bd80b0a960a0d0e8724683f policy/v2: clean up dead error variables, stale TODO, and test skip reasons
  • 2fb71690e8321fc6a57cde5bb2fdf3da900ae0c2 policy/v2: convert ACL compat tests to data-driven format with Tailscale SaaS captures
  • 500442c8f1699618a947aa4a4acb05939807188d policy/v2: convert routes compat tests to data-driven format with Tailscale SaaS captures
  • 013dea4f40716ae9f8e4869715c6a540aa2cf958 policy/v2: evaluate sshTests at write boundary
  • b29ae25356c57fb0dc8c8d4507d66540c0d3b7ac policy/v2: evaluate the tests block on user-initiated writes
  • f95b254ea9063e7b9ba653ed32e804219409b509 policy/v2: exclude exit routes from ReduceFilterRules
  • 2e1a716a9aacf4a77bf848fd50ba146fd065cae0 policy/v2: fix empty grants/acls returning FilterAllowAll
  • 8573ff915890043a2e26c15c0897655e9c57f10f policy/v2: fix grant-only policies returning FilterAllowAll
  • c36cedc32f7666a0ef5d62fa89987179bcf73147 policy/v2: fix via grants in BuildPeerMap, MatchersForNode, and ViaRoutesForPeer
  • 28be15f8ead74d01536b085c22bf8e4b5bd01cab policy/v2: handle autogroup:internet in via grant compilation
  • 0e3acdd8ecf99170b7bfeca2b690d23019c684c7 policy/v2: implement CapGrant compilation with companion capabilities
  • 687cf0882f8ad2baf42c1bea4d97077d04a96108 policy/v2: implement autogroup:danger-all support
  • 4f040dead2badfae47ccaf41a8d62072e19fc540 policy/v2: implement grant validation rules matching Tailscale SaaS
  • 54db47badca4d7ecf76d28b3d4382325aa42d192 policy/v2: implement via route compilation for grants
  • d5b2837231d4b79f16d4081efcd434c44106d45e policy/v2: match default proto set for tests with no proto
  • e5fcd01ee60a7896c34beb7551992ae96e25e04d policy/v2: match via-grant destinations by prefix overlap
  • a7c9721faa2f9a90544b0c8c330035fd4c62e099 policy/v2: overhaul compat test infrastructure
  • a4f05b0962141213393f1c26f196d8e8bc3e57a4 policy/v2: parse, validate, and compile nodeAttrs
  • ebe0f4078dcca09b008ee174f41e1ee6719ac55f policy/v2: preserve non-wildcard source IPs alongside wildcard ranges
  • 9f7aa556890927d780c29dad8d4a9a080bf28648 policy/v2: refactor alias resolution to use ResolvedAddresses
  • dda35847b020f9b9ac787caccf11dac18d8f6d9a policy/v2: reorder ACL self grants to match Tailscale rule ordering
  • 2b7f15abaa079039660a1415ef357344372bc1fb policy/v2: surface autogroup:internet via grants on exit nodes
  • e05f45cfb12b71a66660b5809f4624b2a5c9c561 policy/v2: use approved node routes in wildcard SrcIPs
  • 927ce418d2bd936a9e286a3ce0ac54f5997f2d19 policy/v2: use bare IPs in autogroup:self DstPorts
  • ccd284c0a58fbb51480309ab52d5edb639c31cfc policy/v2: use per-node filter compilation for via grants
  • 6a0a297c7f8669f291b6e12f8e05ffac9e202e6f policy/v2: validate sshTests at parse
  • f172dba0e33b7a4140f805fbd9444eb67baf139b policy/v2: validate tests block at parse boundary
  • b051e7b2bc76bfaaf700a92c4ccb7519680728dd policy/v2: wire PolicyManager through compiledGrant
  • f735502eae8459f3f6be0f714853b7079bbdb510 policy: add ICMP protocols to default and export constants When ACL rules don't specify a protocol, Headscale now defaults to [TCP, UDP, ICMP, ICMPv6] instead of just [TCP, UDP], matching Tailscale's behavior. Also export protocol number constants (ProtocolTCP, ProtocolUDP, etc.) for use in external test packages, renaming the string protocol constants to ProtoNameTCP, ProtoNameUDP, etc. to avoid conflicts. This resolves 78 ICMP-related TODOs in the Tailscale compatibility tests, reducing the total from 165 to 87.
  • 53d17aa321485a276f1251288b6e4df706db9fb7 policy: add comprehensive Tailscale ACL compatibility tests Add extensive test coverage verifying Headscale's ACL policy behavior matches Tailscale's coordination server. Tests cover: - Source/destination resolution for users, groups, tags, hosts, IPs - autogroup:member, autogroup:tagged, autogroup:self behavior - Filter rule deduplication and merging semantics - Multi-rule interaction patterns - Error case validation Key behavioral differences documented: - Headscale creates separate filter entries per ACL rule; Tailscale merges rules with identical sources - Headscale deduplicates Dsts within a rule; Tailscale does not - Headscale does not validate autogroup:self source restrictions for ACL rules (only SSH rules); Tailscale rejects invalid sources Tests are based on real Tailscale coordination server responses captured from a test environment with 5 nodes (1 user-owned, 4 tagged).
  • 835b7eb9605e6005d1c81b20b62504f872bc3a41 policy: autogroup:internet does not generate packet filters
  • 14f833bdb9874075f888b4a01f3d1d327f6b338b policy: fix autogroup:self handling for tagged nodes Skip autogroup:self destination processing for tagged nodes since they can never match autogroup:self (which only applies to user-owned nodes). Also reorder the IsTagged() check to short-circuit before accessing User() to avoid potential nil pointer access on tagged nodes.
  • 95b1fd636eaf08d2c114038a0699c0666f7f7ee4 policy: fix wildcard DstPorts format and proto:icmp handling
  • 93d79d8da90f05399e4e5e69a85d7e336617ed09 policy: include IPv6 in identity-based alias resolution
  • 0b1727c3378f69340149d3dc7470d7d969384f2b policy: merge filter rules with identical SrcIPs and IPProto
  • c7a0ca709f677f25bec400b9679beb2efa9d7537 policy: surface exit nodes via autogroup:internet (#3212)
  • 29aa08df0ed593368a04b6a071b9f9e686f2e558 policy: update test expectations for merged filter rules
  • 8baa14ef4abd765cba571a32c7439a5bd1fa2f29 policy: use CGNAT/ULA ranges for wildcard resolution Change Asterix.Resolve() to use Tailscale's CGNAT range (100.64.0.0/10) and ULA range (fd7a:115c:a1e0::/48) instead of all IPs (0.0.0.0/0 and ::/0). This better matches Tailscale's security model where wildcard (*) means "any node in the tailnet" rather than literally "any IP address on the internet". Updates #3036
  • 08fe2e4d6c4577017532ce569b9584f65a308e28 policy: use CIDR format for autogroup:self destinations
  • ebdbe0363924fb222bcbd61a2c46c0b42e1b7db6 policy: validate autogroup:self sources in ACL rules Tailscale validates that autogroup:self destinations in ACL rules can only be used when ALL sources are users, groups, autogroup:member, or wildcard (). Previously, Headscale only performed this validation for SSH rules. Add validateACLSrcDstCombination() to enforce that tags, autogroup:tagged, hosts, and raw IPs cannot be used as sources with autogroup:self destinations. Invalid policies like tag:client → autogroup:self:* are now rejected at validation time, matching Tailscale behavior. Wildcard () is allowed because autogroup:self evaluation narrows it per-node to only the node's own IPs.
  • ded51a4d30e92d4e23fcd09636cbe5666606a2d9 policyutil: fix reduceCapGrantRule and add route reduction
  • fffc58b5d04c0da9f38d07f2c53dd00e12f9907b poll: fix poll test linter violations
  • 4aca9d6568aec48173a1f69a21cfca06f6c9790b poll: stop stale map sessions through an explicit teardown hook
  • dc0e52a960e72e86cc96985e4a5dab248f7edce4 proto: add AuthRegister and AuthApprove RPCs
  • 56146de3774263e0c83503614d8c5376670892e9 proto: add CheckPolicy RPC
  • a8f7fedced74b0c7259948fecfd481174ec610f0 proto: add disable_expiry field to ExpireNodeRequest
  • 786ce2dce870ce96b882bac4f8d0950a8577062c routes: add health dimension to HA primary route election
  • 863fa2f8157bf974b153a2d8f19eee12ad58bb3d servertest, integration: cover HA both-offline recovery
  • 0378e2d2c60b8f49eed30ddfa19d9a1ab0b46038 servertest: add HA health probing tests
  • 164d659dd28eb44beaf7117e5355d28166f92265 servertest: add TestViaGrantHACompat for via+HA compat tests
  • 436d3db28e8bcf446639beb1bc2394b4bc55f84f servertest: add dynamic HA failover tests
  • 0431039f2a427403129cf2eb3fe4e01f39fd3146 servertest: add regression tests for via grant filter rules
  • 7d104b8c8dde06435460cd812f45efa959b436fe servertest: add via grant map compat tests
  • b5b786f5197a6bae3904bdefab2b978367fc4672 servertest: cover broader-dst via grant in filter test
  • 76ee29352b46659fc3ef16d8464bb50eb23ba497 servertest: cover via-grant exit-node visibility end-to-end
  • 53b8a81d480bfc0be76eadb312552e683c935d83 servertest: support tagged pre-auth keys in test clients
  • ff29af63f665869ffe4e9a35b3e8f8482b02c476 servertest: use memnet networking and add WithNodeExpiry option
  • 7bab8da36625f6be7d2d245d1f54febe31d367f8 state, policy, noise: implement SSH check period auto-approval
  • e2f2f9211f1bcf1ac59855edc26eeba2050302a8 state, servertest: property-test HA election + invariant catalogue
  • 3ca4ff8f3ff2df43abacfe9b12f5ce54a2faef6f state,servertest: add grant control plane tests and fix via route ReduceRoutes filtering
  • 90e65ccd6364f68b602ec47b2ab145366b0fab36 state: add HA health prober
  • b1196baf6d30094808714e9cd8d8a4f281f7a5b2 state: add regression test for Node slice persistence
  • 6337a3dbc4b958c9ad1965b2a44ef57881a566f6 state: apply default node key expiry on registration
  • a2c3ac095e6569c66d32fd7e1ebd5d4b8ea134b5 state: auto-bump GivenName on collision and add SetGivenName
  • 01e548e030fbe2326608657f43b316bc75a4ce0a state: avoid nil deref in registration handlers when old user is missing
  • de6be71a8630f00d84e78af056700ef83519132c state: batch HA probe results so dual-disconnect cannot flap primary
  • 9f7c8e9a07bd6682e78ea7ddec57215375c6d802 state: clear Unhealthy when node leaves HA candidate set
  • da927eb01881a088ad2bd4a574bbb0dab899e646 state: compute primary routes inside NodeStore snapshot
  • fb8eecae2530aebdfcc25003cf8fdc819d479afa state: defer HA failover when probe target reconnected mid-cycle
  • 66ac785c22631289e4d3bf7111c59e28418f2779 state: delete routes package, port primary route tests
  • 842f36225e6a2ff32d90e42c14fa8f36c6351528 state: drain pending pings on Close
  • ccddeceeec951a4eb08a08794c2718a37b40f181 state: fix GORM not persisting user_id=NULL on tagged node conversion
  • 6ae182696f33b77bd6cff96efd37f6e63976cea3 state: fix policy change race in UpdateNodeFromMapRequest
  • 82bb4331f5b639905cf71f82af21acc51776e1e5 state: fix routesChanged mutating input Hostinfo
  • c7630b505b2b757dd14284273ee5e84bb580c987 state: leave prefix unmapped when all primary candidates unhealthy
  • de60982d83f93b9ae8d44bdf9be80b19c3c3adf3 state: note tagged-path coverage and self-healing behaviour for #3199
  • 94ec607bcabd1b7b92abb30051f2e57df4d809fc state: per-goroutine deadline in HA probe cycle
  • 0e10ca4e9a27d0eb379207c2939e76dc789484ce state: preserve nil expiry on user owned registration when no default is configured
  • 3d5c0af4e703482aff8f979ff5bde330a7bf97f5 state: preserve previous primary when all HA advertisers unhealthy
  • 0d4f2293ffb8c9f1bcc394b68881e4a015e64783 state: replace zcache with bounded LRU for auth cache
  • 437754aeeaef6bd91736c8be9c35dbf43a50a8f2 state: switch consumers to NodeStore primary routes
  • 978f1e3947c3543fca2ec5aa73bc50bf110fef4b state: tie-break ResolveNode by GivenName then lowest NodeID
  • 380f5313421a68f34c1dc90a4c4ff7b801952cd3 state: trigger PolicyChange on every Connect and Disconnect
  • 0e5569c3fc99d8cdca6e7b2d3bff1008c3b8e64c templates: add detailsBox collapsible component
  • c15caff48c2d7437e8e75f1ad4ca5a88c7831238 templates: add error box component and error page template
  • f3eb9a7bba3631845d012f3667cd2b976ae98823 templates: escape query value in ping page
  • 4a7e1475c09418ac75c6a7f6fcec6757e7c265a7 templates: generalise auth templates for web and OIDC
  • 814226f327a05c060d1b9c04e82b26ced8b14e1a templates: improve accessibility, dark mode, and typography
  • c9dbea5c1878aca5bd7e6008d9a15701c461e83f templates: improve ping page spacing and design system usage
  • 3918020551eb1a7a44c57dd702df9dc35d8f6baa templates: use CSS variables in all shared components
  • de5b1eab68ca2f303e09fda25f17d4dfefa8058c templates: use table layout for registration confirm details
  • f34dec2754394609995ad81dd26244091eb094d3 testcapture: add typed capture format package
  • f49c42e71688b3e8315337aaff2405f8423431ca testdata: add SaaS captures for compat tests
  • 30dce30a9d83c909aaea5fb8c03620b7f3c93371 testdata: convert .json to .hujson with header comments
  • 9482cdf5906019b66b67a7e2fa113ec74b0b73d6 testdata: drop unused uppercase SSH-*.hujson fixtures
  • 835db974b5cd69028b6dca0c441acde25005469b testdata: strip unused fields from all test data files (23MB -> 4MB)
  • d243adaedd85bc06fd6bb5d21a2e80f67e1f5c9e types,mapper,integration: enable Taildrive and add cap/drive grant lifecycle test
  • 2a2d5c869aeee6792209abc24874e57cd0e23e00 types/change: fix slice aliasing in Change.Merge
  • cef5338cfe80a636f07a3c16d7c137669a3e1403 types/change: panic on Merge with conflicting TargetNode values
  • 64d13f77e8d2c0613ed63daf239f916ce48eff18 types/config, types/node: model default-auto-update from auto_update.enabled
  • 5ebc53c29e05e20037eedc1e62c5da326d5aa12a types/node, mapper, policy/v2: assemble self CapMap inside TailNode
  • 5d502bfb8836b8fd09fb284f6ff494527e11098a types/node, mapper: strip own IPv4 from emission when node has disable-ipv4 cap
  • 8ea4cd3faab314f536b973597fe361f12965b8d3 types/node, policy/v2: drop taildrive caps from baseline emission
  • cf3d30b6f61ad685054bce900e46c23062bbad98 types: add MarshalZerologObject to domain types
  • 1fe682b141b1e50135fc6295c8669c58177644e3 types: add Unhealthy and SessionEpoch fields to Node
  • 4d0b273b9013796ec0d7cb99a7243b340a640313 types: add node.expiry config, deprecate oidc.expiry
  • 610c1daa4dfd1ccff642e066d0740231a25e3079 types: avoid NodeView clone in CanAccess
  • b01e67e8e5bb3e701b2580c687fac7fdaa56f09c types: consider subnet routes as source identity in ACL matching
  • 3529fe0da16e9f79b34ce9d13cfcaf4035b9c751 types: fix OIDC identifier path traversal dropping subject
  • 4064f13bda2929aeff6f9b8ac5d3f1b8dd5fb946 types: fix nil panics in Owner() and TailscaleUserID() for orphaned nodes
  • 15c1cfd77859f1435e3a582903a139eec7eb30ef types: include ExitRoutes in HasNetworkChanges
  • 942313a10ae84ef6ade7fb96ef53ce74bb5b1ed8 types: move DebugRoutes from routes to types
  • a3c4ad2ca3159318bf2f9a1fbd829c7f97a8cf0b types: omit secret fields from JSON marshalling
  • 7a20db9f4963cf366d0dce1872be3d181fb4d651 types: persist Node JSON slices via named IsZero types
  • 58020696fee08981f6da197998661e7951366f87 zlog: add utility package for safe and consistent logging
View originalPermalink
How v0.29.0-beta.1 went

v0.28.0

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 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
  • Add --id flag to expire/delete commands as alternative to --prefix for API Keys #3016
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
  • 7f003ecafff59e627fda3f9597bd3e7d28cacf53 Add a page to describe supported registration methods
  • 5d300273dc57d552c64d9ebd0142f1bce8a1bf66 Add a tags page and describe a few common operations
  • d32f6707f7e2f8e670a6aadf155817bb1ebcb194 Add missing words
  • 89e436f0e6abdb4aa259425699aca8c046c332f4 Bump year/version for mkdocs
  • 49b70db7f2997a0a13a68ba50390feac937dd488 Conversion from personal to tagged node is reversible
  • 04b4071888980f7944d231487790158553d2c9a1 Fix node expiration success message
  • ee127edbf7fcbfc949fbcce56e4307730ae72c51 Remove trace log for preauthkeys create
  • 2695d1527e6edb76a151b4a72e1cebb0d349a228 Use registration key instead of machine key
  • 44af046196e9824394f3cca4c8c0329279bb9b5e all: update Go module dependencies
  • 4a744f423b7cd47c4432b8d174057a60b58cd111 changelog: change api key format
  • 97fa117c48a58bee3941e5e8b4393873501afe14 changelog: set 0.28 date
  • b5329ff0f3acf0d4fbbc8457c05ac8e8a5cdc2a1 flake.lock: update nixpkgs to 2026-02-03
  • eac8a57bce7688efddaa4ad0b3ca07244a9712c5 flake.nix: update hashes for dependency changes
  • ca75e096e6833c0a01d14381a1b9cacf12e7272f integration: add test for tagged→user-owned conversion panic
  • a09b0d1d6919058de6ed58557032bcd7a671bb22 policy/v2: add Caller() to log statements in compileACLWithAutogroupSelf
  • 1f32c8bf615ce97b21bdb29dc920c1227f058df1 policy/v2: add IsTagged() guards to prevent panics on tagged nodes
  • c2f28efbd710c129b1c62dcffa9041e99860b0b3 policy/v2: add test for issue #2990 same-user tagged device
  • 11f0d4cfdd935b3cc7612eab48a75a29c9c910b4 policy/v2: include nodes with empty filters in BuildPeerMap
  • 362696a5ef220a09b10459f6a899d2a5720bcab7 policy/v2: keep partial IPSet on SSH destination resolution errors
  • fb137a8fe3bfc7e4c739bceb30a5297ebe8b11eb policy/v2: use partial IPSet on group resolution errors in autogroup:self path
  • df184e5276ae2ea827955f9d4332386734cc4367 state: fix expiry handling during node tag conversion
  • 306aabbbce1fbcedad8e74b180490b266d993667 state: fix nil pointer panic when re-registering tagged node without user
  • 4912ceaaf5f8576ef7270f91c3b568caa74b7f92 state: inline reauthExistingNode and convertTaggedNodeToUser
  • 46daa659e23278751063001a402f308f35693dc3 state: omit AuthKeyID/AuthKey in node Updates to prevent FK errors
  • 0630fd32e5f5abf73f04daf5b282da904c7c4734 state: refactor HandleNodeFromAuthPath for clarity
  • ce7c256d1e30dc2583e32d557432879bea6ef9b0 state: set User pointer during tagged→user-owned conversion
  • d7f7f2c85e762bba3f11cae10a31f7099af960ce state: validate tags before UpdateNode to ensure consistency
View originalPermalink
How v0.28.0 went

v0.28.0-beta.2

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/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
  • API Key CLI: Add --id flag to expire/delete commands as alternative to --prefix #3016
    • headscale apikeys expire --id <ID> or --prefix <PREFIX>
    • headscale apikeys delete --id <ID> or --prefix <PREFIX>
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
  • 9146140217ecdd500d64a3648d04a2f2489910de Add headscale-operator
  • bb30208f97d8b02e7f8ea13f6972f00b6dd209df Add headscale-piying web UI to docs
  • 25a74348304de99df372926ae5920035dfc09fe5 Bump version in mkdocs
  • e43f19df794158328729c47f479069ec4fdafaf8 CHANGELOG: add breaking change for Node API simplification
  • d50108c722fb8a13c1920d9a5c2dfbec15b36bce Changelog: mark oidc.email_verified_required as breaking
  • c3e2e57f8e6e68deb66eed8e0f76c5d229791d85 Clarify autogroup:member
  • 6d21a4a3fed44fd5e0c09d4ff8c219d42ef2533b Document /version in the API docs
  • 7d81dca9aaae7bbf5ed3047039805aab8f7e37f7 Document how to disable the metrics interfaces
  • 99d35fbbbc3a9d58d33029bfd993932f982596b7 Document oidc.email_verification_required
  • 183a38715c045e8e0b5ec7f71d395c2f65970210 Fix list-routes examples
  • 8387c9cd82ecd0d441a35051d02389c838fed801 Fix ownership description for auto approved routers/exits
  • 18e13f6ffa61d89eb084154f2a42bc300e07e2f5 Link to headscale.net for docs
  • a445278f7601f1d95600fca390275e1476ef182d Mention tags on the features page
  • eec54cbbf347c0c612462d91b7e67b3d299e7546 api/v1: replace ForcedTags/InvalidTags/ValidTags with Tags
  • 606e5f68a0af9d87adfcaeda39450c998d716e0c changelog: fixups for 0.28.0-beta.2
  • c1cfb59b91645dd18a5e7fc3088c3c8f54146e10 ci: add ACL unknown user tests to integration workflow
  • 1325fd8b271c4cf0fa274e10541775e4519cb236 cli,hscontrol: use ID-based preauthkey operations
  • e0bae9b7697f984ecd3a5b6035cafc972d985d5a cli: add --id flag to API key expire/delete commands
  • 72fcb93ef3ae99a5f9fe769a51ea0d50ef702b97 cli: ensure tagged-devices is included in profile list (#2991)
  • 165c5f04915d9ce91bbf7dbf67558cfc203c2c6b cli: fix preauthkeys expire/delete argument validation
  • 951fd5a8e778253d33240651d83a48ccc5f1c059 cli: show Owner column in preauthkeys list
  • 6654142fbe9bfde2d40c4ed5e3e0d405d46dd618 cmd/headscale: migrate tests from check.v1 to testify
  • 0bcfdc29ada99b491cff641b386b28bff8bb4709 cmd/hi: enable concurrent test execution
  • 424e26d6365dcbd19ef1474e8d78fb09e5d7dc56 db: migrate tests from check.v1 to testify
  • 4e1834adaf0021c8ed22c3404ccbb081f9390ac7 db: use PolicyManager for RequestTags migration
  • aa29fd95a381af330543796f4253df9466abee38 derp: migrate to derpserver package API
  • 84c092a9f9875ed274aa40c9c14ebbcb05166f43 flake.lock: Update
  • 8631581852266a3720cd80fd37904e5e6c6fba3e gen: regenerate proto code
  • a04b21abc630f1a0fef34c158e3fcd7d6a1ae6fb gen: regenerate protobuf and type views
  • 8776745428f6330efd2c667b4e83ff863e443f31 gen: regenerate protobuf code
  • 0516c0ec375cb24fa657060a427f5d87945e405f gen: regenerate protobuf code
  • 515a22e696e403fa24021830ac21f76ab48230c3 go.mod: remove gopkg.in/check.v1 dependency
  • 0565e01c2fc8f8ac18952fb64d76229d0e5ede9a go.mod: update dependencies
  • a194712c34bdc8577e3aa18be7a4bd3ac8ef54b4 grpc: support expire/delete API keys by ID
  • c8c3c9d4a01181d2d82f2338256e0acad14445fb hscontrol: allow CreatePreAuthKey without user when tags provided
  • 3b4b9a443684858571edd2be70f407b3ad5de055 hscontrol: fix tag updates not propagating to node self view
  • 4ab06930a23c599e785dc61f08f223722e6ced34 hscontrol: handle tags-only PreAuthKeys in registration
  • 07a4b1b1fda371751eac2f1780df5dc0bec49a49 integration/tags: add dedicated issue #2978 reproduction test
  • 1b6db34b934b9c4d1c1fd2c4a9225c49db6eeb84 integration/tags: add self-tag validation to existing tests
  • 87c230d2513271964bd7e3e270924f50919b575d integration: add run ID isolation for concurrent test execution
  • 2e180d2587c56d0bc3e03daf1aa6807ff2587431 integration: add test for reauth tag removal
  • 98c0817b957d17b7787755a15cbc846c8f143023 integration: add tests for ACL group with deleted/unknown users
  • b3c4d0ec81d360bf1ed97ff646824aca402eb3c1 integration: add tests for API key expire/delete by ID
  • b8f3e09046f131e6e84c0e706c9b7e7d66095985 integration: fix tags-only auth key tests
  • 740d2b5a2c5cdf8c31378ab2bd0d129ffafc943d integration: support auth keys without user
  • 00da5361b3e3af966925048f9194b90eadf76957 integration: test tags-only auth key behavior
  • 4dd1b49a35d9100899771d6469a94d7215fe4c8b integration: update CLI tests for ID-based preauthkey commands
  • db6882b5f5ea5a1a2b11621bac214ef2c5a1fe8b integration: update DeleteAuthKey to use ID
  • e9a94f00a982f450a8c447d9eaa96f99a659611d integration: update SSH tests for validation rules
  • aee1d2a640584c7f50a0055d6dda13c53fc3b74b nix: fix deprecated attributes and update dev tools
  • 92caadcee642af42fbdaf5861d5dcb89056031dc nix: update vendor hash for Go dependencies
  • f5c779626ab1d32541c01fb0a93ac1c6e2373d86 nix: use testers.nixosTest instead of nixosTest
  • 5688c201e95e52bf299b158c65403eb9ed318023 policy/v2: validate SSH source/destination combinations
  • 22afb2c61b3fb6c24e409ac4d9d1f541e9de2885 policy: fix asymmetric peer visibility with autogroup:self
  • d40203e1534c4bc61212bcbf3df1c6b1464e6478 policy: update tests for SSH validation rules
  • b01eda721cd7fd2829f58fb124219a6488450bfa proto: add id field to API key expire/delete requests
  • 1398d01bd8ba445fc2a74c1a9fdb5eff04977087 proto: change preauthkey API to ID-based operations
  • 5103b35f3cfe988f96264399e26d2e6d88a1f7a3 sqliteconfig: add config opt for tx locking
  • 42bd9cd05817032f3e523dbbc120d0e601528c13 state: add GetAPIKeyByID method
  • d9cbb966032118546f795b67bda90adb5ed97cfe state: add unit test for DeleteUser change signal
  • 0451dd47181bb802b54f26b2e97dca05a4bd3ab7 state: allow untagging nodes via reauth with empty RequestTags
  • 00f22a84437a9ee61dcfa5139484ac3690a5e8f5 state: disable key expiry for nodes with approved advertise-tags
  • 1d9900273e206cc4546d8587b84b38d88284b6a1 state: disable key expiry for tagged nodes
  • 4be13baf3f680167ab09ea8634e19057e1095fbe state: update policy manager when deleting users
  • 3689f05407225f8ad29c913e2c5dcfd24b53a85f types: use Username() in User.Proto() when Name is empty
  • a6696582a439aedc8c5ff6b15e11c3f0c088fcb2 util/dns: fix variable redeclaration in ValidateDNSName
View originalPermalink
How v0.28.0-beta.2 went
View all

Discussion