Repository navigation
Releases: insanerask77/headscale-easy
Release list
v2.0.2
Fixed
- Docker tab: the exit node of a container survives the first deploy (#83). It is generated as
TS_ROUTES=0.0.0.0/0,::/0(plus any subnet routes) instead of--advertise-exit-nodeinTS_EXTRA_ARGS.
WithTS_AUTH_ONCE=truethe image skipstailscale up, the only placeTS_EXTRA_ARGSapplies, so ticking
the exit node on a container that had already signed in was ignored. - Docker tab: a container on a host whose kernel has only nftables no longer registers as an exit node that
forwards nothing (#84). The snippets setTS_DEBUG_FIREWALL_MODE=auto; the image defaults to an iptables
backend that such a kernel lacks.
Added
- Docker tab: a yellow "Troubleshooting and tips" dropdown with eleven entries (no connection, spent key,
no Internet, an exit node that does not route or does not appear, options that do not change, subnets that
do not show, IP forwarding, missing permissions, DNS, relays), in every language, with commands that use the
container's name. The same text is in the docs, under "Docker devices". - Docker tab: the command that applies the form's options to a container that already runs (#87), including
withdrawing a route or an exit node. - Docker tab: the auth key goes in a separate
.envblock, not indocker-compose.yml(#88). - Docker tab: the image is pinned to a tested Tailscale version;
latestis an explicit choice (#89). - Docker tab: options for a container that uses another device as exit node or accepts subnet routes (#86).
- Machines: an exit node or subnet routes waiting for approval say so in the badge, can be approved in one
click, and administrators see a banner counting them (#85).
Changed
- CI runs the Docker tab end-to-end smoke test (#90): the snippets the tab generates are deployed and checked.
ApipefailSIGPIPE made itsnocapcase flaky; the container logs are now read fully before grepping.
v2.0.1
Fixed
- Docker tab of Add device: an administrator can generate the single-use auth key the first time. The owner
picker used to appear only after an error, so the first attempt failed with "There is no Headscale user to
own the key".
Added
- Docker tab: with an exit node or routes in kernel networking, a note lists what to try when they do not
route, from Tailscale's exit node guide: approve the node, enable IPv4 and IPv6 forwarding on the host, or use
userspace networking.
v2.0.0
Headscale Easy 2.0 is one container: Headscale, Caddy and the console, supervised together, installed
with a Docker Compose file. The image is about 232 MB and uses about 72 MB of RAM at rest.
End of 1.x
- Headscale Easy 1.x is discontinued as of this release: 1.5.0 is the last 1.x version and it receives no
more fixes, security fixes included. There is no in-place upgrade and no conversion tool: install 2.0 as a
new deployment. See 1.x is discontinued.
Install
- A single
compose.yamland an optional.env:docker compose up -d, then open the setup wizard.
The image is pinned to the release (HSE_VERSION); no Docker socket, no added capability, no root. - The first-run wizard asks the language, the public URL, HTTPS (Let's Encrypt, internal CA or a proxy in
front), the administrator (with two-factor authentication), the tailnet name and its MagicDNS domain,
the DERP relay, who may sign up, and the backups. Every answer can also be given asHSE_*variables for
a headless start. - Headscale runs unmodified, pinned to a tested version.
Sign-in and accounts
- Local accounts: password (scrypt), TOTP with recovery codes, invitations, password-reset links, roles
(administrator, network administrator, auditor, member), revocable sessions and sign-in rate limiting.
Invitation and reset links are shown once and can be sent by e-mail withSMTP_*. - Sign-up from the sign-in page: off, by invitation key, or open.
- Device sign-in without an identity provider: the link
tailscale upprints opens the console, which asks
you to sign in and approve the device. - An external OIDC provider is optional: see Advanced configurations.
Network
- Embedded DERP/STUN relay by default (no third-party relay needed), a custom DERP map, or Tailscale's
public relays. - Network isolation per user, an ACL policy editor, DNS and MagicDNS editor, device key expiry, live
device status, a Docker tab in Add device, an activity log, webhook notifications, a status page. - English, Spanish, French, German and Portuguese.
Backups
- A nightly backup (consistent SQLite copies, keys, configuration, Caddy's CA) kept for 14 days,
hse backupandhse restore, a Backups page with upload, download and restore, and an optional sidecar
that uploads each backup to S3, B2, SFTP or a server.
Advanced configurations
- Compose overlays under
advanced/: external PostgreSQL (with a read-only role for the console, and an
optional bundled server), a proxy in front (nginx, Traefik, Caddy, Nginx Proxy Manager), remote backups,
and OIDC providers (Authentik, Pocket ID, Keycloak, Google). HSE_TRUSTED_PROXIESgives the console and Headscale the real client address behind a proxy;
HSE_OIDC_ALLOWED_*andPORTAL_*_GROUPSsay who may sign in and who is what.
Releases
- Images are published as
ghcr.io/insanerask77/headscale-easyandheadscale-easy-backupwith the tags
X.Y.Z,X.Y,Xandlatest.VERSIONis the single source of the version; CI checks that the
compose file, the image and this changelog agree.
v2.0.0-rc.1
Headscale Easy 2.0 is one container: Headscale, Caddy and the console, supervised together, installed
with a Docker Compose file. The image is about 232 MB and uses about 72 MB of RAM at rest.
Install
- A single
compose.yamland an optional.env:docker compose up -d, then open the setup wizard.
The image is pinned to the release (HSE_VERSION); no Docker socket, no added capability, no root. - The first-run wizard asks the language, the public URL, HTTPS (Let's Encrypt, internal CA or a proxy in
front), the administrator (with two-factor authentication), the tailnet name and its MagicDNS domain,
the DERP relay, who may sign up, and the backups. Every answer can also be given asHSE_*variables for
a headless start. - Headscale runs unmodified, pinned to a tested version.
Sign-in and accounts
- Local accounts: password (scrypt), TOTP with recovery codes, invitations, password-reset links, roles
(administrator, network administrator, auditor, member), revocable sessions and sign-in rate limiting.
Invitation and reset links are shown once and can be sent by e-mail withSMTP_*. - Sign-up from the sign-in page: off, by invitation key, or open.
- Device sign-in without an identity provider: the link
tailscale upprints opens the console, which asks
you to sign in and approve the device. - An external OIDC provider is optional: see Advanced configurations.
Network
- Embedded DERP/STUN relay by default (no third-party relay needed), a custom DERP map, or Tailscale's
public relays. - Network isolation per user, an ACL policy editor, DNS and MagicDNS editor, device key expiry, live
device status, a Docker tab in Add device, an activity log, webhook notifications, a status page. - English, Spanish, French, German and Portuguese.
Backups
- A nightly backup (consistent SQLite copies, keys, configuration, Caddy's CA) kept for 14 days,
hse backupandhse restore, a Backups page with upload, download and restore, and an optional sidecar
that uploads each backup to S3, B2, SFTP or a server.
Advanced configurations
- Compose overlays under
advanced/: external PostgreSQL (with a read-only role for the console, and an
optional bundled server), a proxy in front (nginx, Traefik, Caddy, Nginx Proxy Manager), remote backups,
and OIDC providers (Authentik, Pocket ID, Keycloak, Google). HSE_TRUSTED_PROXIESgives the console and Headscale the real client address behind a proxy;
HSE_OIDC_ALLOWED_*andPORTAL_*_GROUPSsay who may sign in and who is what.
Releases
- Images are published as
ghcr.io/insanerask77/headscale-easyandheadscale-easy-backupwith the tags
X.Y.Z,X.Y,Xandlatest.VERSIONis the single source of the version; CI checks that the
compose file, the image and this changelog agree.
v1.5.0
Upgrade: run ./install.sh again to pick up the new settings and the hs-helper service.
An older docker-compose.yml that still mounts the Docker socket in web keeps working,
with a warning in the logs. Existing installs keep their ACL policy: see Fixed.
Security
- Sessions are now revocable: the signed cookie carries a session id that must
exist, unrevoked, indata/web/sessions.db(600). New Settings →
Sessions page: your sessions (admins and auditors: everybody's) with
Log out, Sign out everywhere and, for admins, Sign out everyone else.
Deleting a user, or a role change seen at their next sign-in, revokes their
older sessions. Cookies issued before this change have no session id: users
sign in again once. - Sign-in rate limiting per client IP (
SIGNIN_RATE_LIMIT, default 10, per
SIGNIN_RATE_WINDOW, default 600 seconds) on the API key sign-in and the
OIDC sign-in:429withRetry-Afterand anauth.rate_limitedactivity
event. New activity events:auth.session_revoked,
auth.sessions_revoked_all. - The web UI no longer mounts the Docker socket (equivalent to root on the
host) and is no longer in the Docker group. A newhs-helperservice
(helper/, imageghcr.io/insanerask77/headscale-easy-helper) is the only
container with the socket. It has no network and answers the web UI over a
660Unix socket in thehse-helpervolume with exactly three fixed
operations on theheadscalecontainer:POST /configtest,
POST /restartandGET /status(container health and Headscale's
version). It takes no parameters; anything else is refused before Docker is
contacted. Installations with an olderdocker-compose.ymlthat still
mounts the socket inwebkeep working, with a warning in the logs.
make validatefails if any other service mounts the socket.
Added
- The console is now available in French, German and Portuguese (Brazilian),
next to English and Spanish: full catalogs inweb/locales/{fr,de,pt}.json
and{fr,de,pt}.d/, offered in the language selector in Settings.UI_LANG
acceptsfr,deandpt; the installer lets you choose them and keeps its
own messages in English for those languages. - PostgreSQL support for Headscale's database (roadmap item 17). The installer
asks: SQLite (default, recommended), PostgreSQL in the stack (new
headscale-postgresqlcontainer,postgresCompose profile, volume
headscale-db) or your own PostgreSQL server (host, port, database, owner,
password, TLS mode). NewHEADSCALE_DB_TYPEandHEADSCALE_PG_*settings
in.env; switching type warns that data is not migrated. web/pgwire.py: a minimal read-only PostgreSQL client written with the
standard library only (protocol v3, SCRAM-SHA-256 with server signature
check, cleartext password, optional TLS with libpq'ssslmodevalues,
simple query; the legacy MD5 method is refused). The web image still has no
third-party dependencies.- The web UI reads device Hostinfo (OS, Tailscale version, DERP, endpoints)
from PostgreSQL through its own read-only role (HEADSCALE_PG_RO_USER,
created bytemplates/headscale-pg-readonly.sql):SELECTon three
columns ofnodesonly, read-only sessions; it never gets Headscale's
credentials. - Backups and restore support PostgreSQL:
pg_dumpof Headscale's database
(headscale/headscale.sqlin the archive) andmake restoreloads it back
and re-creates the read-only role. tests/test_postgresql.py: the client against a fake server (recorded
protocol messages, a real SCRAM server side, MD5 refused, errors, TLS refusal),
RFC 7677 SCRAM and RFC 4013 SASLprep vectors, Hostinfo from PostgreSQL on
the machine page.- Server status page (Settings → Status, admins and auditors): Headscale
and Headscale Easy versions with an update notice (GitHub releases, cached
12 h;STATUS_UPDATE_CHECK=falseturns it off), container health from
hs-helper, disk use of/dataand/headscale, devices online and basic
Headscale metrics. Each source fails independently. - Remote backups: set
BACKUP_REMOTEand every backup is also uploaded to S3,
B2, SFTP... (any rclone remote) or to a server with rsync over SSH
(rsync:user@host:/dir). Remote retention withBACKUP_REMOTE_KEEP_DAYS;
credentials indata/backup-remote/. The installer asks for it, and
make restore file=s3:bucket/dir/headscale-easy-....tar.gzdownloads the
backup first. A failed upload is reported without losing the local backup. tests/test_backup_remote.py: testsbackup/remote.shagainst a local
destination with fake rclone/rsync.- DERP relay status: the machine detail shows the preferred relay with its
latency and the latency to every relay the device measured; the Machines
list has a Relay column. - New DERP relays page (Network): which relays the devices use and their
median latency, and an editor (admins) for your own DERP map. It writes
headscale-derp.yamland pointsderp.pathsofconfig.yamlat it (a
marked block, like DNS), validates it withheadscale configtest, restarts
Headscale and restores the previous map if anything fails. Run
./install.shonce on existing installs to add the block and the file. - Webhook notifications (roadmap item 14): Slack, Telegram, ntfy and a generic
JSON webhook, for new, expired, about-to-expire and removed devices.
Configured withNOTIFY_URLS/NOTIFY_EVENTS(the installer asks,
optionally); sending is in the background with a timeout and retries, and
never slows down or breaks the web UI. Settings -> General ->
Notifications lists the destinations and has a "Send a test" button
(admins; blocked in the demo).
Changed
- Machines that register as
localhostare now renamed within about 5 seconds
(was 30). Each pass is one node-list call; host details are only fetched when
something needs renaming. Tune it withRENAME_INTERVAL(seconds, minimum 1).
Fixed
- Exit nodes had no internet with
NETWORK_ISOLATION=true: the policy the installer applies only
allowedautogroup:self, so an exit node accepted connections but forwarded nothing. It now also
allowsautogroup:member->autogroup:internet:*. Existing installs keep their policy; add the rule
in Access controls (see the configuration docs).
v1.4.0
Security
- An email in
PORTAL_ADMIN_EMAILSno longer grants admin when the OIDC
provider says it is not verified (email_verified: false). Before, with a
provider that lets users set an unverified email, anyone could claim an
admin's address. - The activity log database (
data/web/audit.db, with emails and client IPs)
is now readable only by the web UI's user (600); it was world-readable. - New
DEMO_MODE=truefor public demo instances: a "DEMO ENVIRONMENT" banner
on every page and the actions that grant access or change things for
everyone (auth keys, API keys, registering devices, invitations, reset
links, users, DNS, the ACL policy (visual editor and Advanced), two-factor,
key expiry, removing or expiring machines, one by one or in bulk) are
refused.
Added
tests/test_security.py: permission-boundary tests run in CI (forged and
expired sessions, CSRF, admin-only pages and actions, members limited to
their own machines and keys, path traversal, redirects, OIDC
admin-by-email, demo mode).- Documentation: security notice in the README and docs;
SECURITY.mdwith
what is exposed, review and test status, known limitations and how to
report;AI_USAGE.md; new pages Why Headscale Easy? (the problem it
solves, comparison with Headplane, end-to-end workflow), Production and
hardening and Architecture and resources (with measured RAM, CPU and disk
use); a 5-minute quick start; more troubleshooting (OIDC, HTTPS, front
proxies, device registration, DNS, ACL). - Contributing: pull requests say whether they were AI-assisted, and changes to
sign-in, sessions, permissions or the Docker socket need a security test.
Changed
- The project is described as what it is — a deployment and management layer
around the official Headscale — rather than "the open source Tailscale
alternative".
v1.3.1
Fixed
- SSO login behind proxies that block Python's default user agent. The
console now identifies its outbound OIDC requests (discovery, token
exchange, userinfo) asheadscale-easy/<version>. Some identity-provider
proxies rejectPython-urllib: Pocket ID behind Cloudflare answered
discovery with HTTP 403 / Error 1010, which broke sign-in. Thanks to
@vhryniv for the report and the fix (#21).
Changed
- The README now lists the project's contributors.
v1.3.0
Closes out the roadmap's Medium-priority section (items 10-13).
Added
- Auto-approval of routes and exit nodes. A 5th "Auto-approval" tab in
Access controls exposes Headscale'sautoApproverspolicy section:
declare which tag, group or user gets a subnet route (or the exit node
role) approved automatically, instead of approving each device by hand.
Verified live: a tagged test device's advertised route and exit-node role
were both approved with zero manual steps. - Tailscale SSH rules. A 6th "SSH rules" tab: who can SSH into which
machines, as which host users, with optional periodic re-authentication —
no SSH keys to manage. Tailscale SSH itself still needs turning on per
device (tailscale up --ssh). - Bulk actions on machines. Tick several rows in the Machines table (or
the header checkbox for all of them) to expire keys, add a tag or remove
them all at once, instead of one at a time. - Network admin and Auditor roles, both opt-in and built on Authentik
groups (PORTAL_NETWORK_ADMIN_GROUPS,PORTAL_AUDITOR_GROUPS): a network
admin edits the ACL policy and DNS only; an auditor sees everything an
admin sees — every machine, Users, DNS, Access controls, Logs — but can
never change anything, anywhere. No Authentik blueprint changes needed:
the existingprofileOIDC scope already sends every group a user belongs
to.
v1.2.0
Added
- Visual ACL policy editor. Access controls now has four tabs: Rules
(who can reach what, with source/destination/port/protocol forms),
Groups & tags (reusable groups and tag owners), Test access (a
simulator: pick a source and a destination and it says whether the policy
allows it and which rule matched — not a live packet test), and Advanced
(HuJSON), the original text editor kept as a full fallback. Under the
hood, visual edits rewrite only theacls,groupsortagOwnersblock
they touch and leave the rest of the file — comments, key order, hand-written
sshorautoApproverssections — untouched. Verified against a live
Headscale instance: real device connectivity (per-user isolation and a
tag-based rule) matched the simulator's predictions in every case tested. - Optional public DERP servers. The installer now asks whether to also use
Tailscale's public DERP relays (default: yes); answering no gives a fully
self-hosted install with only the embedded relay (DERP_USE_PUBLICin
.env).uninstall.shgains a--lang en|esoption.
v1.1.0
Built by five agents working in parallel, one feature each, then integrated
and tested together.
Added
- Invitations and password reset. Admins invite people from Users →
Invite user (member or admin, optional email, 1–30 days, QR code for the
link); the person chooses their own user name and password. Pending
invitations can be copied again or revoked. "Password reset link…" makes a
single-use link for any account, no email needed. Optional SMTP (asked by the
installer) adds "Forgot password?" on the sign-in page and lets invitations
and reset links be emailed. - Expiry warnings and inactive machines. An orange "Expires soon" badge
(14 days,EXPIRY_WARNING_DAYS), a notice at the top of Machines with a link
that filters them, "Expiring soon" and "Offline for 30+ days" filters
(INACTIVE_DAYS), and bulk removal of inactive machines (admins, with
confirmation; each machine is checked again before removal). - Activity log (Logs page, admins): configuration changes made in the web
UI (machines, users, keys, ACL with a diff, DNS before/after, key expiry,
two-factor, invitations, password resets), console sign-ins and failed
sign-ins with the client IP, and device events (registered, removed,
connected, disconnected, key expired, Tailscale version changed, renamed
outside the console). Search, filters, CSV export, live updates. Kept
AUDIT_RETENTION_DAYS(90) in./data/web/audit.db, included in backups.
Secrets are never stored. - QR codes in Add device (server URL on the iOS and Android tabs, whose
steps now follow Tailscale's current custom-server flow) and for new auth
keys, generated by the web UI itself (no JavaScript or third-party code). - DNS page like Tailscale's: the implicit MagicDNS nameserver
(100.100.100.100), split DNS and global nameservers as rows with add/remove,
"Use local DNS settings", the tailnet domain as the fixed first search
domain, custom records as rows, and confirmation before renaming the tailnet
or disabling MagicDNS. Works without JavaScript. - Unit tests (
make test, run in CI).
Fixed
- The Spanish strings of custom DNS records were lost in 1.0.7.
Also in this release
Versions 1.0.4 to 1.0.7 were never released separately; upgrading from 1.0.3 also brings:
1.0.7
Added
- Custom DNS records in the DNS page (
name address, A or AAAA), resolved by
every device of the tailnet (Headscale'sdns.extra_records).
1.0.6
Added
- Optional daily backups: the installer asks (off by default, recommended) and
lets you choose the time, folder and retention. Abackupcontainer
(Compose profilebackup) backs up Headscale's database and private keys,
Authentik's database, the configuration and Caddy's internal CA. The
installer makes a first backup right away and shows the exact restore
command. make restore file=...(scripts/restore.sh): restores all of it, also on a
new server.make backupmakes a one-off backup, with or without the
schedule.
Fixed
make backupcopied Headscale's SQLite files while Headscale was writing,
which could produce an inconsistent database. It now uses SQLite's online
backup and checks the copy's integrity.
1.0.5
Added
- Two-factor authentication with the built-in Authentik: an authenticator app
(TOTP) or a passkey after the password. Required for admins by default;
MFA_REQUIRED(asked by the installer) can make it required for everyone or
optional. Users who already have a second factor are always asked for it. - Admins change the two-factor mode live from the web UI (Settings → General
→ Two-factor authentication), without reinstalling. The web UI uses an
Authentik API token (PORTAL_AUTHENTIK_TOKEN, generated by the installer)
of a service account that may only read and change the two-factor policy;
restarting Authentik keeps the admin's choice, andMFA_REQUIREDis the
initial value (re-running the installer applies the value chosen there).
Without the built-in Authentik the section is hidden; without the token it
is read-only.
1.0.4
Fixed
- The web UI stopped working 90 days after installing, when its Headscale API
key expired. It now renews the key by itself 15 days before (keeping the new
one indata/web/api-keyand expiring the old one); the installer reuses the
renewed key. If no valid key is left, the web UI says how to fix it instead
of showing a generic error. - API key prefixes containing "-" were misread, which could hide the "Used by
Headscale Easy" mark on the web UI's own key.
Upgrading
git pull && ./install.sh — required once for the new Authentik flows (invitations, password reset, two-factor) and to choose optional backups and email.