PackageTrack
Sign in Get early access

github.com/juanfont/headscale

v0.29.3 #43 most downloaded on Go modules juanfont/headscale

What this package is like to depend on

Last release 25 days ago

29 Jul 2026

Ships fairly regularly

a new release about every 3 weeks

Some releases are documented

notes for 37 of 75 stable releases

Nothing withdrawn

no release was ever pulled

6 years old

552 releases · first in 2020

58 releases in the last 12 months

see the full history below

Release timeline

552 releases · Aug 2020 to Jul 2026
2021 2022 2023 2024 2025 2026
Release Pre-release

Releases

latest 60 of 552
  1. v0.29.3 29 Jul 2026
    Release notes

    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
    Open source →
    Release notes

    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
    • Remove a leftover trace log that always rendered a JSON marshaling error #3398
    Open source →
    Release notes

    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
    Open source →
  2. v0.29.2 01 Jul 2026
    Release notes

    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
    Open source →
    Release notes

    Minimum supported Tailscale client version: v1.80.0

    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
    Open source →
  3. v0.29.2-0.20260701144739-8eea89488c64 01 Jul 2026 pre-release

    Nothing published for this version

  4. v0.29.1 18 Jun 2026
    Release notes

    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'
    Open source →
    Release notes

    Minimum supported Tailscale client version: v1.80.0

    Changes

    • Fix nodes with tags='null' losing their assigned user on upgrade #3325
    Open source →
  5. v0.29.1-0.20260618132111-74928c024129 18 Jun 2026 pre-release

    Nothing published for this version

  6. v0.29.0 17 Jun 2026
    Release notes

    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

    Open source →
    Release notes

    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
    Open source →
  7. v0.29.0-beta.4 15 Jun 2026 pre-release
    Release notes

    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
    Open source →
  8. v0.29.0-beta.3.0.20260615100239-a5ef3aff15ab 15 Jun 2026 pre-release

    Nothing published for this version

  9. v0.29.0-beta.3 11 Jun 2026 pre-release
    Release notes

    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

    • 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
    Open source →
  10. v0.29.0-beta.2.0.20260609132118-f585f8a94d5c 09 Jun 2026 pre-release

    Nothing published for this version

  11. v0.29.0-beta.2 29 May 2026 pre-release
    Release notes

    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

    • 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
    Open source →
  12. v0.29.0-beta.1.0.20260527074905-ea8fc7257009 27 May 2026 pre-release

    Nothing published for this version

  13. v0.29.0-beta.1.0.20260522122924-4483fd0cad38 22 May 2026 pre-release

    Nothing published for this version

  14. v0.29.0-beta.1 22 May 2026 pre-release
    Release notes

    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

    Note truncated.

    Open source →
  15. v0.28.1-0.20260402192427-23a5f1b62855 02 Apr 2026 pre-release

    Nothing published for this version

  16. v0.28.1-0.20260401131042-4f040dead2ba 01 Apr 2026 pre-release

    Nothing published for this version

  17. v0.28.1-0.20260319060835-568baf3d021b 19 Mar 2026 pre-release

    Nothing published for this version

  18. v0.28.1-0.20260306131844-65880ecb5867 06 Mar 2026 pre-release

    Nothing published for this version

  19. v0.28.1-0.20260228131411-5c449db12589 28 Feb 2026 pre-release

    Nothing published for this version

  20. v0.28.1-0.20260207072351-20dff82f95e3 07 Feb 2026 pre-release

    Nothing published for this version

  21. v0.28.1-0.20260206064029-bfb6fd80dfa9 06 Feb 2026 pre-release

    Nothing published for this version

  22. v0.28.0 04 Feb 2026
    Release notes

    Minimum supported Tailscale client version: v1.74.0

    Tags as identity

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

    User-owned nodes can now request tags during registration using --advertise-tags. Tags are validated against the tagOwners policy and applied at registration time. Tags can be managed via the CLI or API after registration. Tagged nodes can return to user-owned by re-authenticating with tailscale up --advertise-tags= --force-reauth.

    A one-time migration will validate and migrate any RequestTags (stored in hostinfo) to the tags column. Tags are validated against your policy's tagOwners rules during migration. #3011

    Smarter map updates

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

    Pre-authentication key security improvements

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

    Web registration templates redesign

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

    Database migration support removed for pre-0.25.0 databases

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

    BREAKING

    • API: The Node message in the gRPC/REST API has been simplified - the ForcedTags, InvalidTags, and ValidTags fields have been removed and replaced with a single Tags field that contains the node's applied tags #2993

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

      • headscale preauthkeys create no longer requires --user flag (optional for tracking creation)
      • headscale preauthkeys list lists all keys (no longer filtered by user)
      • headscale preauthkeys expire --id <ID> replaces --user <USER> <KEY>
      • headscale preauthkeys delete --id <ID> replaces --user <USER> <KEY>

      Before:

      headscale preauthkeys create --user 1 --reusable --tags tag:server
      headscale preauthkeys list --user 1
      headscale preauthkeys expire --user 1 <KEY>
      headscale preauthkeys delete --user 1 <KEY>
      

      After:

      headscale preauthkeys create --reusable --tags tag:server
      headscale preauthkeys list
      headscale preauthkeys expire --id 123
      headscale preauthkeys delete --id 123
      
    • Tags: The gRPC SetTags endpoint now allows converting user-owned nodes to tagged nodes by setting tags. #2885

    • Tags: Tags are now resolved from the node's stored Tags field only #2931

      • --advertise-tags is processed during registration, not on every policy evaluation
      • PreAuthKey tagged devices ignore --advertise-tags from clients
      • User-owned nodes can use --advertise-tags if authorized by tagOwners policy
      • Tags can be managed via CLI (headscale nodes tag) or the SetTags API after registration
    • Database migration support removed for pre-0.25.0 databases #2883

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

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

      • When true (default), only verified emails can authenticate via OIDC in conjunction with oidc.allowed_domains or oidc.allowed_users. Previous versions allowed to authenticate with an unverified email but did not store the email address in the user profile. This is now rejected during authentication with an unverified email error.
      • When false, unverified emails are allowed for OIDC authentication and the email address is stored in the user profile regardless of its verification state.
    • SSH Policy: Wildcard (*) is no longer supported as an SSH destination #3009

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

      Before:

      { "action": "accept", "src": ["group:admins"], "dst": ["*"], "users": ["root"] }
      

      After:

      { "action": "accept", "src": ["group:admins"], "dst": ["autogroup:member", "autogroup:tagged"], "users": ["root"] }
      
    • SSH Policy: SSH source/destination validation now enforces Tailscale's security model #3010

      Per Tailscale SSH documentation, the following rules are now enforced:

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

      Invalid policies now rejected at load time:

      // INVALID: tag source to user destination
      {"src": ["tag:server"], "dst": ["alice@"], ...}
      
      // INVALID: autogroup:tagged to autogroup:member
      {"src": ["autogroup:tagged"], "dst": ["autogroup:member"], ...}
      
      // INVALID: group to specific user (use autogroup:self instead)
      {"src": ["group:admins"], "dst": ["alice@"], ...}
      

      Valid patterns:

      // Users/groups can SSH to their own devices via autogroup:self
      {"src": ["group:admins"], "dst": ["autogroup:self"], ...}
      
      // Users/groups can SSH to tagged devices
      {"src": ["group:admins"], "dst": ["autogroup:tagged"], ...}
      
      // Tagged devices can SSH to other tagged devices
      {"src": ["autogroup:tagged"], "dst": ["autogroup:tagged"], ...}
      
      // Same user can SSH to their own devices
      {"src": ["alice@"], "dst": ["alice@"], ...}
      

    Changes

    • Smarter change notifications send partial map updates and node removals instead of full maps #2961
      • Send lightweight endpoint and DERP region updates instead of full maps #2856
    • Add NixOS module in repository for faster iteration #2857
    • Add favicon to webpages #2858
    • Redesign OIDC callback and registration web templates #2832
    • Reclaim IPs from the IP allocator when nodes are deleted #2831
    • Add bcrypt hashing for pre-authentication keys #2853
    • Add prefix to API keys (hskey-api-{prefix}-{secret}) #2853
    • Add prefix to registration keys for web authentication tracking (hskey-reg-{random}) #2853
    • Tags can now be tagOwner of other tags #2930
    • Add taildrop.enabled configuration option to enable/disable Taildrop file sharing #2955
    • Allow disabling the metrics server by setting empty metrics_listen_addr #2914
    • Log ACME/autocert errors for easier debugging #2933
    • Improve CLI list output formatting #2951
    • Use Debian 13 distroless base images for containers #2944
    • Fix ACL policy not applied to new OIDC nodes until client restart #2890
    • Fix autogroup:self preventing visibility of nodes matched by other ACL rules #2882
    • Fix nodes being rejected after pre-authentication key expiration #2917
    • Fix list-routes command respecting identifier filter with JSON output #2927
    • Add --id flag to expire/delete commands as alternative to --prefix for API Keys #3016
    Open source →
  23. v0.28.0-beta.2 22 Jan 2026 pre-release

    Nothing published for this version

  24. v0.28.0-beta.1.0.20251223192257-9146140217ec 23 Dec 2025 pre-release

    Nothing published for this version

  25. v0.28.0-beta.1 18 Dec 2025 pre-release

    Nothing published for this version

  26. v0.27.2-rc.1 30 Nov 2025 pre-release

    Nothing published for this version

  27. v0.27.2-0.20251115164400-e0c9e18e222a 15 Nov 2025 pre-release

    Nothing published for this version

  28. v0.27.2-0.20251114135127-21af106f68fa 14 Nov 2025 pre-release

    Nothing published for this version

  29. v0.27.2-0.20251113193849-7fb0f9a50120 13 Nov 2025 pre-release

    Nothing published for this version

  30. v0.27.2-0.20251113104410-75247f82b85a 13 Nov 2025 pre-release

    Nothing published for this version

  31. v0.27.2-0.20251113061522-665cc4409498 13 Nov 2025 pre-release

    Nothing published for this version

  32. v0.27.2-0.20251112192654-8394e7094a42 12 Nov 2025 pre-release

    Nothing published for this version

  33. v0.27.2-0.20251112153636-da9018a0ebc2 12 Nov 2025 pre-release

    Nothing published for this version

  34. v0.27.2-0.20251112034657-218a8db1b901 12 Nov 2025 pre-release

    Nothing published for this version

  35. v0.27.2-0.20251111232700-1dcb04ce9b2f 11 Nov 2025 pre-release

    Nothing published for this version

  36. v0.27.2-0.20251111193523-6d24afba1ce7 11 Nov 2025 pre-release

    Nothing published for this version

  37. v0.27.1 11 Nov 2025
    Release notes

    Minimum supported Tailscale client version: v1.64.0

    Changes

    • Expire nodes with a custom timestamp #2828
    • Fix issue where node expiry was reset when tailscaled restarts #2875
    • Fix OIDC authentication when multiple login URLs are opened #2861
    • Fix node re-registration failing with expired auth keys #2859
    • Remove old unused database tables and indices #2844 #2872
    • Ignore litestream tables during database validation #2843
    • Fix exit node visibility to respect ACL rules #2855
    • Fix SSH policy becoming empty when unknown user is referenced #2874
    • Fix policy validation when using bypass-grpc mode #2854
    • Fix autogroup:self interaction with other ACL rules #2842
    • Fix flaky DERP map shuffle test #2848
    • Use current stable base images for Debian and Alpine containers #2827
    Open source →
  38. v0.27.1-0.20251111111259-4728a2ba9ea6 11 Nov 2025 pre-release

    Nothing published for this version

  39. v0.27.1-0.20251110193412-21e3f2598de6 10 Nov 2025 pre-release

    Nothing published for this version

  40. v0.27.1-0.20251102121959-2024219bd10a 02 Nov 2025 pre-release

    Nothing published for this version

  41. v0.27.1-0.20251101070913-f9bb88ad24d9 01 Nov 2025 pre-release

    Nothing published for this version

  42. v0.27.1-0.20251028045526-0a43aab8f5c8 28 Oct 2025 pre-release

    Nothing published for this version

  43. v0.27.1-0.20251027111402-19a33394f6e0 27 Oct 2025 pre-release

    Nothing published for this version

  44. v0.27.0 27 Oct 2025
    Release notes

    Minimum supported Tailscale client version: v1.64.0

    Database integrity improvements

    This release includes a significant database migration that addresses longstanding issues with the database schema and data integrity that has accumulated over the years. The migration introduces a schema.sql file as the source of truth for the expected database schema to ensure new migrations that will cause divergence does not occur again.

    These issues arose from a combination of factors discovered over time: SQLite foreign keys not being enforced for many early versions, all migrations being run in one large function until version 0.23.0, and inconsistent use of GORM's AutoMigrate feature. Moving forward, all new migrations will be explicit SQL operations rather than relying on GORM AutoMigrate, and foreign keys will be enforced throughout the migration process.

    We are only improving SQLite databases with this change - PostgreSQL databases are not affected.

    Please read the PR description for more technical details about the issues and solutions.

    SQLite Database Backup Example:

    # Stop headscale
    systemctl stop headscale
    
    # Backup sqlite database
    cp /var/lib/headscale/db.sqlite /var/lib/headscale/db.sqlite.backup
    
    # Backup sqlite WAL/SHM files (if they exist)
    cp /var/lib/headscale/db.sqlite-wal /var/lib/headscale/db.sqlite-wal.backup
    cp /var/lib/headscale/db.sqlite-shm /var/lib/headscale/db.sqlite-shm.backup
    
    # Start headscale (migration will run automatically)
    systemctl start headscale
    

    DERPMap update frequency

    The default DERPMap update frequency has been changed from 24 hours to 3 hours. If you set the derp.update_frequency configuration option, it is recommended to change it to 3h to ensure that the headscale instance gets the latest DERPMap updates when upstream is changed.

    Autogroups

    This release adds support for the three missing autogroups: self (experimental), member, and tagged. Please refer to the documentation for a detailed explanation.

    autogroup:self is marked as experimental and should be used with caution, but we need help testing it. Experimental here means two things; first, generating the packet filter from policies that use autogroup:self is very expensive, and it might perform, or straight up not work on Headscale installations with a large number of nodes. Second, the implementation might have bugs or edge cases we are not aware of, meaning that nodes or users might gain more access than expected. Please report bugs.

    Node store (in memory database)

    Under the hood, we have added a new datastructure to store nodes in memory. This datastructure is called NodeStore and aims to reduce the reading and writing of nodes to the database layer. We have not benchmarked it, but expect it to improve performance for read heavy workloads. We think of it as, "worst case" we have moved the bottle neck somewhere else, and "best case" we should see a good improvement in compute resource usage at the expense of memory usage. We are quite excited for this change and think it will make it easier for us to improve the code base over time and make it more correct and efficient.

    BREAKING

    • Remove support for 32-bit binaries #2692
    • Policy: Zero or empty destination port is no longer allowed #2606
    • Stricter hostname validation #2383
      • Hostnames must be valid DNS labels (2-63 characters, alphanumeric and hyphens only, cannot start/end with hyphen)
      • Client Registration (New Nodes): Invalid hostnames are automatically renamed to invalid-XXXXXX format
        • my-laptop → accepted as-is
        • My-Laptopmy-laptop (lowercased)
        • my_laptopinvalid-a1b2c3 (underscore not allowed)
        • test@hostinvalid-d4e5f6 (@ not allowed)
        • laptop-🚀invalid-j1k2l3 (emoji not allowed)
      • Hostinfo Updates / CLI: Invalid hostnames are rejected with an error
        • Valid names are accepted or lowercased
        • Names with invalid characters, too short (<2), too long (>63), or starting/ending with hyphen are rejected

    Changes

    • Database schema migration improvements for SQLite #2617
      • IMPORTANT: Backup your SQLite database before upgrading
      • Introduces safer table renaming migration strategy
      • Addresses longstanding database integrity issues
    • Add flag to directly manipulate the policy in the database #2765
    • DERPmap update frequency default changed from 24h to 3h #2741
    • DERPmap update mechanism has been improved with retry, and is now failing conservatively, preserving the old map upon failure. #2741
    • Add support for autogroup:member, autogroup:tagged #2572
    • Fix bug where return routes were being removed by policy #2767
    • Remove policy v1 code #2600
    • Refactor Debian/Ubuntu packaging and drop support for Ubuntu 20.04. #2614
    • Remove redundant check regarding noise config #2658
    • Refactor OpenID Connect documentation #2625
    • Don't crash if config file is missing #2656
    • Adds /robots.txt endpoint to avoid crawlers #2643
    • OIDC: Use group claim from UserInfo #2663
    • OIDC: Update user with claims from UserInfo before comparing with allowed groups, email and domain #2663
    • Policy will now reject invalid fields, making it easier to spot spelling errors #2764
    • Add FAQ entry on how to recover from an invalid policy in the database #2776
    • EXPERIMENTAL: Add support for autogroup:self #2789
    • Add healthcheck command #2659
    Open source →
  45. v0.27.0-beta.2.0.20251024152253-e68e2288f799 24 Oct 2025 pre-release

    Nothing published for this version

  46. v0.27.0-beta.2 23 Oct 2025 pre-release

    Nothing published for this version

  47. v0.27.0-beta.1.0.20251018055659-46477b802148 18 Oct 2025 pre-release

    Nothing published for this version

  48. v0.27.0-beta.1 17 Oct 2025 pre-release

    Nothing published for this version

  49. v0.26.1 06 Jun 2025
    Release notes

    Changes

    • Ensure nodes are matching both node key and machine key when connecting. #2642
    Open source →
  50. v0.26.1-0.20251011035739-c4a8c038cdd6 11 Oct 2025 pre-release

    Nothing published for this version

  51. v0.26.1-0.20250917143229-bd35fcf338d6 17 Sep 2025 pre-release

    Nothing published for this version

  52. v0.26.1-0.20250913061501-30d12dafed21 13 Sep 2025 pre-release

    Nothing published for this version

  53. v0.26.1-0.20250827150913-8e25f7f9dd12 27 Aug 2025 pre-release

    Nothing published for this version

  54. v0.26.1-0.20250815151138-30a1f7e68e17 15 Aug 2025 pre-release

    Nothing published for this version

  55. v0.26.1-0.20250704154701-855c48aec287 04 Jul 2025 pre-release

    Nothing published for this version

  56. v0.26.1-0.20250704125817-ded049b90555 04 Jul 2025 pre-release

    Nothing published for this version

  57. v0.26.1-0.20250704120059-3bad5d559098 04 Jul 2025 pre-release

    Nothing published for this version

  58. v0.26.1-0.20250704085137-d461db3abdf2 04 Jul 2025 pre-release

    Nothing published for this version

  59. v0.26.1-0.20250624124544-e73b2a9fb9db 24 Jun 2025 pre-release

    Nothing published for this version

  60. v0.26.1-0.20250606101411-bad783321e8e 06 Jun 2025 pre-release

    Nothing published for this version

Every package, every release, already written down.

The archive is open and free. Watching your own project is what we are building next.

Browse the archive