Section

Installation

Stand up headscale-admin - by hand, with Docker, Docker Compose, or Ansible. The same handful of decisions (URL, TLS, auth) apply to every method.

headscale-admin is a single binary that supervises headscale itself. There is no separate headscale to install: on first start it downloads the pinned, checksum-verified headscale release, generates and owns headscale.yaml + the sqlite DB / keys under work_dir, and is the only public listener in front of it.

Pick the method that fits you - they all configure the same binary:

  • By hand - download the binary, write a config, run it under systemd.
  • Docker - run the container directly with docker run.
  • Docker Compose - a self-contained service with a persistent volume.
  • Ansible - the bundled role: binary + config + systemd + optional headscale pre-config.

Prerequisites

  • A Linux host (amd64 or arm64), or any OS that can run the binary - published builds cover linux and darwin × amd64 / arm64.
  • Outbound internet on first start - headscale-admin fetches the pinned headscale release from GitHub and verifies it against the release checksums, then caches it. For air-gapped hosts you can point headscale.binary at a binary you supply.
  • A public address your Tailscale clients can reach (headscale.public_url). headscale’s control plane and the admin UI are served from this one address.
  • A way to terminate TLS. Real Tailscale clients need HTTPS - headscale-admin can do it in-process (server.tls.mode: self | manual | acme) or you can front it with a TLS-terminating proxy (mode: none + server.behind_tls_proxy: true).

The decisions that matter

Every install boils down to the same few settings (the fully-commented reference config documents them all):

Setting What it controls
headscale.public_url The client-facing control-server URL. Must match how clients actually connect (scheme + host + any non-default port). It becomes headscale’s server_url and the connect command in the UI.
server.addr The single public listen address (default :3000; use :443 for a real deploy).
server.tls.mode none (behind a proxy / local test), self (self-signed, IP-only/internal), manual (your cert + key), or acme (Let’s Encrypt, needs ports 80 + 443 reachable).
auth disabled (everyone is admin - dev only), local email + password, or OIDC SSO. With auth on you must set auth.session_secret.
headscale.work_dir Where headscale’s config, sqlite DB, keys, socket, and the downloaded binary live. This is the directory to persist and back up.

Configuration & secrets

Config is a YAML file (--config <path>, default ./headscale-admin.yaml). Any setting can also be supplied as an environment variable: prefix HSA_, uppercase the dotted path, and replace dots with underscores - so server.addrHSA_SERVER_ADDR and auth.oidc.client_idHSA_AUTH_OIDC_CLIENT_ID. Env wins over the file, which is the tidy way to inject secrets (the session secret, the OIDC client secret, the headscale-pf token) without writing them to disk.

Generate the session secret (required when auth is enabled, ≥ 32 chars) with:

openssl rand -hex 32

First-run admin bootstrap

When authentication is enabled, headscale-admin refuses to start until at least one admin principal exists - so nobody is ever locked out of an open console. Create the first admin out-of-band:

headscale-admin admin create --config /path/to/config.yaml
  • With OIDC disabled, it prompts for an email and password (8–72 characters).
  • With OIDC enabled, it prompts for an email only - the matching same-email OIDC account then signs in as a (pinned) admin. Alternatively, list bootstrap admins in auth.oidc.admin_emails and skip this step entirely.

headscale-admin admin list shows the current principals. (Each install guide below repeats the exact command for its environment.)

What’s next

Head to the guide for your platform: By hand · Docker · Docker Compose · Ansible.