Configuration
Headtower reads its configuration from the environment, but nothing is strictly required to boot: with no Headscale connection set, it opens a /setup wizard, and identity providers and the agent can be configured from the Settings panel once you’re in. A value you do provide is validated - a malformed URL or a half-set group (see OIDC below) is named rather than silently ignored.
These values include secrets and are read server-side only. They are never
exposed to the browser, so do not prefix them with NEXT_PUBLIC_.
Required
| Variable | Description |
|---|---|
HEADSCALE_URL | Base URL of your Headscale instance. Must be an http(s) URL; a trailing slash is stripped automatically. |
HEADSCALE_API_KEY | Headscale API key Headtower uses to authenticate against the control plane. Grants full control-plane access - guard it. |
HEADSCALE_URL="https://headscale.example.com"
HEADSCALE_API_KEY="<your-headscale-api-key>"With just these two set, Headtower runs in operator mode: whoever holds the configured API key is the single authenticated operator. There are no per-user accounts until single sign-on is configured.
Single sign-on (optional)
To put real identities in front of the console, configure OIDC. It works with any standards-compliant provider - for example Pocket ID or a generic IdP.
| Variable | Description |
|---|---|
HEADTOWER_SESSION_SECRET | Required for sign-in. Signs session cookies. Min 32 chars - generate with openssl rand -hex 32. A sign-in attempt fails without it. |
HEADTOWER_OIDC_ISSUER | Issuer URL of your OpenID provider. |
HEADTOWER_OIDC_CLIENT_ID | OAuth client ID registered for Headtower. |
HEADTOWER_OIDC_CLIENT_SECRET | OAuth client secret for that client. |
OIDC is all-or-nothing: set all three HEADTOWER_OIDC_* variables, or
none. A partial set is treated as a misconfiguration and Headtower names the
gap instead of silently half-enabling sign-in. You can also add providers from
the Settings panel instead of the environment - either way, set
HEADTOWER_SESSION_SECRET first.
HEADTOWER_SESSION_SECRET="<32+ char random string>"
HEADTOWER_OIDC_ISSUER="https://id.example.com"
HEADTOWER_OIDC_CLIENT_ID="headtower"
HEADTOWER_OIDC_CLIENT_SECRET="<client-secret>"Secrets at rest (optional)
| Variable | Description |
|---|---|
HEADTOWER_SECRET | Encrypts secrets (the Headscale API key, OIDC client secrets) stored in the local SQLite DB. Unset means they are stored in plaintext. Rotating it re-encrypts on the next write. |
Agent (optional)
The browser SSH terminal and richer device metadata come from an optional agent
sidecar, off by default (docker compose --profile agent up -d).
| Variable | Description |
|---|---|
HEADTOWER_AGENT_AUTHKEY | A Headscale pre-auth key the agent registers its own node with. |
HEADTOWER_AGENT_LOGIN_SERVER | Your Headscale URL (required when the agent is enabled). |
HEADTOWER_AGENT_URL | Where the app reaches the agent, e.g. http://agent:8410. |
HEADTOWER_AGENT_SSH_SECRET | Shared HMAC secret for the SSH bridge; must match on both the app and the agent. |
HEADTOWER_AGENT_HOSTNAME | The agent’s node name (must match on both services; default headtower-agent). |
Local state (optional)
| Variable | Description |
|---|---|
HEADTOWER_DB_PATH | Filesystem path to Headtower’s own local store - operators and roles, sessions, and the audit log. |
Headscale stays the source of truth for tailnet data; this store only holds
Headtower-specific state. Point HEADTOWER_DB_PATH at a persistent volume so it
survives restarts and upgrades:
HEADTOWER_DB_PATH="/data/headtower.db"In a container, bind-mount the directory that holds this file (see the Docker note in Getting started). If unset, Headtower falls back to a sensible default path for local development.
A complete example
# Required - the Headscale connection.
HEADSCALE_URL="https://headscale.example.com"
HEADSCALE_API_KEY="<your-headscale-api-key>"
# Optional - single sign-on (all three, or none).
HEADTOWER_OIDC_ISSUER="https://id.example.com"
HEADTOWER_OIDC_CLIENT_ID="headtower"
HEADTOWER_OIDC_CLIENT_SECRET="<client-secret>"
# Optional - where Headtower keeps its own state.
HEADTOWER_DB_PATH="/data/headtower.db"