Skip to main content
The headscale.sh script provides a convenient wrapper around common Headscale operations, simplifying user management, node administration, pre-auth key generation, and system monitoring.

Location

scripts/headscale.sh

Usage

./scripts/headscale.sh <command> [arguments]
The script automatically checks if the Headscale container is running before executing commands and provides colored output for better readability.

User Management

users list

List all users in the Headscale server.
./scripts/headscale.sh users list
Example output:
id | name     | created_at
---|----------|------------------
1  | myuser   | 2026-03-04 10:30:00
2  | alice    | 2026-03-04 11:15:00

users create

Create a new user.
./scripts/headscale.sh users create <username>
username
string
required
The username to create. Must be unique.
Example:
./scripts/headscale.sh users create myuser
# Output: User 'myuser' created successfully

users destroy

Delete a user and all associated data. This command requires confirmation.
./scripts/headscale.sh users destroy <username>
username
string
required
The username to delete. This will remove all associated nodes, keys, and data.
Example:
./scripts/headscale.sh users destroy myuser
# Warning: This will delete user 'myuser' and all associated data
# Are you sure? (yes/no): yes
# Output: User 'myuser' destroyed
Destroying a user permanently deletes all associated nodes, pre-auth keys, and routes. This action cannot be undone.

Node Management

nodes list

List all nodes (devices) connected to the Headscale server.
./scripts/headscale.sh nodes list
Example output:
id | hostname      | user   | ip addresses     | last seen
---|---------------|--------|------------------|------------------
1  | laptop        | myuser | 100.64.0.1       | 2026-03-04 12:00:00
2  | server        | myuser | 100.64.0.2       | 2026-03-04 11:55:00

nodes delete

Delete a specific node by its identifier.
./scripts/headscale.sh nodes delete <node-id>
node-id
string
required
The node ID or hostname to delete. Use nodes list to find the identifier.
Example:
./scripts/headscale.sh nodes delete 1
# Output: Node deleted

nodes expire

Expire all offline nodes in the network.
./scripts/headscale.sh nodes expire
Example:
./scripts/headscale.sh nodes expire
# Output: Expired all offline nodes
Expiring nodes marks them as inactive but doesn’t delete them. Use this for cleanup of temporarily disconnected devices.

Pre-Auth Key Management

keys list

List all pre-auth keys for a specific user.
./scripts/headscale.sh keys list <username>
username
string
required
The username whose pre-auth keys to list.
Example:
./scripts/headscale.sh keys list myuser

keys create

Create a new pre-auth key for device registration.
./scripts/headscale.sh keys create <username> [options]
username
string
required
The username for whom to create the pre-auth key.
Options:
--reusable
flag
Makes the key reusable for multiple devices. Without this flag, the key can only be used once.
--ephemeral
flag
Creates an ephemeral node that will be automatically removed when disconnected.
--expiration
duration
Sets the key expiration time. Format: 24h (hours), 7d (days), 168h (1 week).
Examples:
# Create a single-use key
./scripts/headscale.sh keys create myuser

# Create a reusable key that expires in 24 hours
./scripts/headscale.sh keys create myuser --reusable --expiration 24h

# Create an ephemeral key
./scripts/headscale.sh keys create myuser --ephemeral

# Create a reusable key that expires in 7 days
./scripts/headscale.sh keys create myuser --reusable --expiration 168h
For production environments, use short expiration times (24h or less) and avoid reusable keys unless necessary.

Route Management

routes list

List all subnet routes advertised by nodes.
./scripts/headscale.sh routes list
Example output:
id | node    | route          | enabled
---|---------|----------------|--------
1  | server  | 10.0.0.0/24   | false
2  | gateway | 192.168.1.0/24| true

routes enable

Enable a specific subnet route.
./scripts/headscale.sh routes enable <route-id>
route-id
number
required
The route ID to enable. Use routes list to find the route ID.
Example:
./scripts/headscale.sh routes enable 1
# Output: Route enabled
Routes must be explicitly enabled after a node advertises them. This provides security by requiring administrator approval for subnet routing.

Monitoring Commands

status

Show the status of all Docker containers in the stack.
./scripts/headscale.sh status
Example output:
Docker Container Status:
CONTAINER ID   NAMES            STATUS
abc123         headscale        Up 2 hours
def456         nginx            Up 2 hours
ghi789         postgres         Up 2 hours

health

Check the Headscale health endpoint.
./scripts/headscale.sh health
Example output:
Checking Headscale health...
{"status":"pass"}
The health check queries the internal health endpoint at http://localhost:8080/health inside the container.

logs

Show Headscale container logs with follow mode.
./scripts/headscale.sh logs [lines]
lines
number
default:"50"
Number of recent log lines to display. Defaults to 50 if not specified.
Examples:
# Show last 50 lines (default)
./scripts/headscale.sh logs

# Show last 100 lines
./scripts/headscale.sh logs 100
Logs are displayed in follow mode (-f), showing real-time updates. Press Ctrl+C to exit.

Help Command

Display the help message with all available commands.
./scripts/headscale.sh help
# or
./scripts/headscale.sh --help
# or
./scripts/headscale.sh -h

Implementation Details

Container Check

The script verifies the Headscale container is running before executing commands:
if ! docker ps | grep -q headscale; then
    echo "Error: Headscale container is not running"
    exit 1
fi

Command Execution

All Headscale commands are executed inside the container:
docker exec headscale headscale <command>

Color-Coded Output

  • Green: Success messages
  • Yellow: Warnings
  • Red: Errors

Common Workflows

Initial Setup

# 1. Create a user
./scripts/headscale.sh users create myuser

# 2. Generate a pre-auth key
./scripts/headscale.sh keys create myuser --reusable --expiration 24h

# 3. Use the key to connect devices
# (Run on the device)
sudo tailscale up --login-server http://localhost:8000 --authkey <key>

# 4. Verify nodes are connected
./scripts/headscale.sh nodes list

Subnet Routing

# 1. Advertise routes from a node
# (Run on the node)
sudo tailscale up --advertise-routes=10.0.0.0/24

# 2. List advertised routes
./scripts/headscale.sh routes list

# 3. Enable the route
./scripts/headscale.sh routes enable 1

Cleanup

# Remove offline nodes
./scripts/headscale.sh nodes expire

# Delete specific nodes
./scripts/headscale.sh nodes list
./scripts/headscale.sh nodes delete <node-id>

# Remove users
./scripts/headscale.sh users destroy olduser

Error Handling

The script exits with an error code if:
  • The Headscale container is not running
  • Required arguments are missing
  • Unknown commands are provided
  • Docker commands fail

Direct Headscale Access

For commands not covered by the script, use direct Docker exec:
docker exec headscale headscale <command>
Refer to the Headscale CLI documentation for all available commands.

Build docs developers (and LLMs) love