Section

UI walkthrough

A page-by-page tour of the headscale-admin console: what each view shows, what you can do there, and where the data comes from.

The console is a Svelte single-page app served by the same binary that supervises headscale, under /admin. The sidebar groups pages into four areas:

  • Overview - the dashboard.
  • Network - Nodes, Routes & exit nodes, DNS.
  • Access - Users, Pre-auth keys, API keys, ACL policy, Access map, and (when enabled) User sync.
  • System - Audit log, Admins, Settings, Backup. Admin-only.

Operators see Overview, Network, and Access; admins additionally see the System group. The screenshots below were captured against a synthetic dev dataset (15 users, 15 nodes, a representative ACL policy) - your data will look the same.

Connect a device

Connect a device

The public signup page (the default landing for /) is where someone onboards a machine. Pick a platform - Apple, Linux, or Windows - and it expands with copy-paste install and tailscale up --login-server … steps pre-filled with this server’s URL. It needs no login, so you can hand the link to a teammate to join the tailnet. (When the admin UI is IP-restricted you can still keep just this page public with server.public_signup.)

Sign in

The login screen appears when authentication is enabled. Local sign-in is email + password; if a second factor is set up, it then asks for a TOTP code (or a recovery code). With OIDC configured, a Sign in with SSO button appears, and with passkeys registered, a Sign in with a passkey button. When auth.disabled: true, there is no login screen at all - every visitor is treated as admin (local development only).

Overview

Overview dashboard

The dashboard summarises the tailnet at a glance: clickable tiles for nodes (online / total), users, pre-auth keys, and API keys; a control-server status card (headscale-admin and headscale health + versions, and the client-facing control URL); a ready-to-copy tailscale up / tailscale login snippet; and a recently added nodes grid with each machine’s addresses. A red banner appears here if headscale can’t be reached.

Nodes

Nodes

Every machine registered with the control server. Search by name / user / IP / tag, filter by last-seen, and sort any column. Rows show IPs, ACL tags, route counts (advertised / approved), online status, and last-seen. The toolbar has register node, backfill IPs, and refresh.

Clicking a node opens its detail modal:

Node detail

The modal shows the full record - status, IDs, hostname, user, addresses, tags, advertised and approved routes (here an exit node advertising 0.0.0.0/0, ::/0), timestamps and key expiry - and the per-node actions: Rename, Edit tags, Edit routes, Expire, Delete.

Routes & exit nodes

Routes & exit nodes

Subnet routes and exit nodes in one place. Tiles up top count advertised, approved, pending, and exit nodes. The Subnet routes table lists each advertised CIDR, the node and owner advertising it, its approval state, and an enable toggle; Exit nodes does the same for machines offering 0.0.0.0/0, ::/0. Approving or revoking a route is a single toggle.

DNS

DNS

MagicDNS and resolver configuration, written straight into headscale.yaml. Toggle MagicDNS, set the base domain, manage global nameservers, add split-DNS domains routed to private resolvers, force clients onto your nameservers with override local DNS, and add search domains. Because these land in headscale’s config, the page notes when a headscale restart is needed for changes to take effect.

Users

Users

Tailnet identities. Search by name / email / provider, and create users with + new user. Each row shows the provider (e.g. oidc or local) and creation time; clicking a user opens a detail modal with their ACL groups and ownable tags (read from the policy) and the nodes they own. The tagged-devices chip surfaces machines owned by an ACL tag: rather than a real user.

Pre-auth keys

New pre-auth key

Pre-authentication keys for non-interactive onboarding (tailscale up --auth-key=…). The list shows each key’s user, reusable / ephemeral / used flags, ACL tags, and expiry. The + new key modal (above) picks the user, the reusable and ephemeral flags, an expiration (e.g. 24h, 7d, or 0 for none), and optional ACL tags. The minted key is shown once with a ready-to-run tailscale up command - copy it before closing.

API keys

API keys

Bearer keys for headscale’s own API, used by the headscale CLI and other tooling. Each row shows the key prefix and its created / expires timestamps, with expire and delete actions. As with pre-auth keys, a newly created key is revealed only once.

ACL policy

ACL policy - visual editor

Who can reach what across the tailnet. The visual editor has a tab per policy section - Groups, Tag owners, Hosts, Auto-approvers, ACL rules, Grants, Node attrs, SSH - each row reorderable and editable, with “+ from known” pickers that pull existing users, groups, and tags. The JSON editor toggle exposes the raw HuJSON with format / validate. The header shows the policy mode (database or file) and last-updated time; Validate checks the policy and Save policy applies it. In file mode, saving notes that a headscale restart is required.

Access map

Access map

A live graph of the effective access policy - ACL rules and grants rendered as edges between users, groups, tags, hosts, and nodes, annotated with the allowed ports (e.g. group:ops → tag:prod on 22, 443). Search to focus an entity’s neighbourhood, fold users into group badges or devices into tag counts, and export PNG. It’s the fastest way to sanity-check “can X actually reach Y?” after a policy edit.

User sync

User sync

Shown only when headscale_pf.enabled: true. Fills the group: members of your ACL policy from an external identity source using the bundled headscale-pf. Click Sync to pull the latest membership and preview the diff against the current policy; review it, then Save to apply over the local socket. Source credentials live only in headscale-admin’s config and are never exposed to the browser.

Audit log

Audit log

An immutable record of every state-changing action through the console - user / node / key edits, policy and config changes, and login attempts - newest first. Tiles count events, unique actors, failures, and action types in the window. Free-text search spans target, IP, actor, action, and the detail payload; an exact-action filter and a time-range selector narrow further. Each row expands to its full event payload. Admins can prune old entries; a daily auto-prune runs per the configured retention.

Admins

Admins & operators

Who can sign in to the console and what they can do. Each principal has a role (admin or operator), a source (local or oidc), an enabled flag, and a last-login time. Create principals with + new principal (email, name, role, and - in local mode - a password). When OIDC is enabled, password login is disabled and roles follow the IdP group mapping; pre-creating a principal here pre-assigns a role by email, and pinned admins stay admin regardless of group changes.

Settings

Settings

The supervised headscale’s configuration, plus deployment info. The top section reports the authentication mode, the client-facing control server URL, and the running headscale-admin / headscale versions - with a Download & switch control to move the supervised headscale to another release (checksum-verified, restarts headscale, and auto-reverts on failure). Below, the User sync (headscale-pf) card shows the configured source and tool version, and the rest of the page is a structured editor over headscale.yaml, with the raw file available in a collapsible at the bottom. Saving rewrites headscale.yaml (a .bak is written first) and flags when a restart is needed.

Backup

Backup

One-click exports of the pieces that make up this deployment: the ACL policy (HuJSON), the headscale-admin database, the headscale database, or everything in a single archive. The database and full-archive exports briefly stop headscale for a consistent snapshot. Files contain secrets (keys, sessions) - store them accordingly. To restore, stop headscale-admin, put the files back under work_dir, and start it again.