Changelog
New features and fixes in every Northplane release.
Changes
- The docs now live at northplane.sageware.io, built from
main. Links in the interface, the README andconfig.example.yamlpoint there instead of headplane.net.
Upgrading
- DNS records are only read from and written to the file in Headscale's
dns.extra_records_path. Records listed inline underdns.extra_recordsshow as read-only, so move them into the file to keep editing them. - The
procandkubernetesintegrations now restart Headscale instead of reloading it, so Headscale needs a supervisor that brings it back: systemd withRestart=always, which Headscale's own unit and the NixOS module already set, or the kubelet. - Enabling more than one integration now turns them all off, with an error in the log. Keep one.
- After signing in with single sign-on, only owners, admins and IT admins can pick which Headscale user their account links to. Everyone else links automatically or is linked from the Users page.
- Builds no longer read
__INTERNAL_PREFIX. If a build used it to serve Headplane somewhere other than/admin, setserver.base_pathto that path instead.
Changes
- Headplane is now called Northplane. The name changes in the interface, the docs and the README. Nothing operators configure changes: the
HEADPLANE_*environment variables, the config file keys, the container image and the/adminpath all keep working. - The interface is built on shadcn/ui. Buttons, inputs, menus, dialogs, tabs, tables and notices share shadcn's look in light and dark mode, with Northplane's colors.
- The navigation bar shows which Headscale server Northplane is managing, from
headscale.public_url(orheadscale.urlwhen that isn't set), next to the Northplane name. - Headplane can be served under a path other than
/adminby settingserver.base_path, without building it from source.#637 - Headplane has a new color palette built from its logo: warm greys, black primary buttons, and a muted blue that marks links, focus, and the current page. Dark mode now separates the page, menus, and dialogs by shade so overlays stand out, and secondary text is easier to read in both themes.#651
- Dialogs can now be closed by clicking outside them, except the ones that confirm a destructive action like removing a machine.
- The navigation bar is slimmer, and on small screens the pages now live in a menu instead of a second row of tabs.
- The navigation bar stays at the top while you scroll. With a mouse or trackpad, the page scrollbar is now a thin overlay that fades in while scrolling, so pages no longer shift sideways when they become long enough to scroll.
- The machines list is easier to scan: each machine shows its status next to its name, its tags and badges on one line, and both of its addresses. Badges only use color when something needs attention, like an expired key, a key expiring within a week, or routes waiting for approval. Click an address to copy it.#654
- With the Headplane agent enabled, the machines list marks clients running an older Tailscale release than the latest stable one. Headplane checks pkgs.tailscale.com for the latest version once a day in the background.
- The machine page uses the same layout as DNS and Settings, with its details split into Routing, Machine, Addresses, and Connectivity sections instead of boxed cards. Addresses, domains, and the node key copy when clicked.
- The Users page matches the new machines list: each user shows their avatar, email, and groups on one line, along with when their machines were last seen. Add user now sits in the page header, and users whose profile picture fails to load get an initial instead of a broken image.
- The DNS page is reorganized: the tailnet name, Magic DNS, and the local DNS override sit together under General, and each section has its heading and add button in a column beside its list. Split DNS nameservers now sit in the same list as global ones, marked with the domain they answer for, and nameservers can be reordered the same way as search domains.
- Settings has a sidebar for Auth keys, Sign-in restrictions, and Agent instead of a page of links, and
/settingsopens auth keys directly. Auth keys are listed in a table, newest first, with filters like the machines list. Sign-in restrictions and the agent status use the same layout as the DNS page. - The agent page shows more of what the agent is doing: how many times in a row a sync has failed, how often it syncs, when its process started, its own machine and Tailscale version, and which machines were missing from the last sync and how old their details are.
- The sign-in page is redesigned: the form sits on one side and an animated tailnet on the other, with machines coming and going and passing packets between them. Single sign-on sits above the API key field, sign-in errors are shorter and appear under the field, and the signed-out and single sign-on error pages share the same layout.
- Accounts that sign in with single sign-on keep trying to link to a matching Headscale user (same subject or email) after sign-in, not only during it. With single sign-on set up on Headscale too, an account links by itself once its first device signs in.
- Only admins can pick which Headscale user their account links to, on a page shown once after signing in that they can skip. Previously anyone signing in for the first time could claim any unlinked Headscale user, and with it that user's machines and auth keys wherever their role allowed. Everyone else is linked automatically or by an admin from the Users page.
- People without dashboard access get a new home page that walks through installing Tailscale and connecting it to this network, and shows whether their account is linked yet. It updates live, so it notices as soon as their first device signs in.
- Error pages are redesigned. Inside the app, an error keeps the navigation bar and explains what went wrong in a sentence, with raw details tucked under Details and a Try again button where retrying can help. Errors that stop the app from loading, and unknown addresses, use the sign-in layout.
- The routes dialog and the lists in the tags, groups, auth key, and policy rule dialogs use the same rows as the DNS and Settings pages, with a Remove button on each entry instead of an X.
- Pre-auth keys are called auth keys everywhere in Headplane, matching Tailscale. Generate Pre-auth Key in the Add machine menu is now Create auth key.
- Browser SSH's username prompt, offline machine page, and error pages use the sign-in layout, with shorter error messages that link straight to the matching troubleshooting section.
- Open pages notice within a few seconds when Headscale goes down or comes back, and update on their own. The "Can't reach Headscale" banner and error pages clear without a reload.
- When the Headscale configuration is writable but no integration is set up to restart Headscale, the DNS and sign-in restrictions pages now say that changes only take effect once Headscale restarts.
- The Rules tab on the Access Control page can now edit grants and auto approvers. Grants set ports and protocols separately from the destination, can route traffic through tagged subnet routers or exit nodes with
via, and keep app capabilities as written. Grants need Headscale 0.29 or newer. Auto approvers cover both subnet routes and exit nodes. - The Access Control file editor now formats HuJSON, preserves comments while formatting, and shows syntax errors before an invalid policy can be saved.
Fixes
- Saving from the Rules or Tags & Groups tabs no longer drops the comma from text like
,}or,]inside a policy value. - Live updates work again right after signing in. Previously the machines list and other pages only refreshed on their own after a full page reload.
- Changes to DNS settings and sign-in restrictions now actually apply with the
procandkubernetesintegrations. Both sentSIGHUP, which only makes Headscale reload its ACL policy. They now stop Headscale withSIGTERMand wait for systemd or Kubernetes to restart it, so Headscale needs a supervisor that restarts it. - If Headscale doesn't come back after a change, Headplane now shows an error instead of reporting success.
- Enabling two integrations at once is now caught at startup. Previously Headplane only refused when all three were on, and otherwise silently picked Docker.
- The cookie that tracked SSO state was not signed. It is now signed with
server.cookie_secretand cleared once SSO succeeds.#659 - Profile pictures from OIDC now correctly update on each sign-in. If the provider stops sending pictures, Headplane removes the picture instead of leaving a potentially broken image behind.
- Browser SSH correctly handles full-screen programs like
btopandhtopwhere it may have previously rendered layouts incorrectly. - A machine's page now requires permission to view machines, like the machines list. Previously anyone signed in could open it by its address.
- The auth keys page only shows people with self-service keys (like auditors and viewers) their own keys. Previously it sent every user's keys to anyone signed in, in full on Headscale 0.27, even when the page itself said they had no access. Self-service users also can no longer create tag-only keys or expire someone else's key by submitting the form directly.
- Syncing the agent now requires permission to view machines, like the agent page itself. Previously anyone signed in could trigger a sync.
- Enabling key expiry on a machine no longer signs it out on the spot. Its key now expires in 180 days, matching Tailscale. On Headscale 0.29, where machines added with an auth key start with key expiry off, this affected every machine the first time expiry was turned on.
- Signing in ignores spaces and line breaks pasted around an API key. A key with stray characters inside it is reported as invalid instead of "Couldn't reach Headscale."
- Errors on the machine page and in Settings no longer replace the whole app with an error message. The navigation bar and the Settings sidebar stay so you can move on.
- The agent page loads while Headscale is unreachable, showing the agent's sync status without the machine details.
- An unexpected error during a background agent sync no longer crashes Headplane.
- Fixed the error page crashing on errors that bundle several failures together.
- The logo in the navigation bar now follows the theme picked in Headplane instead of the system theme.
- Fields with an error keep their red border while focused instead of turning blue.
- Browser SSH no longer connects as a Headscale user that's already linked to someone else's account just because the email matches. An unlinked account now links the same way it does everywhere else first.
- The agent no longer creates a new auth key everytime it starts, which caused duplicate machines to be registered on Headscale 0.28.
- The agent now reuses its existing key and will delete leftover machines on Headscale if it detects a duplicate.
- Restart resiliency and crash recovery have been improved. There is now a 20 second heartbeat to catch an unresponsive agent.
- Headplane now rejects an agent work directory it can't write to.
- If the agent's machine is deleted while Headplane is stopped, the agent registers again on the next start instead of waiting for a login that never comes.
- Creating or renaming a user to a name that's already taken now says so, instead of showing a database error from Headscale.
- Linking an account to a Headscale user that no longer exists now shows an error instead of saving a broken link.
- Changing the owner's role or transferring ownership to yourself now explains why it can't be done, instead of asking to try again.
- Removing the last global nameserver while local DNS is overridden, or turning the override on without a global nameserver, no longer saves a config Headscale refuses to start with. Headplane now explains what to change first.
- Removing or reordering a nameserver, search domain or DNS record that changed in another tab now says so, instead of restarting Headscale for nothing.
- Turning off key expiry on Headscale 0.27 or 0.28 no longer signs the machine out. Headplane now explains that this needs Headscale 0.29 or newer.
- Acting on a machine that was deleted elsewhere now says the machine no longer exists.
- The ACL page no longer shows an empty, editable policy when Headscale can't load it. With Headscale's default file-mode configuration it now shows the policy as read-only, and other errors are reported instead of offering to overwrite the policy.
- Browser SSH shows why a session couldn't open, such as SSH rules that don't allow the chosen user, instead of a spinner that never ends. A session that stalls while connecting now gives up after 30 seconds.
- Browser SSH works with SSH rules that use the
checkaction on Headscale 0.29. The page links to the sign-in Headscale asks for, and the session opens once it's done.#591 - Browser SSH no longer opens a second SSH session right after the first one connects.
- Browser SSH no longer says it isn't available when Headplane can't reach its own public address, as happens behind some reverse proxies. The page checks for its files when it loads them instead.
This release fixes actions failing with "Bad Request" or "Unexpected Server Error" when Headplane runs behind a reverse proxy that terminates HTTPS. If you made proxy rules rewriting the Origin header as a workaround, it's safe to remove them now.
Upgrading
- Single sign-on stays off until
server.base_urlis set to Headplane's public URL. - Proxy rules that rewrite the
Originheader, added as a workaround for 0.8.0, can be removed.
Fixes
- Fixed several broken actions (such as creating auth keys) that began failing with "Bad Request" or "Unexpected Server Error" after Headplane 0.8.0 when sitting behind a reverse proxy that terminates TLS. If you added a proxy rule rewriting the
Originheader as a workaround, it's safe to remove.#657#658 - Fixed a bug where the Add nameserver dialog would not show the Restrict to domain switch in the correct state when closing and reopening the dialog after adding or canceling a split DNS nameserver.#635
- Single sign-on no longer fails with "Unexpected Server Error" when
server.base_urlis missing. Headplane now logs a warning at startup and turns off single sign-on untilserver.base_urlis set to Headplane's public URL.#585 - Logs now explain why a request to Headscale failed, such as Headscale being unreachable or rejecting the API key, instead of printing
[object Object]. This covers signing in with an API key, linking an account after single sign-on, and agent syncs.#587#600 - Deleting a Headscale user that's linked to a Headplane account now unlinks the account. Previously the account kept pointing at the deleted user, showed its raw single sign-on ID instead of a name, and wasn't marked as Not linked.#625
Upgrading
- Building from source needs Go 1.27.1 or newer.
Changes
- Added a structured editor to the Access Control page with Rules and Tags & Groups tabs for editing
acls,ssh,hosts,groups, andtagOwnerswithout touching HuJSON. Sources and destinations are suggested from the groups, tags, hosts, and users in your tailnet. Nothing is applied until you press Save, and the file editor is still there for everything else. Editing through the structured editor drops comments from the policy, and Headplane warns you when that would happen.#607#608 - The Users page now shows which policy groups each user belongs to, and Edit groups in the row menu lets you change them (requires the
write_policypermission). - The machine tag dialog now flags tags that are missing from
tagOwners, since Headscale accepts them but they never match any rule. - Added
config.oidc.jwks_endpointto allow manually setting the JWKs keyset for OIDC.#620 - Docker images now publish floating minor version tags (e.g.,
0.8) that always point to the latest patch release, so you can pin to a minor version in yourdocker-compose.yml.
Fixes
- The footer now stacks on narrow screens instead of forcing the whole page to scroll sideways on phones.#648
- User pickers across Headplane, including the pre-auth key, new machine, change owner, and machine filter menus, are now sorted alphabetically to match the Users page.#622
- Long dropdown menus, such as the user and tag filters on the Machines page, now scroll instead of running off the screen.#621
- DNS records added from the UI now have leading and trailing whitespace trimmed from their name and value, which previously produced records that silently never resolved.#647
- Fixed being signed straight back in a few seconds after logging out when
oidc.disable_api_key_loginis enabled. The logged-out page now stays put across reloads and has a Sign in again button.#642 - Fixed the Authentication Restrictions link disappearing from Settings after a restart while signed in with an existing session.#645
- Fixed copying code, attributes, and machine addresses over plain HTTP, and added feedback when copying fails.#597
- Updated NPM & Go dependencies to fix vulnerabilities.
- Fixed the Native installation documentation as it was outdated and missing steps.#633
Changes
- Rebuilt the Browser SSH module to use Tailscale's
tsconnect, which should result in fewer bugs and better compatibility with future Tailscale releases. - Added
integration.agent.tailscale_netns, an agent-only opt-out from Tailscale's routing-loop socket handling for deployments where its fallback pins the agent's Headscale connection to the wrong interface. Existing behavior remains enabled by default. - Added a Disable/Enable key expiry action to the machine menu. Headscale does not keep the toggle state apart from the expiry date, so re-enabling expiry marks the node expired as of that moment.#554
- Headplane now supports Docker API version 1.24+ (Engine 1.12+, including Podman's Docker-compatible socket).
- Usernames are now validated before a user is created or renamed, so Headplane rejects names that Headscale accepts but ACL policy can never match.#502
Fixes
- Fixed untagged Headscale builds being read as version 0.0.0. The per-commit
main-*anddevelopmentimages report a Go pseudo-version from/version, which is now treated as an unknown version instead of an ancient one.#590 - Fixed the Browser SSH WASM module not building under Nix.#588
- Fixed
server.data_path,headscale.config_path,headscale.dns_records_pathandheadscale.tls_cert_pathbeing silently lowercased, which pointed Headplane at a different location for any path containing a capital letter.#612 - Fixed the Headplane agent falling back to an interactive Tailscale login. The agent now starts with a pre-auth-key, preserves its existing state across restarts, and auto-approves itself when Headscale requires manual approval.#582
- Fixed creating pre-auth keys with an expiry of 1000 days or more. The number input submitted its locale-formatted value (
365,000,365 000,365.000), which either failed with a 500 or silently created a key with a truncated expiry. The raw value is now submitted and the server rejects malformed expiries with a 400.#596
Upgrading
- Headplane needs Headscale 0.27.0 or newer.
- The agent needs Headscale 0.28 and
headscale.api_key, and creates its own pre-auth key. Removeintegration.agent.pre_authkeyandintegration.agent.cache_path. oidc.headscale_api_keyis nowheadscale.api_key. The old option still works, but is deprecated.- Logs are JSON, one object per line. Update anything that parses them.
- Accounts are linked to Headscale users. Single sign-on accounts link by subject or email when they sign in. Accounts that don't match need linking by hand before they can create their own auth keys or use Browser SSH.
Changes
- Switched to structured JSON logging.#279
- Added suggestions to pick existing tags to the machine tag dialog.#560
- Headplane correctly handles
dns.extra_records_pathfrom the Headscale configuration.#543 - Fixed Headscale PostgreSQL config validation so
passis not required whenpassword_fileis supplied.#528 - Fixed Browser SSH's WASM DERP probe to account for custom DERP ports.#552
- Fixed Browser SSH pre-auth key handling by increasing the temporary key expiry window and showing key creation errors in the UI.#565
- Fixed machine rename submission by validating names before sending the rename request.#564
- Fixed OIDC token exchange fallback when retrying with
client_secret_basic.#493 - Added support for proxy authentication via
server.proxy_auth.#353 - Added automatic role assignment for new OIDC users via
oidc.default_roleand IdP-provided role claims viaoidc.role_claim.#352 - Fixed the DNS page crashing when Headscale has no Split DNS nameservers configured.#570
- User lists now show Headscale display names while preserving usernames as secondary text.#571
- Fixed the Register Machine Key dialog so it accepts registration URLs and full
hskey-authreq-...registration keys.#579 - Fixed assigning ACL tags to tag-only (no-user) nodes from the UI. The "Add" and "Remove" tag buttons in the tag dialog lacked
type="button", so clicking them submitted the form before the local state update was applied and Headscale received the unchanged tag list. Tag modifications now reach Headscale as intended.#574
- Headplane now requires Headscale 0.27.0 or newer. Support for 0.26.x has been dropped. If
/versionreturns 404 (the endpoint was added in 0.27.0), Headplane logs an error and keeps retrying so an in-place Headscale upgrade is picked up without a restart. - Replaced the OpenAPI hash detection with
/version. Capabilities are now derived from the version reported by/versioninstead of fingerprinting the OpenAPI schema. This dramatically simplifies version detection and works with every supported release out of the box. - Made Headscale boot resilient. Headplane now boots even when Headscale is unreachable; capabilities default permissively and a background retry settles them once Headscale responds. No more cold-start ordering problems with docker-compose.
- Added optional in-process TLS termination. Setting
server.tls_cert_pathandserver.tls_key_pathmakes Headplane serve HTTPS/1.1 onserver.portdirectly — no reverse proxy required.server.cookie_secureis auto-forced totrue(with a warning) whenever TLS is enabled, since browsers refuseSecure-less cookies over HTTPS. HTTP/2 and HTTP/3 are intentionally not supported in-process; terminate those at a reverse proxy if you need them.#403 - Made the bundled Docker healthcheck zero-config across HTTP and HTTPS. Headplane writes its loopback URL (scheme, port, and basename included) to
/tmp/headplane-listenon startup, andhp_healthcheckreads that file and probes the URL verbatim. Enabling TLS or changingserver.portno longer requires any healthcheck-specific configuration. Native installs are unaffected — the listen file is only written whenHEADPLANE_LISTEN_FILEis set, which the Dockerfile does automatically. - Added Rename and Delete actions for unlinked Headscale users on the Users page so admins can manage Headscale users that have no Headplane account.#525
- Documented Custom Certificate Authorities for trusting private or self-signed CAs across every outbound TLS connection (OIDC, Headscale, Docker, etc.) via Node's
NODE_EXTRA_CA_CERTS. This replaces the previous workaround of rebuilding the Docker image to extend the system trust store.#313 - Fixed user-management actions (link, change role, transfer ownership) using the wrong ID type for unlinked Headplane users. Form fields are now explicitly
headplane_user_idvs.headscale_user_id, and the auth layer no longer round-trips through Headscale to recover the OIDC subject. - Fixed the "Register Machine Key" dialog passing the Headscale numeric user id instead of the username. Headscale's
RegisterNodeRequest.userproto field is astringlooked up viaGetUserByName(no numeric fallback), so registration was failing whenever the selected owner's display name differed from their numeric id.#532 - Fixed pre-auth key expiration on Headscale 0.27.x. The pre-0.28 expire endpoint takes a
uint64 userfield which the API layer reads fromkey.user?.id, but the caller was wrapping the id as{ name: user }, causing the request to send an empty user field. Headplane now correctly passes the numeric Headscale user id. - Fixed dialog panels growing beyond the viewport; dialog content is now constrained and scrollable.#556
- Fixed focus rings on inputs and buttons inside dialogs being clipped by the scrollable content container.
- Fixed tooltips on the last row of the machines table being clipped by the viewport; tooltips now anchor above the trigger with collision padding.#508
- Corrected the Docker healthcheck example in the docs to use the required
CMDprefix so reverse proxies don't see the container as unhealthy.#535
- Fixed GHSA-vgj6-hcf2-fqf6, a path traversal / RBAC bypass in Headscale node and user rename API calls.
- Fixed GHSA-vgj6-hcf2-fqf6, a path traversal / RBAC bypass in Headscale node and user rename API calls.
- Rebuilt the user model to enable "account linking" between Headplane and Headscale. OIDC users are automatically linked to their Headscale counterparts based on subject and email. Users who cannot be automatically linked can claim an unlinked Headscale user during onboarding. See the SSO docs for details.#489
- Rebuilt Browser SSH with a new terminal powered by Ghostty WASM, improved session handling, and support for custom DERP ports. See the Browser SSH docs for details.#515#386
- Rearchitected the Headplane Agent with a periodic sync model and extensive caching. The agent now auto-generates ephemeral pre-auth keys (requires Headscale 0.28+). See the Agent docs for details.#350#455
- Replaced
openid-clientwith a new OIDC implementation. Fixesclient_secret_basicnot working with Google SSO and other providers.#493#516 - Migrated all UI components from react-aria to Base UI.
- Consolidated the Headscale API key under
headscale.api_key(andheadscale.api_key_path). Deprecatedoidc.headscale_api_key— it is still read as a fallback but will be removed in a future release. - Added machine list filters for user, tag, status, and route.#507#506
- Added self-service pre-auth key creation for auditor role users.#478#453
- Added an agent status page at
/settings/agentshowing sync status, node count, and errors. - Added local endpoint and address information to the machine detail page.
- Improved the ACL editor appearance and fixed a CodeMirror version mismatch.
- Store OIDC profile pictures in the database to prevent cookie overflow.#510#326
- Detect unsupported Docker API versions early with a clear error message.#497
- Fixed "No expiry" badge not displaying for nodes with zero-time expiry values.#527#526
- Fixed first user not being assigned the owner role on OIDC login.#480#266
- Fixed login errors throwing a server error instead of showing form validation.#475#474
- Fixed pre-auth key expiration on Headscale 0.28+.#519
- Fixed OIDC subject matching for providers with special characters in user IDs, e.g. Auth0.#428
- Fixed
headscale.api_keynot being used consistently across all code paths. - Fixed agent HostInfo not refreshing periodically using
cache_ttl.#477#427 - Fixed agent working directory being wiped on restart.
- Fixed a race condition where the SSE controller could be used after being closed.
- Fixed cookie secret generation using incorrect byte length.#501
- Fixed OIDC configuration error troubleshooting link.#518#517
- Fixed deprecated Nix package attributes.#521
- Updated NixOS module options: removed deprecated agent fields, added
headscale.api_key_pathandintegration.agent.executable_path.
Upgrading
oidc.redirect_uriis replaced byserver.base_url. Headplane logs the value to use when it finds the old option.- PKCE is opt-in. If the identity provider requires it, set
oidc.use_pkce: true. - Environment variables are always read, so remove
HEADPLANE_LOAD_ENV_OVERRIDES..envfiles are no longer loaded. - Remove
oidc.strict_validation. It has no effect.
Changes
- Added support for Headscale 0.28.0 including all API and data model changes.
- Added search and sortable columns to the machines list page.#351
- Added support for Headscale 0.27.0 and 0.27.1
- Bundle all
node_modulesaside from native ones to reduce bundle and container size.#331 - Allow conditionally compiling the SSH WASM integration when building.#337
- Implemented the ability to customize the build with a custom script (see
./build.sh --helpfor more information). - Attempt to warn against misconfigured cookie settings on the login page.
- Made
server.cookie_max_ageandserver.cookie_domainconfigurable.#348 - Re-worked the configuration loading system with several enhancements:
- Re-worked the OIDC integration to adhere to the correct standards and surface more errors to the user.
- Deprecated
oidc.redirect_uriand automated callback URL detection in favor of settingserver.base_urlcorrectly. - Explicitly added
oidc.use_pkceto correctly determine PKCE configuration. oidc.token_endpoint_auth_methodis now optional and will attempt to be auto-detected, defaulting toclient_secret_basicif unavailable.#410- Added
oidc.enabledconfig option to explicitly control OIDC availability.#463
- Deprecated
- Removed several unnecessarily verbose or spammy log messages.
- Updated the minimum Docker API used to support the latest Docker versions.#370
- Enhanced the node tag dialog to show a dropdown of assignable tags.#362
- Fixed an issue where the website favicon would not load correctly.#323
- Correctly handle invalid ACL policy inserts on Headscale 0.27+.#383
- Prevent a machine from changing its owner to itself.#373
- Added an
/admin/api/inforoute that can expose sensitive information ifserver.info_secretis set in the configuration.#324 - Correctly apply Gravatar profile pictures on the user page if applicable.#405
- Machine key registration no longer works if the key isn't 24 characters long.#415
- Fixed some mobile CSS issues across the application.#401
- Added a Docker healthcheck to the container.#411
- Strengthened the validation for the
/procintegration to correctly discover the Headscale PID. - Added lazy retry logic for OIDC providers if they initially fail to respond.#423
- Fixed API key login on Headscale 0.28.0-beta.1+.#429
- Fixed an issue that prevented the pre-auth-key UI from being usable on Headscale 0.28 and later.
- Added support for creating tag-only pre-auth keys on Headscale 0.28+.#465
- Pre-auth keys are now listed without a user filter on Headscale 0.28+, with a fallback to per-user fetching on older versions.#466
- Fixed handling of tag-only nodes that have no user on Headscale 0.28+.#467
- Adapted to the removal of Node Ownership Change in Headscale 0.28.#436
- Fixed pre-auth keys not showing for OIDC users without a username.#470
- Fixed truncated pre-auth key display with longer Headscale 0.28 bcrypt tokens.#435
- Fixed Nix systemd service to use user-specified package.#454
- Version displayed in the UI is now derived from git tags and build args instead of
package.json, fixing incorrect versions shown on beta and nightly builds. - Improved the no-access user page on the UI.#469
Upgrading
- Headplane keeps a database,
hp_persist.db, inserver.data_path. The directory has to be persistent and writable, such as a Docker volume. Existing users migrate on their own. - The Docker image has no shell. For debugging, use the matching
-shelltag, such asghcr.io/tale/headplane:0.6.1-shell.
Changes
- Headplane now supports connecting to machines via SSH in the web browser.
- This is an experimental feature and requires the
integration.agentsection to be set up in the config file. - This is built on top of a Go binary that runs in WebAssembly, using Xterm.js for the terminal interface.
- This is an experimental feature and requires the
- Begin using a new SQLite database file in
/var/lib/headplane/hp_persist.db.- The database is created automatically if it does not exist.
- It currently stores SSH connection details and HostInfo for the agent.
- User information is automatically migrated from the previous database.
- The docker container now runs in a distroless image.#255
- A debug version of the container that runs as root and has a shell is available as
ghcr.io/tale/headplane:<version>-shell.
- A debug version of the container that runs as root and has a shell is available as
- Removing a Split DNS record will no longer make the split domain unresolvable by clients.#231
- Reintroduce the toggle for overriding local DNS settings in the Headscale config.#236
- Prefer cross-compiling in the Dockerfile to speed up builds while still supporting multiple architectures.
- Add a build attestation to validate SLSA provenance for the Docker image.
- Implement more accurate guessing on the PID with the
/procintegration.#219 - Usernames will now correctly fall back to emails if not provided.#257
- Configuration loading via paths is now supported for sensitive values#283
- Options like
server.cookie_secret_pathcan overrideserver.cookie_secret - Environment variables are interpolatable into these paths
- See the full reference in the docs
- Options like
- The nix overlay build is fixed for the SSH module#282
- Switch our build processes to use TypeScript Go and Rolldown Vite for better build and type-check performance.
- Cookies are now encrypted JWTs, preserving API key secrets (GHSA-wrqq-v7qw-r5w7)
- OIDC profile pictures are now available from Gravatar by setting
oidc.profile_picture_sourcetogravatar.#232 - OIDC now allows passing many custom parameters:
oidc.authorization_endpoint,oidc.token_endpoint, andoidc.userinfo_endpointcan be overridden to support non-standard providers or scenarios without discovery.#117oidc.scopecan be set to specify custom scopes (defaults toopenid email profile).oidc.extra_paramscan be set to pass arbitrary query parameters to the authorization endpoint.#197
Upgrading
- Headplane needs Headscale 0.26.0 or newer.
Changes
-
Headplane 0.6.0 now requires Headscale 0.26.0 or newer.
- Breaking API changes with routes and pre auth keys are now supported.#204
- Older versions of Headscale will not work with Headplane.
-
OIDC authorization restrictions can now be controlled from the settings UI.#102
- The required permission role for this is IT Admin or Admin/Owner and require the Headscale configuration.
- Changes made will modify the
oidc.allowed_{domains,groups,users}fields in the Headscale config file.
-
The Headplane agent is now available as an integration.#65
- The agent runs as an embedded process alongside the Headplane server and reports host information and system metrics.
- Refer to the
integrations.agentsection of the config file for more information and how to enable it.
-
Requests to
/adminwill now be redirected to/admin/to prevent issues with the React Router (works with custom prefixes, closes #173). -
The Login page has been simplified and separately reports errors versus incorrect API keys.#186
-
The machine actions backend has been reworked to better handle errors and provide more information to the user.#185
-
Machine tags now show states when waiting for subnet or exit node approval and when expiry is disabled.
-
Expiry status on the UI was incorrectly showing as never due to changes in the Headscale API.
-
Added validation for machine renaming to prevent invalid submissions.#192
-
Unmanaged (non-OIDC) users cannot have a role assigned to them so the menu option was disabled.
-
Support Docker container discovery through labels.#194
-
AAAA records are now supported on the DNS page.#189
-
Add support for
dns.extra_records_pathin the Headscale config.#144 -
Tighten
procintegration logic by checking for theheadscale servecommand.#195 -
Strip newlines in the OIDC
client_secret_pathfile if provided.#199
- Fix an issue where other preferences to skip onboarding affected every user.
- Filter out empty users from the pre-auth keys page which could possibly cause a crash with unmigrated users.
- OIDC users cannot be renamed, so that functionality has been disabled in the menu options.
- Suppress hydration errors for any fields with a date in it.
- You can now skip the onboarding page if desired.
- Added the UI to change user roles in the dashboard.
- Fixed an issue where integrations would throw instead of loading properly.
- Loading the ACL page no longer spams blank updates to the Headscale database (fixes #151)
- Automatically create
/var/lib/headplanein the Docker container (fixes #166) - OIDC logout with
disable_api_key_loginset to true will not automatically login again (fixes #149)
- Hotfix an issue where assets aren't served under
/adminor the prefix.
Upgrading
- Single sign-on accounts get roles. New accounts start as members with no dashboard access, and the first person to sign in becomes the owner, so sign in right after upgrading to claim the owner account.
Changes
IMPORTANT
PLEASE update to this ASAP if you were using Google OIDC. This is because previously ANY accounts have admin access to your Tailnet if they discover the URL that Headplane is being hosted on. This new change enforces that new logins by default are not given any permissions. You will need to re-login to Headplane to generate an owner account and prevent unauthorized access.
Implemented proper authentication methods for OIDC.
This is a large update and copies the permission system from Tailscale.
Permissions are not automatically derived from OIDC, but they can be configured via the UI.
Additionally, certain roles give certain capabilities, limiting access to parts of the dashboard.
By default, new users will have a member role which forbids access to the UI.
If there are no users, the first user will be given an owner role which cannot be removed.
Changes:
- Switched the internal server to use
honofor better performance. - Fixed an issue that caused dialogs to randomly refocus every 3 seconds.
- Headplane will not send API requests when the tab is not focused.
- Continue loosening the configuration requirements for Headscale (part of an ongoing effort).
- Unknown values in the Headplane config no longer cause a crash.
- Fixed an issue that caused copied commands to have a random space (fixes #161)
- Hotfix an issue that caused Headplane to crash if no agents are available
- Fixed a typo in the Kubernetes documentation
- Handle split and global DNS records not being set in the Headscale config#129
- Stop checking for the
mkey:prefix on machine registration#131 - OIDC auth was not using information from the
user_infoendpoint. - Support the picture of the user who is logged in via OIDC if available.
- Rewrote the Agent implementation to better utilize disk space and perform better (coming soon).
- Loosened checking for the Headscale configuration as it was too strict and required certain optional fields.
- Deleting a node will now correctly redirect back to the nodes page (fixes #137)
- Supports connecting to Headscale via TLS and can accept a certificate file (partially fixes #82)
- Add support for running Headplane through Nix, though currently unsupported#132
- You can now pass in an OIDC client secret through
oidc.client_secret_pathin the config (fixes #126) - Correctly handle differently localized number inputs (fixes #125)
- Hotfixed an issue where the server bundle got reloaded on each request
- Fixed an issue that caused the entire server to crash on start
- Fixed the published semver tags from Docker
- Fixed the Kubernetes integration not reading the config
This release is a major overhaul and contains a significant breaking change. We now use a config file for all settings instead of environment variables. Please see config.example.yaml for the new format.
Upgrading
- Settings moved from environment variables to a config file. Start from config.example.yaml. Settings that used to be shared with Headscale, such as
public_urlandoidc, are now separate.
Changes
- Completely redesigned the UI from the ground up for accessibility and performance.
- Switched to a config-file setup (this introduces breaking changes, see config.example.yaml for the new format).
- If the config is read-only, the options are still visible, just disabled (fixes #48)
- Added support for Headscale 0.25.0 (this drops support for any older versions).
- Fixed issues where renaming, deleting, and changing node owners via users was not possible (fixes #91)
- Operations now have significantly less moving parts and better error handling.
- Updated to
pnpm10 and Node.js 22. - Settings that were previously shared like
public_urloroidcare now separate within Headplane/Headscale. This is a rather large breaking change but fixes cases where a user may choose to utilize Headscale OIDC for Tailscale but not for the Headplane UI. - Deprecate the
latesttag in Docker for explicit versioning andedgefor nightly builds.
- Fixed an urgent issue where the OIDC redirect URI would mismatch.
- Switched from Remix.run to React-Router
- Fixed an issue where some config fields were marked as required even if they weren't (fixes #66)
- Fixed an issue where the toasts would be obscured by the footer (fixes #68)
- The footer now blurs your Headscale URL as a privacy measure
- Updated to the next stable beta of the React Compiler
- Changed
/healthzto use a well-known endpoint instead of trying an invalid API key - Support
OIDC_REDIRECT_URIto force a specific redirect URI - Redo the OIDC integration for better error handling and configuration
- Gracefully handle when Headscale is unreachable instead of crashing the dashboard
- Reusable Pre-Auth Keys no longer show expired when used (PR #88)
- Tweaked some CSS issues in the UI
- Fixed a race condition bug in the OIDC validation code
- Allow customizing the OIDC token endpoint auth method via
OIDC_CLIENT_SECRET_METHOD(fixes #57) - Added a
/healthzendpoint for Kubernetes and other health checks (fixes #59) - Allow
HEADSCALE_PUBLIC_URLto be set ifHEADSCALE_URLpoints to a different internal address (fixes #60) - Fixed an issue where the copy machine registration command had a typo.
- Quickfix a bug where environment variables are ignored on the server.
- Remove a nagging error about missing cookie since that happens when signed out.
- Clicking on the machine name in the users page now takes you to the machine overview page.
- Completely rebuilt the production server to work better outside of Docker and be lighter. More specifically, we've switched from the
@remix-run/servepackage to our own custom built server. - Fixed a bunch of silly issues introduced by me not typechecking the codebase.
- Improve documentation and support when running Headplane outside of Docker.
- Removing Split DNS records will no longer result in an error (fixes #40)
- Removing the last ACL tag on a machine no longer results in an error (fixes #41)
- Added full support for Exit Nodes in the UI and redesigned the machines page (fixes #36)
- Added a basic check to see if the API keys passed via cookies are invalid.
- Added the ability to load a
.envfile from the PWD whenLOAD_ENV_FILE=trueis set as an environment variable. - Fixed an issue where non-English languages could not create Pre-auth keys due to a localization error
- Improved ACL editor performance by switching back to CodeMirror 6
- Fixed an issue where editing the ACL policy would cause it to revert on the UI (fixes #34)
- Updated to the next stable beta of the React 19 Compiler (See More)
- Implement the ability to create and expire pre-auth keys (fixes #22)
- Fix machine registration not working as expected (fixes #27)
- Removed more references to usernames in MagicDNS hostnames (fixes #35)
- Handle
nullvalues on machine expiry when using a database like PostgreSQL. - Use
X-Forwarded-ProtoandHostheaders for building the OIDC callback URL.
- Fixed the Docker integration to properly support custom socket paths. This regressed at some point previously.
- Allow you to register a machine using machine keys (
nodekey:...) on the machines page. - Added the option for debug logs with the
DEBUG=trueenvironment variable.
- Bumped the minimum supported version of Headscale to 0.23.
- Updated the UI to respect
dns.use_username_in_magic_dns.
- Removed ACL management from the integration since Headscale 0.23-beta2 now supports it natively.
- Removed the
ACL_FILEenvironment variable since it's no longer needed. - Introduce a
COOKIE_SECURE=falseenvironment variable to disable HTTPS requirements for cookies. - Fixed a bug where removing Split DNS configurations would crash the UI.
- Added a proper Kubernetes integration which utilizes
shareProcessNamespacefor PIDs. - Added a new logger utility that shows categories, levels, and timestamps.
- Reimplemented the integration system to be more resilient and log more information.
- Fixed an issue where the /proc integration found
undefinedPIDs.
- Added the ability to manage custom DNS records on your Tailnet.
- ACL tags for machines are now able to be changed via the machine menu.
- Fixed a bug where the ACL editor did not show the diffs correctly.
- Fixed an issue that stopped the "Discard changes" button in the ACL editor from working.
- Fix the dropdown options for machines not working on the machines page.
- Add an option to change the machine owner in the dropdown (aside from the users page).
- Switch to Monaco editor with proper HuJSON and YAML syntax highlighting.
- Utilize magic DNS hostnames for the machine overview page.
- Fixed the expiry issue once and for all.
- Add a nightly build with the
ghcr.io/tale/headplane:edgetag
- Built basic functionality for the machine overview page (by machine ID).
- Possibly fixed an issue where expiry disabled machines' timestamps weren't handled correctly.
- Prevent users from being deleted if they still have ownership of machines.
- Fixed some type issues where
Datewas being used instead ofstringfor timestamps.
- Added support for the
HEADSCALE_INTEGRATIONvariable to allow for advanced integration without Docker. - Fixed a bug where the
expiryfield on the Headscale configuration could cause crashes. - Made the strict configuration loader more lenient to allow for more flexibility.
- Added
HEADSCALE_CONFIG_UNSTRICT=true to revert back to a weaker configuration loader. - Headplane's context now only loads once at start instead of being lazy-loaded.
- Improved logging and error propagation so that it's easier to debug issues.
- Added experimental support for advanced integration without Docker.
- Fixed a crash where the Docker integration tried to use
process.env.API_KEYinstead of context. - Fixed a crash where
ROOT_API_KEYwas not respected in the OIDC flow.
- Robust configuration handling with fallbacks based on the headscale source.
- Support for
client_secret_pathon configuration file based OIDC. DISABLE_API_KEY_LOGINnow works as expected (non 'true' values work).API_KEYis renamed toROOT_API_KEYfor better clarity (old variable still works).- Fixed button responders not actually being invoked (should fix the ACL page).
- Users can now be created, renamed, and deleted on the users page.
- Machines can be dragged between users to change their ownership.
- The login page actually respects the
DISABLE_API_KEY_LOGINvariable. - Implemented some fixes that should stop dialogs from hanging a webpage.
- Upgrade to React 19 beta to take advantage of the compiler (may revert if it causes issues).
- Upgrade other dependencies
- Switched to a better icon set for the UI.
- Support stable scrollbar gutter if supported by the browser.
- Cleaned up the header which fixed a bug that could crash the entire application on fetch errors.
- Added support for renaming, expiring, removing, and managing the routes of a machine.
- Implemented an expiry check for machines which now reflect on the machine table.
- Fixed an issue where
HEADSCALE_CONTAINERwas needed to start even without the Docker integration. - Removed the requirement for the root
API_KEYunless OIDC was being used for authentication. - Switched to React Aria for better accessibility support.
- Cleaned up various different UI inconsistencies and copied components that could've been abstracted.
- Added a changelog for any new versions going forward.