Manual
Install the binary, write a config, and run headscale-admin under systemd. No containers, no Ansible.
This is the bare-metal path: drop the binary on a host, write two files (config + secrets), and run it as a hardened systemd service. headscale-admin downloads and supervises headscale itself, so there is nothing else to install.
The layout used below matches the example systemd unit that ships in the repo:
| Path | Purpose |
|---|---|
/opt/hsa/headscale-admin |
the binary |
/opt/hsa/config.yaml |
non-secret config |
/opt/hsa/headscale-data/ |
headscale.work_dir - headscale data |
/etc/headscale-admin/env |
HSA_* secrets (root:hsa, 0640) |
1. Create a user and directories
sudo useradd --system --home-dir /opt/hsa --create-home --shell /usr/sbin/nologin hsa
sudo install -d -o hsa -g hsa /opt/hsa/headscale-data
sudo install -d -o root -g hsa -m 0750 /etc/headscale-admin
2. Install the binary
Download the build for your OS / architecture (published builds: linux / darwin × amd64 / arm64):
# pick your arch: linux-amd64 or linux-arm64
curl -fsSL -o /tmp/headscale-admin \
https://github.com/YouSysAdmin/headscale-admin/releases/latest/download/headscale-admin-linux-amd64
sudo install -o root -g hsa -m 0755 /tmp/headscale-admin /opt/hsa/headscale-admin
/opt/hsa/headscale-admin version
Releases also publish a
checksums.txt- verify the download against it before installing. If you’d rather build from source, clone the repo and runmake release(producesbin/headscale-admin-linux-amd64, etc.).
3. Write the config
Start from the fully-commented reference config and trim it down. A minimal production config with built-in Let’s Encrypt TLS and local password login:
# /opt/hsa/config.yaml
server:
addr: :443 # the single public listener
db_path: /opt/hsa/admin.db # headscale-admin's own store (audit log, principals)
tls:
mode: acme # Let's Encrypt via HTTP-01 (needs :80 + :443 public)
acme:
email: [email protected]
hosts: [hs.example.com]
http_addr: :80
cache_dir: /opt/hsa/certs
auth:
disabled: false
# session_secret comes from the env file (see step 4)
session_ttl: 12h
oidc:
enabled: false # flip to true + fill issuer/client_id to use SSO instead
logging:
level: info
format: json
output: stdout
headscale:
work_dir: /opt/hsa/headscale-data
# Client-facing control URL - MUST match how clients reach this host.
public_url: https://hs.example.com
Set ownership so the service user can read it:
sudo chown root:hsa /opt/hsa/config.yaml && sudo chmod 0640 /opt/hsa/config.yaml
Not terminating TLS here? Set
server.tls.mode: noneandserver.behind_tls_proxy: true, and put your reverse proxy / load balancer in front. Usemode: selffor an internal/IP-only deployment.
4. Write the secrets (env file)
Keep secrets out of config.yaml - supply them as HSA_* env vars, which override the file:
sudo tee /etc/headscale-admin/env >/dev/null <<EOF
HSA_AUTH_SESSION_SECRET=$(openssl rand -hex 32)
# If using OIDC:
# HSA_AUTH_OIDC_CLIENT_SECRET=...
EOF
sudo chown root:hsa /etc/headscale-admin/env && sudo chmod 0640 /etc/headscale-admin/env
5. Install the systemd unit
Copy the example unit
to /etc/systemd/system/headscale-admin.service. It already:
- runs as
hsa,ExecStart=/opt/hsa/headscale-admin serve --config /opt/hsa/config.yaml; - loads
/etc/headscale-admin/env; - grants
CAP_NET_BIND_SERVICEso it can bind:443/:80without root; - uses
KillMode=control-groupso the supervised headscale stops with it; - applies a strict sandbox (
ProtectSystem=strict,ReadWritePaths=/opt/hsa, …).
sudo curl -fsSL -o /etc/systemd/system/headscale-admin.service \
https://raw.githubusercontent.com/YouSysAdmin/headscale-admin/master/examples/headscale-admin.service
sudo systemctl daemon-reload
sudo systemctl enable --now headscale-admin
The first start needs outbound internet - watch it download and verify headscale, then come up:
journalctl -u headscale-admin -f
6. Create the first admin
With auth enabled, the server won’t fully serve until an admin exists. Create one (run as the service user so it writes
to the right db_path):
sudo -u hsa /opt/hsa/headscale-admin admin create --config /opt/hsa/config.yaml
It prompts for an email + password (or email-only when OIDC is enabled). Then open
https://hs.example.com/admin and sign in.
Upgrading
- headscale-admin - replace
/opt/hsa/headscale-adminwith the new build andsystemctl restart headscale-admin. - headscale - don’t do it by hand. The supervised headscale version is pinned to what headscale-admin was built against (gRPC proto coupling); upgrade it from the Settings page, which downloads + checksum-verifies the new release, restarts headscale, and auto-reverts on failure.
Backups
Everything stateful is two things: headscale.work_dir (/opt/hsa/headscale-data - headscale’s DB, keys, config) and
server.db_path (/opt/hsa/admin.db - the audit log and console principals). Snapshot both, or use the Backup
page in the UI for consistent exports.