juanfont Release Notes

Follow

8 release notes curated from 1 source by the Releasebot Team. Last updated: Aug 7, 2026

Get this feed:

juanfont Products

  • Jul 29, 2026
    • Date parsed from source:
      Jul 29, 2026
    • First seen by Releasebot:
      Aug 7, 2026
    juanfont logo

    headscale by juanfont

    v0.29.3

    headscale fixes tagged node re-authentication, registration, and reconnect issues, improving auth tag handling, ephemeral node cleanup, and follow-up registration security. It also raises the minimum supported Tailscale client version to v1.80.0.

    Minimum supported Tailscale client version

    v1.80.0

    Changes

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

    Upgrade

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

    Changelog

    • 235a57e CHANGELOG: add 0.29.3
    • 089d6c4 Explicitly select lunr as search provider
    • fba84ca auth: check machine key on the followup registration path
    • 1292841 build: bump Go toolchain to 1.26.5
    • 9609a0b hscontrol: gate /key on supported capability version
    • bdc3e99 hscontrol: prefer completed auth over expired ctx in followup wait
    • 5aff68b mkdocs: bump version
    • 4a1e773 policy,state: authorize reauth tags against the authenticating user
    • 5fb514e poll: do not cancel ephemeral GC until Connect succeeds
    • 1fccdb1 state: apply a new pre-auth key's tags on re-registration
    • d202883 state: do not expire tagged nodes on logout
    Original source
  • Jul 1, 2026
    • Date parsed from source:
      Jul 1, 2026
    • First seen by Releasebot:
      Aug 7, 2026
    juanfont logo

    headscale by juanfont

    v0.29.2

    headscale fixes map delivery and WebSocket connectivity, including safer handling of invalid node FQDNs, concurrent policy reads during reconnect storms, and /ts2021 GET upgrades for Tailscale JS/WASM control clients.

    Changes

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

    Upgrade

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

    Changelog

    8eea894 CHANGELOG: drop unreleased 0.30.0 stub
    f708c5b CHANGELOG: note /ts2021 WebSocket GET fix
    735742e CHANGELOG: note 0.29.2 invalid-name map fix
    3ac33cf CHANGELOG: shorten 0.29.2 invalid-name entry, set date
    f885d87 Fix invalid ip syntax
    1ec7b7f hscontrol: register /ts2021 for WebSocket GET
    8f4e69d integration: add TS2021 WebSocket tests to CI matrix
    e7851ef integration: test /ts2021 WebSocket GET with a real WASM client
    9d13274 mapper,policy: add reconnect-storm and lock-concurrency regression tests
    ec67197 mapper: skip peers with invalid names instead of failing the map
    d4f2acf policy: take RLock for reads so map generation runs concurrently
    5fb76eb poll: return an HTTP error on long-poll setup failure
    fd154fd state: log nodes with map-breaking data at startup
    9c9206a state: reject renames whose FQDN exceeds the hostname limit

    Original source
  • All of your release notes in one feed

    Join Releasebot and get updates from juanfont and hundreds of other software products.

    Create account
  • Jun 18, 2026
    • Date parsed from source:
      Jun 18, 2026
    • First seen by Releasebot:
      Aug 7, 2026
    juanfont logo

    headscale by juanfont

    v0.29.1

    headscale fixes tagged node user assignment loss and raises the minimum supported Tailscale client version to 1.80.0

    Minimum supported Tailscale client version: v1.80.0

    Changes

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

    Upgrade

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

    Changelog

    636f660 db: preserve user_id on untagged nodes with tags='null'

    Original source
  • Jun 17, 2026
    • Date parsed from source:
      Jun 17, 2026
    • First seen by Releasebot:
      Aug 7, 2026
    juanfont logo

    headscale by juanfont

    v0.29.0

    headscale adds major policy, SSH, and grants updates, with beta policy tests, SSH check approval flows, Taildrive and nodeAttrs support, stricter ACL validation, hostname handling aligned with Tailscale, and HA subnet router health probing, plus upgrade path and config changes.

    Minimum supported Tailscale client version: v1.80.0

    Tailscale ACL compatibility improvements

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

    #3036

    SSH check action

    SSH rules with "action": "check" are now supported. When a client initiates a SSH connection to a node
    with a check action policy, the user is prompted to authenticate via OIDC or CLI approval before access
    is granted. OIDC approval requires the authenticated user to own the source node; tagged source nodes
    cannot use SSH check-mode.

    A new headscale auth CLI command group supports the approval flow:

    headscale auth approve --auth-id <id> approves a pending authentication request (SSH check or web auth)
    headscale auth reject --auth-id <id> rejects a pending authentication request
    headscale auth register --auth-id <id> --user <user> registers a node (replaces deprecated headscale nodes register)

    #1850

    #3180

    Policy tests (beta)

    Headscale now evaluates the tests block in a policy file. Tests assert reachability between
    named sources and destinations and cover the whole policy — both acls and grants rules
    contribute. They run on user-initiated writes via headscale policy set, on SIGHUP reload
    (systemctl reload headscale / kill -HUP $(pidof headscale)), and on headscale policy check.

    A failing test rejects the write before it is applied, with the same error message Tailscale SaaS
    would return for the same policy.

    At boot a stored policy whose tests no longer pass — for example because a referenced user was
    deleted while the server was offline — logs a warning and the server keeps running. Fix the
    policy and reload.

    This feature is beta while behavioural coverage against Tailscale SaaS broadens.

    #3229

    SSH policy tests (beta)

    Headscale now evaluates the sshTests block in a policy file. Each entry names a source, one or
    more destination hosts, and three optional user lists: accept asserts the listed login users
    reach every destination via an accept- or check-action SSH rule, deny asserts none of them
    reach any destination, and check requires reachability specifically through a check-action
    rule. Tests run on headscale policy set, on SIGHUP reload (systemctl reload headscale /
    kill -HUP $(pidof headscale)), and on headscale policy check. A failing test rejects the
    write before it is applied, with the same error message Tailscale SaaS would return for the same
    policy.

    At boot a stored policy whose sshTests no longer pass — for example because a referenced user was
    deleted while the server was offline — logs a warning and the server keeps running. Fix the
    policy and reload.

    This feature is beta while behavioural coverage against Tailscale SaaS broadens.

    #3263

    SSH rule validation

    SSH rule parsing now trims surrounding whitespace on action, users, src, and dst,
    rejects empty or wildcard entries in users, rejects empty acceptEnv, and rejects negative
    checkPeriod. hosts: aliases are rejected as SSH destinations, non-ASCII tag names are
    rejected at parse time, and the wording for group-nesting cycles matches Tailscale SaaS.

    #3263

    Grants

    We now support Tailscale grants
    alongside ACLs. Grants extend what you can express in a policy beyond packet filtering: the app
    field controls application-level features like Taildrive file sharing and peer relay, and the via
    field steers traffic through specific tagged subnet routers or exit nodes. The ip field works like
    an ACL rule. Grants can be mixed with ACLs in the same policy file.

    #2180

    As part of this, we added autogroup:danger-all. It resolves to 0.0.0.0/0 and ::/0, all IP
    addresses, including those outside the tailnet. This replaces the old behaviour where * matched
    all IPs (see BREAKING below). The name is intentional: accepting traffic from the entire
    internet is a security-sensitive choice. autogroup:danger-all can only be used as a source.

    Node attributes (nodeAttrs)

    ACL policies now accept a nodeAttrs block. Each entry hands a list of
    Tailscale node capabilities to every node matching target. The accepted
    target forms are the same as acls.src and grants.src: users, groups,
    tags, hosts, prefixes, autogroup:member, autogroup:tagged, and *.

    {
    "randomizeClientPort": true,
    "nodeAttrs": [
    { "target": ["autogroup:tagged"], "attr": ["disable-captive-portal-detection"] },
    { "target": ["[email protected]"], "attr": ["nextdns:abc123"] },
    ],
    }
    

    Frequently requested capabilities this unlocks include magicdns-aaaa,
    disable-relay-server, disable-captive-portal-detection,
    nextdns:<profile> / nextdns:no-device-info, randomize-client-port,
    and the Taildrive drive:share / drive:access pair. The set is not
    limited to these, any string-only cap an operator places in policy
    reaches clients unchanged.

    randomizeClientPort also lands as a top-level policy field that toggles
    the default for every node, replacing the old server-config knob.

    A new auto_update.enabled config option controls the tailnet-wide
    default for client auto-update. When true, every node's CapMap carries
    default-auto-update: [true] so fresh clients pick up the default
    unless they make a local opt-in / opt-out choice.

    Policies that use the funnel cap, ipPool blocks, or
    autogroup:admin / autogroup:owner targets are rejected at load —
    those features depend on machinery headscale does not yet ship.

    #3251

    Taildrive

    Taildrive (file-sync between
    nodes) is now
    configurable through policy. Grant drive:share to the node that
    hosts files and drive:access to nodes that read or write them; pair
    with a tailscale.com/cap/drive grant to set the per-share access
    mode:

    {
    "nodeAttrs": [
    { "target": ["tag:fileserver"], "attr": ["drive:share"] },
    { "target": ["autogroup:member"], "attr": ["drive:access"] },
    ],
    "grants": [
    {
    "src": ["autogroup:member"],
    "dst": ["tag:fileserver"],
    "app": {
    "tailscale.com/cap/drive": [{ "shares": ["*"], "access": "rw" }],
    },
    },
    ],
    }
    

    A wildcard nodeAttrs ("target": ["*"]) hands the caps to every
    node when fine-grained control is not needed.

    Hostname sanitisation

    Hostnames are now santised using Tailscales magicdns sanitisation rules, matching Tailscale SaaS behavior. This means that hostnames with non-ASCII characters, special characters, or reserved DNS label characters are now transformed into valid DNS labels for MagicDNS. This improves our previously too strict sanitisation that rejected hostnames based on our guesswork and not based on the Tailscale upstream behaviour.

    Examples that previously regressed and now work:

    Input
    Raw (Hostname)
    DNS label (GivenName)
    Joe's Mac mini
    Joe's Mac mini
    joes-mac-mini
    Yuri's MacBook Pro
    Yuri's MacBook Pro
    yuris-macbook-pro
    Test@Host
    Test@Host
    test-host
    mail.server
    mail.server
    mail-server
    My-PC!
    My-PC!
    my-pc
    我的电脑
    我的电脑
    node

    #3202

    HA subnet router health probing

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

    #3194

    BREAKING

    Hostname handling

    The GivenName collision policy changed from an 8-char random hash suffix (laptop-abc12xyz) to a monotonic numeric suffix (laptop, laptop-1, laptop-2, …), matching Tailscale SaaS. Empty / all-non-ASCII hostnames now fall back to the literal node instead of invalid-<rand>. MagicDNS names change on upgrade for any node whose previous label was a random-suffix form; the raw Hostname column is unchanged. #3202

    ACL Policy

    Wildcard (*) in ACL sources and destinations now resolves to Tailscale's CGNAT range (100.64.0.0/10) and ULA range (fd7a:115c:a1e0::/48) instead of all IPs (0.0.0.0/0 and ::/0) #3036

    This better matches Tailscale's security model where * means "any node in the tailnet" rather than "any IP address"

    Policies that need to match all IP addresses including non-Tailscale IPs should use autogroup:danger-all as a source, or explicit CIDR ranges as destinations #2180

    autogroup:danger-all can only be used as a source; it cannot be used as a destination

    Note: Users with non-standard IP ranges configured in prefixes.ipv4 or prefixes.ipv6 (which is unsupported and produces a warning) will need to explicitly specify their CIDR ranges in ACL rules instead of using *

    Validate autogroup:self source restrictions matching Tailscale behavior - tags, hosts, and IPs are rejected as sources for autogroup:self destinations #3036

    Policies using tags, hosts, or IP addresses as sources for autogroup:self destinations will now fail validation

    The proto:icmp protocol name now only includes ICMPv4 (protocol 1), matching Tailscale behavior #3036

    Previously, proto:icmp included both ICMPv4 and ICMPv6

    Use proto:ipv6-icmp or protocol number 58 explicitly for ICMPv6

    Upgrade Path

    Headscale now enforces a strict version upgrade path #3083

    Skipping minor versions (e.g. 0.27 → 0.29) is blocked; upgrade one minor version at a time

    Downgrading to a previous minor version is blocked

    Patch version changes within the same minor are always allowed

    Configuration

    The randomize_client_port server-config key was removed; the
    toggle now lives in the policy file as a top-level
    randomizeClientPort field, matching the Tailscale-hosted schema. #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

    b0c221f changelog: set 0.29 date

    Original source
  • Jun 15, 2026
    • Date parsed from source:
      Jun 15, 2026
    • First seen by Releasebot:
      Aug 7, 2026
    juanfont logo

    headscale by juanfont

    v0.29.0-beta.4

    headscale releases a major update with SSH check approvals, policy and SSH tests, grants and Taildrive support, tighter ACL validation, improved hostname handling, HA subnet router probing, and stricter upgrade and config controls to better match Tailscale behavior.

    Minimum supported Tailscale client version: v1.80.0
    Tailscale ACL compatibility improvements
    Extensive test cases were systematically generated using Tailscale clients and the official SaaS
    to understand how the packet filter should be generated. We discovered a few differences, but
    overall our implementation was very close.
    #3036
    SSH check action
    SSH rules with "action": "check" are now supported. When a client initiates a SSH connection to a node
    with a check action policy, the user is prompted to authenticate via OIDC or CLI approval before access
    is granted. OIDC approval requires the authenticated user to own the source node; tagged source nodes
    cannot use SSH check-mode.
    A new headscale auth CLI command group supports the approval flow:
    headscale auth approve --auth-id <id> approves a pending authentication request (SSH check or web auth)
    headscale auth reject --auth-id <id> rejects a pending authentication request
    headscale auth register --auth-id <id> --user <user> registers a node (replaces deprecated headscale nodes register)
    #1850
    #3180
    Policy tests (beta)
    Headscale now evaluates the tests block in a policy file. Tests assert reachability between
    named sources and destinations and cover the whole policy — both acls and grants rules
    contribute. They run on user-initiated writes via headscale policy set, on SIGHUP reload
    (systemctl reload headscale / kill -HUP $(pidof headscale)), and on headscale policy check.
    A failing test rejects the write before it is applied, with the same error message Tailscale SaaS
    would return for the same policy.
    At boot a stored policy whose tests no longer pass — for example because a referenced user was
    deleted while the server was offline — logs a warning and the server keeps running. Fix the
    policy and reload.
    This feature is beta while behavioural coverage against Tailscale SaaS broadens.
    #3229
    SSH policy tests (beta)
    Headscale now evaluates the sshTests block in a policy file. Each entry names a source, one or
    more destination hosts, and three optional user lists: accept asserts the listed login users
    reach every destination via an accept- or check-action SSH rule, deny asserts none of them
    reach any destination, and check requires reachability specifically through a check-action
    rule. Tests run on headscale policy set, on SIGHUP reload (systemctl reload headscale /
    kill -HUP $(pidof headscale)), and on headscale policy check. A failing test rejects the
    write before it is applied, with the same error message Tailscale SaaS would return for the same
    policy.
    At boot a stored policy whose sshTests no longer pass — for example because a referenced user was
    deleted while the server was offline — logs a warning and the server keeps running. Fix the
    policy and reload.
    This feature is beta while behavioural coverage against Tailscale SaaS broadens.
    #3263
    SSH rule validation
    SSH rule parsing now trims surrounding whitespace on action, users, src, and dst,
    rejects empty or wildcard entries in users, rejects empty acceptEnv, and rejects negative
    checkPeriod. hosts: aliases are rejected as SSH destinations, non-ASCII tag names are
    rejected at parse time, and the wording for group-nesting cycles matches Tailscale SaaS.
    #3263
    Grants
    We now support Tailscale grants
    alongside ACLs. Grants extend what you can express in a policy beyond packet filtering: the app
    field controls application-level features like Taildrive file sharing and peer relay, and the via
    field steers traffic through specific tagged subnet routers or exit nodes. The ip field works like
    an ACL rule. Grants can be mixed with ACLs in the same policy file.
    #2180
    As part of this, we added autogroup:danger-all. It resolves to 0.0.0.0/0 and ::/0, all IP
    addresses, including those outside the tailnet. This replaces the old behaviour where * matched
    all IPs (see BREAKING below). The name is intentional: accepting traffic from the entire
    internet is a security-sensitive choice. autogroup:danger-all can only be used as a source.
    Node attributes (nodeAttrs)
    ACL policies now accept a nodeAttrs block. Each entry hands a list of
    Tailscale node capabilities to every node matching target. The accepted
    target forms are the same as acls.src and grants.src: users, groups,
    tags, hosts, prefixes, autogroup:member, autogroup:tagged, and .
    {
    "randomizeClientPort": true,
    "nodeAttrs": [
    { "target": ["autogroup:tagged"], "attr": ["disable-captive-portal-detection"] },
    { "target": ["[email protected]"], "attr": ["nextdns:abc123"] },
    ],
    }
    Frequently requested capabilities this unlocks include magicdns-aaaa,
    disable-relay-server, disable-captive-portal-detection,
    nextdns:<profile> / nextdns:no-device-info, randomize-client-port,
    and the Taildrive drive:share / drive:access pair. The set is not
    limited to these, any string-only cap an operator places in policy
    reaches clients unchanged.
    randomizeClientPort also lands as a top-level policy field that toggles
    the default for every node, replacing the old server-config knob.
    A new auto_update.enabled config option controls the tailnet-wide
    default for client auto-update. When true, every node's CapMap carries
    default-auto-update: [true] so fresh clients pick up the default
    unless they make a local opt-in / opt-out choice.
    Policies that use the funnel cap, ipPool blocks, or
    autogroup:admin / autogroup:owner targets are rejected at load —
    those features depend on machinery headscale does not yet ship.
    #3251
    Taildrive
    Taildrive (file-sync between
    nodes) is now
    configurable through policy. Grant drive:share to the node that
    hosts files and drive:access to nodes that read or write them; pair
    with a tailscale.com/cap/drive grant to set the per-share access
    mode:
    {
    "nodeAttrs": [
    { "target": ["tag:fileserver"], "attr": ["drive:share"] },
    { "target": ["autogroup:member"], "attr": ["drive:access"] },
    ],
    "grants": [
    {
    "src": ["autogroup:member"],
    "dst": ["tag:fileserver"],
    "app": {
    "tailscale.com/cap/drive": [{ "shares": ["
    "], "access": "rw" }],
    },
    },
    ],
    }
    A wildcard nodeAttrs ("target": [""]) hands the caps to every
    node when fine-grained control is not needed.
    Hostname sanitisation
    Hostnames are now santised using Tailscales magicdns sanitisation rules, matching Tailscale SaaS behavior. This means that hostnames with non-ASCII characters, special characters, or reserved DNS label characters are now transformed into valid DNS labels for MagicDNS. This improves our previously too strict sanitisation that rejected hostnames based on our guesswork and not based on the Tailscale upstream behaviour.
    Examples that previously regressed and now work:
    Input
    Raw (Hostname)
    DNS label (GivenName)
    Joe's Mac mini
    Joe's Mac mini
    joes-mac-mini
    Yuri's MacBook Pro
    Yuri's MacBook Pro
    yuris-macbook-pro
    Test@Host
    Test@Host
    test-host
    mail.server
    mail.server
    mail-server
    My-PC!
    My-PC!
    my-pc
    我的电脑
    我的电脑
    node
    #3202
    HA subnet router health probing
    Headscale now actively probes HA subnet routers to detect nodes that are connected but not
    forwarding traffic. The control plane periodically pings HA subnet routers via the Noise
    control channel and fails over to a healthy standby if the primary stops responding. This is
    enabled by default (node.routes.ha.probe_interval: 10s, probe_timeout: 5s) and only
    active when HA routes exist (2+ nodes advertising the same prefix). Set probe_interval to
    0 to disable. This complements the existing disconnect-based failover, catching "zombie
    connected" routers that maintain their control session but cannot route packets.
    #3194
    BREAKING
    Hostname handling
    The GivenName collision policy changed from an 8-char random hash suffix (laptop-abc12xyz) to a monotonic numeric suffix (laptop, laptop-1, laptop-2, …), matching Tailscale SaaS. Empty / all-non-ASCII hostnames now fall back to the literal node instead of invalid-<rand>. MagicDNS names change on upgrade for any node whose previous label was a random-suffix form; the raw Hostname column is unchanged. #3202
    ACL Policy
    Wildcard (
    ) in ACL sources and destinations now resolves to Tailscale's CGNAT range (100.64.0.0/10) and ULA range (fd7a:115c:a1e0::/48) instead of all IPs (0.0.0.0/0 and ::/0) #3036
    This better matches Tailscale's security model where * means "any node in the tailnet" rather than "any IP address"
    Policies that need to match all IP addresses including non-Tailscale IPs should use autogroup:danger-all as a source, or explicit CIDR ranges as destinations #2180
    autogroup:danger-all can only be used as a source; it cannot be used as a destination
    Note: Users with non-standard IP ranges configured in prefixes.ipv4 or prefixes.ipv6 (which is unsupported and produces a warning) will need to explicitly specify their CIDR ranges in ACL rules instead of using *
    Validate autogroup:self source restrictions matching Tailscale behavior - tags, hosts, and IPs are rejected as sources for autogroup:self destinations #3036
    Policies using tags, hosts, or IP addresses as sources for autogroup:self destinations will now fail validation
    The proto:icmp protocol name now only includes ICMPv4 (protocol 1), matching Tailscale behavior #3036
    Previously, proto:icmp included both ICMPv4 and ICMPv6
    Use proto:ipv6-icmp or protocol number 58 explicitly for ICMPv6
    Upgrade Path
    Headscale now enforces a strict version upgrade path #3083
    Skipping minor versions (e.g. 0.27 → 0.29) is blocked; upgrade one minor version at a time
    Downgrading to a previous minor version is blocked
    Patch version changes within the same minor are always allowed
    Configuration
    The randomize_client_port server-config key was removed; the
    toggle now lives in the policy file as a top-level
    randomizeClientPort field, matching the Tailscale-hosted schema. #3251
    Headscale refuses to start when the old key is set. Move it to the
    policy file referenced by policy.path:
    {
    "randomizeClientPort": true,
    }
    If you do not have a policy file yet, create one with that minimal
    content and point policy.path at it. The default carries over —
    empty / absent policy means randomizeClientPort: false, matching
    the previous behaviour for operators who never set the key. Per-node
    opt-in via nodeAttrs is also supported and stacks on top of the
    global default.
    CLI
    headscale nodes register is deprecated in favour of headscale auth register --auth-id <id> --user <user> #1850
    The old command continues to work but will be removed in a future release
    Changes
    ACL Policy
    Fix subnet-to-subnet peer visibility — subnet routers now correctly become peers when ACL rules reference only subnet CIDRs as sources, without requiring node IP rules #3175
    Fix filter rule reduction to use only approved subnet routes instead of all advertised routes, matching Tailscale SaaS behavior #3175
    Add ICMP and IPv6-ICMP protocols to default filter rules when no protocol is specified #3036
    Fix autogroup:self handling for tagged nodes - tagged nodes no longer incorrectly receive autogroup:self filter rules #3036
    Use CIDR format for autogroup:self destination IPs matching Tailscale behavior #3036
    Merge filter rules with identical SrcIPs and IPProto matching Tailscale behavior - multiple ACL rules with the same source now produce a single FilterRule with combined DstPorts #3036
    Fix exit nodes incorrectly receiving filter rules for destinations that only overlap via exit routes #3169 #3175
    Fix address-based aliases (hosts, raw IPs) incorrectly expanding to include the matching node's other address family #2180
    Fix identity-based aliases (tags, users, groups) resolving to IPv4 only; they now include both IPv4 and IPv6 matching Tailscale behavior #2180
    Fix wildcard () source in ACLs now using actually-approved subnet routes instead of autoApprover policy prefixes #2180
    Fix non-wildcard source IPs being dropped when combined with wildcard * in the same ACL rule #2180
    Fix exit node approval not triggering filter rule recalculation for peers #2180
    Policy validation error messages now include field context (e.g., src=, dst=) and are more descriptive #2180
    Reject policies whose user@ tokens match multiple DB users; rename the duplicate via headscale users rename to load #3160
    Evaluate the policy tests block on user-initiated writes across both acls and grants; reject policies whose tests fail (beta) #1803
    Grants
    Add support for policy grants with ip, app, and via fields #2180
    Add autogroup:danger-all as a source-only autogroup resolving to all IP addresses #2180
    Add capability grants for Taildrive (cap/drive) and peer relay (cap/relay) with automatic companion capabilities #2180
    Add per-viewer via route steering — grants with via tags control which subnet router or exit node handles traffic for each group of viewers #2180
    Enable Taildrive node attributes on all nodes; actual access is controlled by cap/drive grants #2180
    SSH Policy
    Add support for localpart:
    @<domain> in SSH rule users field, mapping each matching user's email local-part as their OS username #3091
    Add SSH check action support with OIDC and CLI-based approval flows #1850
    CLI
    Add headscale auth register, headscale auth approve, and headscale auth reject CLI commands #1850
    Deprecate headscale nodes register --key in favour of headscale auth register --auth-id #1850
    headscale policy check --bypass-grpc-and-access-database-directly validates user@ tokens against the live user database #3160
    Remove deprecated --namespace flag from nodes list, nodes register, and debug create-node commands (use --user instead) #3093
    Remove deprecated namespace/ns command aliases for users and machine/machines aliases for nodes #3093
    Fix DestroyUser deleting all pre-auth keys in the database instead of only the target user's keys #3155
    headscale policy check evaluates the tests block when invoked with --bypass-grpc-and-access-database-directly; without the flag it warns instead of running the tests against empty data #1803
    API
    Add auth related routes. The auth/register endpoint now expects data as JSON #1850
    Remove gRPC reflection from the remote (TCP) server #3180
    OIDC
    Add a confirmation page before completing node registration, showing the device hostname and machine key fingerprint #3180
    Generalise auth templates into reusable AuthSuccess and AuthWeb components #1850
    Unify auth pipeline with AuthVerdict type, supporting registration, reauthentication, and SSH checks #1850
    Configuration
    Add node.expiry configuration option to set a default node key expiry for nodes registered via auth key #3122
    Tagged nodes (registered with tagged pre-auth keys) are exempt from default expiry
    oidc.expiry has been removed; use node.expiry instead (applies to all registration methods including OIDC)
    ephemeral_node_inactivity_timeout is deprecated in favour of node.ephemeral.inactivity_timeout
    Add trusted_proxies to gate True-Client-IP / X-Real-IP / X-Forwarded-For (previously honoured from any client) #3268
    Debug
    Add node connectivity ping page for verifying control-plane reachability #3183
    Omit secret fields (Pass, ClientSecret, APIKey) from /debug/config JSON output #3180
    Route statsviz through tsweb.Protected #3180
    Other
    Remove old migrations for the debian package #3185
    Install config-example.yaml as example for the debian package #3186
    Fix user-owned re-registration with zero client expiry and no default storing 0001-01-01 00:00:00 in the database instead of NULL #3199
    Fix tailscaled restart on a node with no expiry resetting NULL to 0001-01-01 00:00:00 in the database, affecting both tagged and untagged nodes #3197
    Backfill nodes.expiry rows persisted by older versions as 0001-01-01 00:00:00 to NULL, so nodes upgraded from <0.28 stop reporting as expired #3284
    Update reverse proxy documentation for trusted_proxies configuration option #3292
    Upgrade
    Please follow the steps outlined in the upgrade guide to update your existing Headscale installation.
    Changelog
    e759d9f auth: re-validate key when an expired node re-registers
    68a6d3c db: drop ambiguous machine-key getter, match precisely in test helper
    9b89497 db: treat unknown pre-auth key as not found
    a1d3e98 state: allow key expiry to be set on tagged nodes
    fd08b8f state: make any-user machine-key lookup deterministic
    a5ef3af state: patch relogins and gate endpoint broadcasts
    0961e79 state: re-register converted tagged nodes with reused key
    a73d38b state: reject re-registration claiming another node's key
    1689478 state: return all nodes for a machine key, reject ambiguous ownership
    96d2e6e state: roll back node store when re-registration write fails
    b83bf3f state: serialise registration per machine key
    bff216a state: update node in place on pre-auth-key re-registration
    4da0692 types/change: add NodeKeyRotated for relogin peer patch

    Original source
  • Similar to juanfont with recent updates:

  • Jun 11, 2026
    • Date parsed from source:
      Jun 11, 2026
    • First seen by Releasebot:
      Aug 7, 2026
    juanfont logo

    headscale by juanfont

    v0.29.0-beta.3

    headscale ships a major release with Tailscale ACL and SSH compatibility improvements, policy tests in beta, grants and Taildrive support, node attributes, safer hostname handling, HA subnet router probing, stricter upgrades, and new auth and policy CLI flows.

    Minimum supported Tailscale client version: v1.80.0

    Tailscale ACL compatibility improvements

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

    #3036

    SSH check action

    SSH rules with "action": "check" are now supported. When a client initiates a SSH connection to a node with a check action policy, the user is prompted to authenticate via OIDC or CLI approval before access is granted. OIDC approval requires the authenticated user to own the source node; tagged source nodes cannot use SSH check-mode.

    A new headscale auth CLI command group supports the approval flow:

    headscale auth approve --auth-id <id> approves a pending authentication request (SSH check or web auth)
    headscale auth reject --auth-id <id> rejects a pending authentication request
    headscale auth register --auth-id <id> --user <user> registers a node (replaces deprecated headscale nodes register)

    #1850

    #3180

    Policy tests (beta)

    Headscale now evaluates the tests block in a policy file. Tests assert reachability between named sources and destinations and cover the whole policy — both acls and grants rules contribute. They run on user-initiated writes via headscale policy set, on SIGHUP reload (systemctl reload headscale / kill -HUP $(pidof headscale)), and on headscale policy check.

    A failing test rejects the write before it is applied, with the same error message Tailscale SaaS would return for the same policy.

    At boot a stored policy whose tests no longer pass — for example because a referenced user was deleted while the server was offline — logs a warning and the server keeps running. Fix the policy and reload.

    This feature is beta while behavioural coverage against Tailscale SaaS broadens.

    #3229

    SSH policy tests (beta)

    Headscale now evaluates the sshTests block in a policy file. Each entry names a source, one or more destination hosts, and three optional user lists: accept asserts the listed login users reach every destination via an accept- or check-action SSH rule, deny asserts none of them reach any destination, and check requires reachability specifically through a check-action rule. Tests run on headscale policy set, on SIGHUP reload (systemctl reload headscale / kill -HUP $(pidof headscale)), and on headscale policy check. A failing test rejects the write before it is applied, with the same error message Tailscale SaaS would return for the same policy.

    At boot a stored policy whose sshTests no longer pass — for example because a referenced user was deleted while the server was offline — logs a warning and the server keeps running. Fix the policy and reload.

    This feature is beta while behavioural coverage against Tailscale SaaS broadens.

    #3263

    SSH rule validation

    SSH rule parsing now trims surrounding whitespace on action, users, src, and dst, rejects empty or wildcard entries in users, rejects empty acceptEnv, and rejects negative checkPeriod. hosts: aliases are rejected as SSH destinations, non-ASCII tag names are rejected at parse time, and the wording for group-nesting cycles matches Tailscale SaaS.

    #3263

    Grants

    We now support Tailscale grants alongside ACLs. Grants extend what you can express in a policy beyond packet filtering: the app field controls application-level features like Taildrive file sharing and peer relay, and the via field steers traffic through specific tagged subnet routers or exit nodes. The ip field works like an ACL rule. Grants can be mixed with ACLs in the same policy file.

    #2180

    As part of this, we added autogroup:danger-all. It resolves to 0.0.0.0/0 and ::/0, all IP addresses, including those outside the tailnet. This replaces the old behaviour where * matched all IPs (see BREAKING below). The name is intentional: accepting traffic from the entire internet is a security-sensitive choice. autogroup:danger-all can only be used as a source.

    Node attributes (nodeAttrs)

    ACL policies now accept a nodeAttrs block. Each entry hands a list of Tailscale node capabilities to every node matching target. The accepted target forms are the same as acls.src and grants.src: users, groups, tags, hosts, prefixes, autogroup:member, autogroup:tagged, and *.

    {
      "randomizeClientPort": true,
      "nodeAttrs": [
        { "target": ["autogroup:tagged"], "attr": ["disable-captive-portal-detection"] },
        { "target": ["[email protected]"], "attr": ["nextdns:abc123"] },
      ],
    }
    

    Frequently requested capabilities this unlocks include magicdns-aaaa, disable-relay-server, disable-captive-portal-detection, nextdns:<profile> / nextdns:no-device-info, randomize-client-port, and the Taildrive drive:share / drive:access pair. The set is not limited to these, any string-only cap an operator places in policy reaches clients unchanged.

    randomizeClientPort also lands as a top-level policy field that toggles the default for every node, replacing the old server-config knob.

    A new auto_update.enabled config option controls the tailnet-wide default for client auto-update. When true, every node's CapMap carries default-auto-update: [true] so fresh clients pick up the default unless they make a local opt-in / opt-out choice.

    Policies that use the funnel cap, ipPool blocks, or autogroup:admin / autogroup:owner targets are rejected at load — those features depend on machinery headscale does not yet ship.

    #3251

    Taildrive

    Taildrive (file-sync between nodes) is now configurable through policy. Grant drive:share to the node that hosts files and drive:access to nodes that read or write them; pair with a tailscale.com/cap/drive grant to set the per-share access mode:

    {
      "nodeAttrs": [
        { "target": ["tag:fileserver"], "attr": ["drive:share"] },
        { "target": ["autogroup:member"], "attr": ["drive:access"] },
      ],
      "grants": [
        {
          "src": ["autogroup:member"],
          "dst": ["tag:fileserver"],
          "app": {
            "tailscale.com/cap/drive": [{ "shares": ["*"], "access": "rw" }],
          },
        },
      ],
    }
    

    A wildcard nodeAttrs ("target": ["*"]) hands the caps to every node when fine-grained control is not needed.

    Hostname sanitisation

    Hostnames are now santised using Tailscales magicdns sanitisation rules, matching Tailscale SaaS behavior. This means that hostnames with non-ASCII characters, special characters, or reserved DNS label characters are now transformed into valid DNS labels for MagicDNS. This improves our previously too strict sanitisation that rejected hostnames based on our guesswork and not based on the Tailscale upstream behaviour.

    Examples that previously regressed and now work:

    Input
    Raw (Hostname)
    DNS label (GivenName)
    Joe's Mac mini
    Joe's Mac mini
    joes-mac-mini
    Yuri's MacBook Pro
    Yuri's MacBook Pro
    yuris-macbook-pro
    Test@Host
    Test@Host
    test-host
    mail.server
    mail.server
    mail-server
    My-PC!
    My-PC!
    my-pc
    我的电脑
    我的电脑
    node

    #3202

    HA subnet router health probing

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

    #3194

    BREAKING

    Hostname handling

    The GivenName collision policy changed from an 8-char random hash suffix (laptop-abc12xyz) to a monotonic numeric suffix (laptop, laptop-1, laptop-2, …), matching Tailscale SaaS. Empty / all-non-ASCII hostnames now fall back to the literal node instead of invalid-<rand>. MagicDNS names change on upgrade for any node whose previous label was a random-suffix form; the raw Hostname column is unchanged. #3202

    ACL Policy

    Wildcard (*) in ACL sources and destinations now resolves to Tailscale's CGNAT range (100.64.0.0/10) and ULA range (fd7a:115c:a1e0::/48) instead of all IPs (0.0.0.0/0 and ::/0) #3036

    This better matches Tailscale's security model where * means "any node in the tailnet" rather than "any IP address"

    Policies that need to match all IP addresses including non-Tailscale IPs should use autogroup:danger-all as a source, or explicit CIDR ranges as destinations #2180

    autogroup:danger-all can only be used as a source; it cannot be used as a destination

    Note: Users with non-standard IP ranges configured in prefixes.ipv4 or prefixes.ipv6 (which is unsupported and produces a warning) will need to explicitly specify their CIDR ranges in ACL rules instead of using *

    Validate autogroup:self source restrictions matching Tailscale behavior - tags, hosts, and IPs are rejected as sources for autogroup:self destinations #3036

    Policies using tags, hosts, or IP addresses as sources for autogroup:self destinations will now fail validation

    The proto:icmp protocol name now only includes ICMPv4 (protocol 1), matching Tailscale behavior #3036

    Previously, proto:icmp included both ICMPv4 and ICMPv6

    Use proto:ipv6-icmp or protocol number 58 explicitly for ICMPv6

    Upgrade Path

    Headscale now enforces a strict version upgrade path #3083

    Skipping minor versions (e.g. 0.27 → 0.29) is blocked; upgrade one minor version at a time

    Downgrading to a previous minor version is blocked

    Configuration

    The randomize_client_port server-config key was removed; the toggle now lives in the policy file as a top-level randomizeClientPort field, matching the Tailscale-hosted schema. #3251

    Headscale refuses to start when the old key is set. Move it to the policy file referenced by policy.path:

    {
      "randomizeClientPort": true,
    }
    

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

    CLI

    headscale nodes register is deprecated in favour of headscale auth register --auth-id <id> --user <user> #1850

    The old command continues to work but will be removed in a future release

    Changes

    ACL Policy

    • Fix subnet-to-subnet peer visibility — subnet routers now correctly become peers when ACL rules reference only subnet CIDRs as sources, without requiring node IP rules #3175
    • Fix filter rule reduction to use only approved subnet routes instead of all advertised routes, matching Tailscale SaaS behavior #3175
    • Add ICMP and IPv6-ICMP protocols to default filter rules when no protocol is specified #3036
    • Fix autogroup:self handling for tagged nodes - tagged nodes no longer incorrectly receive autogroup:self filter rules #3036
    • Use CIDR format for autogroup:self destination IPs matching Tailscale behavior #3036
    • Merge filter rules with identical SrcIPs and IPProto matching Tailscale behavior - multiple ACL rules with the same source now produce a single FilterRule with combined DstPorts #3036
    • Fix exit nodes incorrectly receiving filter rules for destinations that only overlap via exit routes #3169 #3175
    • Fix address-based aliases (hosts, raw IPs) incorrectly expanding to include the matching node's other address family #2180
    • Fix identity-based aliases (tags, users, groups) resolving to IPv4 only; they now include both IPv4 and IPv6 matching Tailscale behavior #2180
    • Fix wildcard (*) source in ACLs now using actually-approved subnet routes instead of autoApprover policy prefixes #2180
    • Fix non-wildcard source IPs being dropped when combined with wildcard * in the same ACL rule #2180
    • Fix exit node approval not triggering filter rule recalculation for peers #2180
    • Policy validation error messages now include field context (e.g., src=, dst=) and are more descriptive #2180
    • Reject policies whose user@ tokens match multiple DB users; rename the duplicate via headscale users rename to load #3160
    • Evaluate the policy tests block on user-initiated writes across both acls and grants; reject policies whose tests fail (beta) #1803

    Grants

    • Add support for policy grants with ip, app, and via fields #2180
    • Add autogroup:danger-all as a source-only autogroup resolving to all IP addresses #2180
    • Add capability grants for Taildrive (cap/drive) and peer relay (cap/relay) with automatic companion capabilities #2180
    • Add per-viewer via route steering — grants with via tags control which subnet router or exit node handles traffic for each group of viewers #2180
    • Enable Taildrive node attributes on all nodes; actual access is controlled by cap/drive grants #2180

    SSH Policy

    • Add support for localpart:*@<domain> in SSH rule users field, mapping each matching user's email local-part as their OS username #3091
    • Add SSH check action support with OIDC and CLI-based approval flows #1850

    CLI

    • Add headscale auth register, headscale auth approve, and headscale auth reject CLI commands #1850
    • Deprecate headscale nodes register --key in favour of headscale auth register --auth-id #1850
    • headscale policy check --bypass-grpc-and-access-database-directly validates user@ tokens against the live user database #3160
    • Remove deprecated --namespace flag from nodes list, nodes register, and debug create-node commands (use --user instead) #3093
    • Remove deprecated namespace/ns command aliases for users and machine/machines aliases for nodes #3093
    • Fix DestroyUser deleting all pre-auth keys in the database instead of only the target user's keys #3155
    • headscale policy check evaluates the tests block when invoked with --bypass-grpc-and-access-database-directly; without the flag it warns instead of running the tests against empty data #1803

    API

    • Add auth related routes. The auth/register endpoint now expects data as JSON #1850
    • Remove gRPC reflection from the remote (TCP) server #3180

    OIDC

    • Add a confirmation page before completing node registration, showing the device hostname and machine key fingerprint #3180
    • Generalise auth templates into reusable AuthSuccess and AuthWeb components #1850
    • Unify auth pipeline with AuthVerdict type, supporting registration, reauthentication, and SSH checks #1850

    Configuration

    • Add node.expiry configuration option to set a default node key expiry for nodes registered via auth key #3122
    • Tagged nodes (registered with tagged pre-auth keys) are exempt from default expiry
    • oidc.expiry has been removed; use node.expiry instead (applies to all registration methods including OIDC)
    • ephemeral_node_inactivity_timeout is deprecated in favour of node.ephemeral.inactivity_timeout
    • Add trusted_proxies to gate True-Client-IP / X-Real-IP / X-Forwarded-For (previously honoured from any client) #3268

    Debug

    • Add node connectivity ping page for verifying control-plane reachability #3183
    • Omit secret fields (Pass, ClientSecret, APIKey) from /debug/config JSON output #3180
    • Route statsviz through tsweb.Protected #3180

    Other

    • Remove old migrations for the debian package #3185
    • Install config-example.yaml as example for the debian package #3186
    • Fix user-owned re-registration with zero client expiry and no default storing 0001-01-01 00:00:00 in the database instead of NULL #3199
    • Fix tailscaled restart on a node with no expiry resetting NULL to 0001-01-01 00:00:00 in the database, affecting both tagged and untagged nodes #3197
    • Backfill nodes.expiry rows persisted by older versions as 0001-01-01 00:00:00 to NULL, so nodes upgraded from <0.28 stop reporting as expired #3284
    • Update reverse proxy documentation for trusted_proxies configuration option #3292

    Upgrade

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

    Changelog

    6777a82 Rewrite reverse proxy documentation
    c5f3d5c auth: clamp logout expiry to now
    5228cb1 change: drop subnet-router full update, use policy change
    7d845ef db: advance allocator cursor under lock during IP backfill
    f61753e db: bound IP allocation scan so exhausted prefixes error out
    ec94573 db: drop superseded ephemeral deletions in the GC drain loop
    99ad555 db: handle degenerate prefixes in random IP allocation
    10696fa db: look up API keys by explicit primary key, not struct condition
    4c165ae db: reap ephemeral GC watcher goroutine on cancel and reschedule
    e413919 derp: clone regions when merging DERP maps
    9f0c74e dns: release lock before extra-records channel send
    8f75ee5 docker: head tailscale latest go
    f585f8a flake, go.mod: move to Go 1.26.4 and refresh dependencies
    21058d1 flake, go.mod: refresh dependencies
    29f87e5 flakehashes: refresh vendor hash after dropping gorilla/mux
    f61d21b go.mod: drop unused gorilla/mux dependency
    b892b8f hscontrol: read Apple platform via chi.URLParam, not mux.Vars
    88044f4 hscontrol: satisfy golangci-lint on changed lines
    cffdb77 mapper, change: coalesce duplicate policy recomputes per tick
    5e05652 mapper: derive incremental visibility from one shared filter
    8237ac6 mapper: filter incremental UserProfiles by ACL visibility
    cd1c208 mapper: filter peer-change patches by ACL visibility
    f4eeb94 mapper: gate broadcast sends until a connection's initial map is delivered
    c483beb mapper: guard nil Hostinfo in addNextDNSMetadata
    efdd946 mapper: keep one batched bundle per node in flight to preserve order
    ad2693f mapper: record initial-map peers only after delivery
    0e7b154 mapper: register reconnecting node atomically against cleanup
    fad8f2a mapper: test incremental visibility matches full map
    9fc88e3 noise: drop write-only nodeKey field to fix data race
    71a4ce3 noise: re-delegate SSH check when the auth session is missing (#3306)
    0921972 oidc: avoid slice panic in getCookieName for short values
    0fdff0c oidc: set SameSite=Lax on state/nonce CSRF cookies
    bceac49 policy: add NodeNeedsPeerRecompute predicate
    56cd3eb policy: guard SSHCheckParams autogroup:self against nil User
    2c9164b policy: precompute node routes in the peer-map build
    40ed210 policy: read pm.pol under the mutex
    020560f policy: remove unused top-level BuildPeerMap
    7918187 servertest: add logout/relogin storm repro with poll churn
    f497b4e state, poll: refcount poll sessions, mark offline only on last release
    a518a50 state: batch route auto-approval into one policy rebuild
    7706552 state: gate reconnect PolicyChange on NodeNeedsPeerRecompute
    06d6816 state: keep nil expiry for nodes that stay tagged on reauth
    2e24018 state: persist live NodeStore node in persistNodeToDB
    4914f9f state: reject re-auth claiming another machine's NodeKey
    eb57a3a state: reject registration claiming another machine's NodeKey
    017162d state: signal NodeStore shutdown without closing writeQueue
    08f186f state: skip database persist for keepalive-only map requests
    759381a types: add ActiveSessions poll session refcount to Node
    4f67300 types: clone Hostinfo before applying DERP change
    5a70a72 types: lock tailcfg DNS config access for extra-records updates
    bb06b90 types: skip malformed derp.urls entries instead of panicking
    84c9902 util: check RNG error before slicing url-safe random string
    ba54349 util: handle single-address IPv4 prefix in reverse DNS generation

    Original source
  • May 29, 2026
    • Date parsed from source:
      May 29, 2026
    • First seen by Releasebot:
      Aug 7, 2026
    juanfont logo

    headscale by juanfont

    v0.29.0-beta.2

    headscale adds major policy and access-control upgrades, including SSH check approvals, policy and SSH tests, Tailscale grants, Taildrive policy support, hostname sanitisation, HA subnet router health probing, and stricter upgrade and validation behavior.

    Minimum supported Tailscale client version: v1.80.0

    Tailscale ACL compatibility improvements

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

    #3036

    SSH check action

    SSH rules with "action": "check" are now supported. When a client initiates a SSH connection to a node
    with a check action policy, the user is prompted to authenticate via OIDC or CLI approval before access
    is granted. OIDC approval requires the authenticated user to own the source node; tagged source nodes
    cannot use SSH check-mode.

    A new headscale auth CLI command group supports the approval flow:

    headscale auth approve --auth-id <id> approves a pending authentication request (SSH check or web auth)

    headscale auth reject --auth-id <id> rejects a pending authentication request

    headscale auth register --auth-id <id> --user <user> registers a node (replaces deprecated headscale nodes register)

    #1850

    #3180

    Policy tests (beta)

    Headscale now evaluates the tests block in a policy file. Tests assert reachability between
    named sources and destinations and cover the whole policy — both acls and grants rules
    contribute. They run on user-initiated writes via headscale policy set, on SIGHUP reload
    (systemctl reload headscale / kill -HUP $(pidof headscale)), and on headscale policy check.
    A failing test rejects the write before it is applied, with the same error message Tailscale SaaS
    would return for the same policy.

    At boot a stored policy whose tests no longer pass — for example because a referenced user was
    deleted while the server was offline — logs a warning and the server keeps running. Fix the
    policy and reload.

    This feature is beta while behavioural coverage against Tailscale SaaS broadens.

    #3229

    SSH policy tests (beta)

    Headscale now evaluates the sshTests block in a policy file. Each entry names a source, one or
    more destination hosts, and three optional user lists: accept asserts the listed login users
    reach every destination via an accept- or check-action SSH rule, deny asserts none of them
    reach any destination, and check requires reachability specifically through a check-action
    rule. Tests run on headscale policy set, on SIGHUP reload (systemctl reload headscale /
    kill -HUP $(pidof headscale)), and on headscale policy check. A failing test rejects the
    write before it is applied, with the same error message Tailscale SaaS would return for the same
    policy.

    At boot a stored policy whose sshTests no longer pass — for example because a referenced user was
    deleted while the server was offline — logs a warning and the server keeps running. Fix the
    policy and reload.

    This feature is beta while behavioural coverage against Tailscale SaaS broadens.

    #3263

    SSH rule validation

    SSH rule parsing now trims surrounding whitespace on action, users, src, and dst,
    rejects empty or wildcard entries in users, rejects empty acceptEnv, and rejects negative
    checkPeriod. hosts: aliases are rejected as SSH destinations, non-ASCII tag names are
    rejected at parse time, and the wording for group-nesting cycles matches Tailscale SaaS.

    #3263

    Grants

    We now support Tailscale grants
    alongside ACLs. Grants extend what you can express in a policy beyond packet filtering: the app
    field controls application-level features like Taildrive file sharing and peer relay, and the via
    field steers traffic through specific tagged subnet routers or exit nodes. The ip field works like
    an ACL rule. Grants can be mixed with ACLs in the same policy file.

    #2180

    As part of this, we added autogroup:danger-all. It resolves to 0.0.0.0/0 and ::/0, all IP
    addresses, including those outside the tailnet. This replaces the old behaviour where * matched
    all IPs (see BREAKING below). The name is intentional: accepting traffic from the entire
    internet is a security-sensitive choice. autogroup:danger-all can only be used as a source.

    Node attributes (nodeAttrs)

    ACL policies now accept a nodeAttrs block. Each entry hands a list of
    Tailscale node capabilities to every node matching target. The accepted
    target forms are the same as acls.src and grants.src: users, groups,
    tags, hosts, prefixes, autogroup:member, autogroup:tagged, and *.

    {
      "randomizeClientPort": true,
      "nodeAttrs": [
        { "target": ["autogroup:tagged"], "attr": ["disable-captive-portal-detection"] },
        { "target": ["[email protected]"], "attr": ["nextdns:abc123"] },
      ],
    }
    

    Frequently requested capabilities this unlocks include magicdns-aaaa,
    disable-relay-server, disable-captive-portal-detection,
    nextdns:<profile> / nextdns:no-device-info, randomize-client-port,
    and the Taildrive drive:share / drive:access pair. The set is not
    limited to these, any string-only cap an operator places in policy
    reaches clients unchanged.

    randomizeClientPort also lands as a top-level policy field that toggles
    the default for every node, replacing the old server-config knob.

    A new auto_update.enabled config option controls the tailnet-wide
    default for client auto-update. When true, every node's CapMap carries
    default-auto-update: [true] so fresh clients pick up the default
    unless they make a local opt-in / opt-out choice.

    Policies that use the funnel cap, ipPool blocks, or
    autogroup:admin / autogroup:owner targets are rejected at load —
    those features depend on machinery headscale does not yet ship.

    #3251

    Taildrive

    Taildrive (file-sync between
    nodes) is now
    configurable through policy. Grant drive:share to the node that
    hosts files and drive:access to nodes that read or write them; pair
    with a tailscale.com/cap/drive grant to set the per-share access
    mode:

    {
      "nodeAttrs": [
        { "target": ["tag:fileserver"], "attr": ["drive:share"] },
        { "target": ["autogroup:member"], "attr": ["drive:access"] },
      ],
      "grants": [
        {
          "src": ["autogroup:member"],
          "dst": ["tag:fileserver"],
          "app": {
            "tailscale.com/cap/drive": [{ "shares": ["*"], "access": "rw" }],
          },
        },
      ],
    }
    

    A wildcard nodeAttrs ("target": ["*"]) hands the caps to every
    node when fine-grained control is not needed.

    Hostname sanitisation

    Hostnames are now santised using Tailscales magicdns sanitisation rules, matching Tailscale SaaS behavior. This means that hostnames with non-ASCII characters, special characters, or reserved DNS label characters are now transformed into valid DNS labels for MagicDNS. This improves our previously too strict sanitisation that rejected hostnames based on our guesswork and not based on the Tailscale upstream behaviour.

    Examples that previously regressed and now work:

    Input
    Raw (Hostname)
    DNS label (GivenName)
    Joe's Mac mini
    Joe's Mac mini
    joes-mac-mini
    Yuri's MacBook Pro
    Yuri's MacBook Pro
    yuris-macbook-pro
    Test@Host
    Test@Host
    test-host
    mail.server
    mail.server
    mail-server
    My-PC!
    My-PC!
    my-pc
    我的电脑
    我的电脑
    node

    #3202

    HA subnet router health probing

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

    #3194

    BREAKING

    Hostname handling

    The GivenName collision policy changed from an 8-char random hash suffix (laptop-abc12xyz) to a monotonic numeric suffix (laptop, laptop-1, laptop-2, …), matching Tailscale SaaS. Empty / all-non-ASCII hostnames now fall back to the literal node instead of invalid-<rand>. MagicDNS names change on upgrade for any node whose previous label was a random-suffix form; the raw Hostname column is unchanged. #3202

    ACL Policy

    Wildcard (*) in ACL sources and destinations now resolves to Tailscale's CGNAT range (100.64.0.0/10) and ULA range (fd7a:115c:a1e0::/48) instead of all IPs (0.0.0.0/0 and ::/0) #3036

    This better matches Tailscale's security model where * means "any node in the tailnet" rather than "any IP address"

    Policies that need to match all IP addresses including non-Tailscale IPs should use autogroup:danger-all as a source, or explicit CIDR ranges as destinations #2180

    autogroup:danger-all can only be used as a source; it cannot be used as a destination

    Note: Users with non-standard IP ranges configured in prefixes.ipv4 or prefixes.ipv6 (which is unsupported and produces a warning) will need to explicitly specify their CIDR ranges in ACL rules instead of using *

    Validate autogroup:self source restrictions matching Tailscale behavior - tags, hosts, and IPs are rejected as sources for autogroup:self destinations #3036

    Policies using tags, hosts, or IP addresses as sources for autogroup:self destinations will now fail validation

    The proto:icmp protocol name now only includes ICMPv4 (protocol 1), matching Tailscale behavior #3036

    Previously, proto:icmp included both ICMPv4 and ICMPv6

    Use proto:ipv6-icmp or protocol number 58 explicitly for ICMPv6

    Upgrade Path

    Headscale now enforces a strict version upgrade path #3083

    Skipping minor versions (e.g. 0.27 → 0.29) is blocked; upgrade one minor version at a time

    Downgrading to a previous minor version is blocked

    Configuration

    The randomize_client_port server-config key was removed; the
    toggle now lives in the policy file as a top-level
    randomizeClientPort field, matching the Tailscale-hosted schema. #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

    ea8fc72 db: backfill zero-time node expiry to NULL
    77ba225 db: treat Go module pseudo-versions as dev builds
    66a5f99 gh: pre-pull released tailscale images for fork-PR CI
    79562b9 hi: add list-versions subcommand
    171fd7a policy: key autogroup:self invalidation on UserID not User view
    4483fd0 tsic, gh: keep unstable on Docker Hub
    2e49f3d tsic: pull tailscale images from ghcr.io

    Original source
  • May 22, 2026
    • Date parsed from source:
      May 22, 2026
    • First seen by Releasebot:
      Aug 7, 2026
    juanfont logo

    headscale by juanfont

    v0.29.0-beta.1

    headscale ships major policy and SSH upgrades with grants, node attributes, Taildrive controls, HA subnet router health probing, stricter version upgrade rules, and better Tailscale compatibility across ACLs, hostname handling, and auth flows.

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

    Original source

This is the end. You've seen all the release notes in this feed!

Releasebot

Curated by the Releasebot team

Releasebot is an aggregator of official release notes from hundreds of software vendors and thousands of sources.

Our editorial process involves the manual review and audit of release notes procured with the help of automated systems.