Installation

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 run make release (produces bin/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: none and server.behind_tls_proxy: true, and put your reverse proxy / load balancer in front. Use mode: self for 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_SERVICE so it can bind :443/:80 without root;
  • uses KillMode=control-group so 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-admin with the new build and systemctl 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.