Documentation
README
headscale-node-lifecycle
Overview
Headscale manages nodes in a tailnet with two identity models:
- Personal nodes — registered under a specific user account (e.g.
yourname@domain). The node key is tied to that user's identity. - Tagged nodes — belong to the special
tagged-devicesuser (created automatically by Headscale). These nodes are identified by one or more tags (e.g.tag:webserver,tag:monitoring) and are typically used for infrastructure service nodes.
Registration happens through:
- Web auth — user visits a URL to authenticate with an OIDC/OAuth provider
- Pre-authenticated key (auth key) — a one-time or reusable key generated by the admin that embeds tags, user, and expiry
- CLI / API — direct node registration via
headscalecommands or REST API calls
Pre-authenticated Keys
Auth keys streamline automated node registration. Key attributes:
| Parameter | Description |
|---|---|
| Expiration | Default: 1 hour. Use 0 for no expiry (not recommended). |
| Reusable | Single-use by default. Mark reusable for fleet provisioning. |
| Ephemeral | Ephemeral nodes are removed from the tailnet when they disconnect. Perfect for CI runners and ephemeral workloads. |
| Tags | Assign tags to create a tagged node automatically. |
Node Registration
- Personal nodes: Create an auth key for a user → run
tailscale up --auth-key=<key>on the device → approve in Headscale if registration is open. - Tagged nodes: Create an auth key with
--tags→ runtailscale up --auth-key=<key>→ the node is auto-approved and tagged.
Node Listing
List nodes filtered by user, tag, or online status. Output includes:
- Node ID
- Name (hostname)
- Tailscale IP address(es)
- Assigned tags
- User/owner
- Online/offline status
- Last seen timestamp
- Operating system
- Tailscale client version
Node Tagging
Tags can be added or replaced on existing nodes. Tags always carry the tag: prefix in Headscale. When adding tags, existing tags are preserved unless --replace is specified.
Node Deletion
To decommission a node permanently:
headscale nodes delete -i <node-id>
Or via the REST API: DELETE /api/v1/node/<node-id>
Decommissioning is irreversible. For ephemeral nodes, disconnection alone is sufficient — the server removes them automatically.
Environment
| Variable | Description |
|---|---|
HEADSCALE_URL |
Base URL of the Headscale server (e.g. https://headscale.example.com) |
HEADSCALE_API_KEY |
API key from headscale apikeys create |
Both env vars are required for API-based operations when the headscale CLI is not available on PATH.
Gotchas
- Auth key expiration: Default is 1 hour. If you're provisioning a device and it takes longer, the key expires and registration fails. Set a longer expiry explicitly.
- Tagged node user: Tagged nodes always belong to the
tagged-devicesuser. Do not try to assign them to a personal user. - Connectivity testing: Nodes must be online (
Connected: true) to test connectivity. Offline nodes do not respond to ping/ICMP within the tailnet. - CLI vs API: When both are available, the CLI is preferred for interactive use. The REST API is preferred for automation scripts.
- Key reuse: Reusable keys are convenient but less secure. Use with care, especially in production environments.
- Ephemeral nodes: Setting
--ephemeralmeans the node is fully removed on disconnection — there is no record of it in the tailnet afterward.
Trigger Conditions
- "auth key"
- "preauthkey"
- "register node"
- "approve node"
- "tag node"
- "node list"
- "decommission node"
Scripts
hs-create-authkey.sh
Create pre-authenticated keys via Headscale CLI or REST API.
hs-create-authkey.sh --user <user> --tags <tag> --expiration <duration> --reusable --ephemeral [--json] [--dry-run]
hs-list-nodes.sh
List all nodes in the tailnet with status and metadata.
hs-list-nodes.sh [--json] [--user <user>] [--tag <tag>] [--online-only]
hs-approve-nodes.sh
Approve pending node registrations.
hs-approve-nodes.sh [--all] [--auth-id <id>] [--dry-run] [--json]
hs-tag-node.sh
Update tags on an existing node.
hs-tag-node.sh --node <id/name> --tags <tag1,tag2> [--replace | --add] [--dry-run] [--json]