Skip to main content

Headscale

This guide explains how to set up single sign-on (SSO) between VaultysHub and Headscale using OpenID Connect. Headscale is an open-source implementation of the Tailscale control server.

Prerequisites​

  • Headscale version 0.22.0 or higher
  • Administrator access to the Headscale server
  • Application configured in VaultysHub with OpenID Connect
  • Valid SSL certificate for Headscale (required for OIDC)

Overview​

Headscale supports authentication via OpenID Connect, allowing users to authenticate with VaultysHub instead of pre-generating authentication keys.

Configuration in VaultysHub​

1. Create the application​

  1. Log in to VaultysHub as an administrator
  2. Go to Applications → Add
  3. Create a new application:
    • Name: Headscale
    • URL: https://headscale.example.com
    • Description: Headscale control server
    • Icon: Use the Tailscale icon or a custom icon

2. Configure OpenID Connect​

  1. In the Authentication tab
  2. Select OpenID Connect as the authentication type
  3. Note the following information:
    • Client ID: headscale-xxxxxx
    • Client Secret: secret-xxxxxx
    • Issuer URL: https://your-vaultyshub.link.vaultys.org/api/oidc/[appid]
    • App ID: [appid] (unique identifier of the application in VaultysHub)

3. Configure the redirect URLs​

Add the callback URL in Authorized redirect URLs:

https://headscale.example.com/oidc/callback

Note: The [appid] will be automatically generated when creating the application in VaultysHub.

4. Configure the scopes​

Required scopes:

  • openid
  • profile
  • email

Headscale Configuration​

1. Configure the config.yaml file​

Edit the Headscale configuration file (usually /etc/headscale/config.yaml):

# Server configuration
server_url: https://headscale.example.com
listen_addr: 0.0.0.0:8080
metrics_listen_addr: 0.0.0.0:9090

# Database configuration
db_type: sqlite3
db_path: /var/lib/headscale/db.sqlite

# OIDC configuration
oidc:
# Enable OIDC
enabled: true

# VaultysHub issuer URL
issuer: "https://your-vaultyshub.link.vaultys.org/api/oidc/[appid]"

# Client ID and Secret from VaultysHub
client_id: "headscale-xxxxxx"
client_secret: "secret-xxxxxx"

# Scopes to request
scope:
- openid
- profile
- email

# Claims to map user information
claims:
# Claim for email (user identifier)
email: email
# Claim for display name
name: name

# Allowed email domains
allowed_domains:
- example.com

# Automatically create users
auto_create_users: true

# Authentication token lifetime
expiry: 180d

# Use OIDC token expiration
use_expiry_from_token: false

# Namespaces/users configuration
ip_prefixes:
- 100.64.0.0/10
- fd7a:115c:a1e0::/48

# DNS configuration
dns_config:
nameservers:
- 1.1.1.1
- 8.8.8.8
domains: []
magic_dns: true
base_domain: example.com

2. Configuration with environment variables​

Alternatively, you can use environment variables:

export HEADSCALE_OIDC_ENABLED=true
export HEADSCALE_OIDC_ISSUER="https://your-vaultyshub.link.vaultys.org"
export HEADSCALE_OIDC_CLIENT_ID="headscale-xxxxxx"
export HEADSCALE_OIDC_CLIENT_SECRET="secret-xxxxxx"
export HEADSCALE_OIDC_ALLOWED_DOMAINS="example.com"

3. Starting with Docker​

If you are using Docker, here is an example of docker-compose.yml:

version: '3.8'

services:
headscale:
image: headscale/headscale:latest
container_name: headscale
volumes:
- ./config:/etc/headscale
- ./data:/var/lib/headscale
ports:
- "8080:8080"
- "9090:9090"
environment:
- HEADSCALE_OIDC_ENABLED=true
- HEADSCALE_OIDC_ISSUER=https://your-vaultyshub.link.vaultys.org
- HEADSCALE_OIDC_CLIENT_ID=headscale-xxxxxx
- HEADSCALE_OIDC_CLIENT_SECRET=secret-xxxxxx
command: headscale serve
restart: unless-stopped

4. Reverse proxy configuration (Nginx)​

Example Nginx configuration for Headscale with SSL:

server {
listen 443 ssl http2;
server_name headscale.example.com;

ssl_certificate /etc/letsencrypt/live/headscale.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/headscale.example.com/privkey.pem;

location / {
proxy_pass http://localhost:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_redirect http:// https://;
proxy_buffering off;
}
}

ACL Configuration with Groups​

1. Structure of group-based ACLs​

VaultysHub does not pass user groups: groups are defined directly in the Headscale ACLs file, based on the users' email addresses.

Create a file acls.yaml or acls.hujson:

groups:
group:admins:
group:developers:

acls:
- action: accept
src:
- group:admins
dst:
- "*:*"

- action: accept
src:
- group:developers
dst:
- tag:dev:*
- tag:staging:*

tagOwners:
tag:prod:
- group:admins
tag:dev:
- group:developers
tag:staging:
- group:developers

2. Applying the ACLs​

# Apply the ACLs
headscale acls set acls.yaml

# Check the ACLs
headscale acls show

Client Configuration​

1. Installing the Tailscale client​

# Linux
curl -fsSL https://tailscale.com/install.sh | sh

# macOS
brew install tailscale

# Windows
# Download from https://tailscale.com/download

2. Logging in with OIDC​

# Log in to the Headscale server with OIDC
tailscale up --login-server https://headscale.example.com

# The browser automatically opens for authentication
# You will be redirected to VaultysHub to log in

3. Checking the connection​

# Check the status
tailscale status

# See connected peers
tailscale status --peers

# Check the assigned IP
tailscale ip

Testing the Configuration​

1. Testing OIDC authentication​

  1. On a client, run:
    tailscale up --login-server https://headscale.example.com
  2. The browser opens on the authentication page
  3. Click on "Login with OIDC"
  4. You are redirected to VaultysHub
  5. Log in with your VaultysHub credentials
  6. After authentication, you are redirected to Headscale
  7. The Tailscale client should connect automatically

2. Verification in Headscale​

# List users
headscale users list

# List machines
headscale machines list

# View details of a user
headscale users show [email protected]

# View routes
headscale routes list

3. Connectivity test​

# Ping another machine
ping 100.64.0.2

# Test with MagicDNS hostname
ping machine-name.example.com.beta.tailscale.net

Troubleshooting​

Error "OIDC authentication failed"​

Problem: OIDC authentication fails

Solution:

  1. Check the Headscale logs:
    journalctl -u headscale -f
  2. Verify the issuer URL in the config
  3. Test OIDC discovery with your appid:
    curl https://your-vaultyshub.link.vaultys.org/api/oidc/[appid]/.well-known/openid-configuration

Error "Invalid client credentials"​

Problem: OIDC credentials are invalid

Solution:

  1. Check the Client ID and Secret in VaultysHub
  2. Ensure there are no spaces or hidden characters
  3. Restart Headscale after modifying the config

The user is created but cannot log in​

Problem: The user exists in Headscale but login fails

Solution:

  1. Check that the email domain is in allowed_domains
  2. Check the ACLs if configured
  3. Try re-authenticating the user:
    headscale preauthkeys create --user [email protected]

Groups are not synchronized​

Problem: VaultysHub groups are not visible in Headscale

Solution: this is the expected behavior. VaultysHub does not pass groups; define them in the Headscale ACLs file (section groups).

SSL/TLS error​

Problem: Certificate error when connecting OIDC

Solution:

  1. Headscale requires HTTPS with a valid certificate
  2. Do not use self-signed certificates
  3. Check the certificate chain:
    openssl s_client -connect headscale.example.com:443 -showcerts

Security​

Recommendations​

  1. HTTPS required: Headscale requires HTTPS for OIDC
  2. Firewall: Limit access to the Headscale port (8080)
  3. Database: Use PostgreSQL in production
  4. Secrets: Store secrets securely
  5. Monitoring: Monitor logs and metrics

Key Rotation​

# Generate a new node key
headscale nodes key rotate --id NODE_ID

# Revoke a machine
headscale machines delete --id MACHINE_ID

Backup​

# Backup the SQLite database
sqlite3 /var/lib/headscale/db.sqlite ".backup /backup/headscale.db"

# Backup the configuration
cp -r /etc/headscale /backup/headscale-config

# With PostgreSQL
pg_dump headscale > /backup/headscale.sql

Advanced Configuration​

Multi-user mode with namespaces​

# In config.yaml
oidc:
# Create a namespace per user
create_namespace_for_user: true

# Namespace format
namespace_format: "{email}"

# Or use a specific claim
namespace_claim: "department"

Integration with Kubernetes​

Deploy Headscale in Kubernetes:

apiVersion: apps/v1
kind: Deployment
metadata:
name: headscale
spec:
replicas: 1
selector:
matchLabels:
app: headscale
template:
metadata:
labels:
app: headscale
spec:
containers:
- name: headscale
image: headscale/headscale:latest
ports:
- containerPort: 8080
env:
- name: HEADSCALE_OIDC_ENABLED
value: "true"
- name: HEADSCALE_OIDC_ISSUER
value: "https://your-vaultyshub.example.com"
- name: HEADSCALE_OIDC_CLIENT_ID
valueFrom:
secretKeyRef:
name: headscale-oidc
key: client-id
- name: HEADSCALE_OIDC_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: headscale-oidc
key: client-secret
volumeMounts:
- name: config
mountPath: /etc/headscale
- name: data
mountPath: /var/lib/headscale
volumes:
- name: config
configMap:
name: headscale-config
- name: data
persistentVolumeClaim:
claimName: headscale-data

Monitoring with Prometheus​

Headscale exposes metrics on port 9090:

# prometheus.yml
scrape_configs:
- job_name: 'headscale'
static_configs:
- targets: ['headscale.example.com:9090']

Resources​