Skip to main content

Headscale

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

Prerequisites

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

Overview

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

1. Create the application

  1. Log in to SmartLink as an administrator
  2. Go to ApplicationsAdd
  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-smartlink.link.vaultys.org/api/oidc/[appid]
    • App ID: [appid] (unique application identifier in SmartLink)

3. Configure Redirect URLs

Add the callback URL to Allowed Redirect URLs:

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

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

4. Configure Scopes

Required scopes:

  • openid
  • profile
  • email
  • groups (optional, for group-based ACLs)

Headscale Configuration

1. Configuration in 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

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

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

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

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

# Allowed email domains
allowed_domains:
- example.com

# Allowed groups (optional)
allowed_groups:
- admins
- developers
- users

# Automatically create users
auto_create_users: true

# Authentication token expiry
expiry: 180d

# Use OIDC token expiry
use_expiry_from_token: false

# Namespace/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-smartlink.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-smartlink.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)

Nginx configuration example 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;
}
}

Group-Based ACL Configuration

1. Structure of Group-Based ACLs

Create an acls.yaml or acls.hujson file:

groups:
group:admins:
- user1@example.com
group:developers:
- user2@example.com

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 ACLs

# Apply ACLs
headscale acls set acls.yaml

# Check ACLs
headscale acls show

Client Configuration

1. Tailscale Client Installation

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

# macOS
brew install tailscale

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

2. Connecting with OIDC

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

# The browser will open automatically for authentication
# You will be redirected to SmartLink to log in

3. Checking the Connection

# Check the status
tailscale status

# View connected peers
tailscale status --peers

# Check the assigned IP
tailscale ip

Configuration Testing

1. Testing OIDC Authentication

  1. On a client, run:
    tailscale up --login-server https://headscale.example.com
  2. The browser opens to the authentication page
  3. Click on "Login with OIDC"
  4. You will be redirected to SmartLink
  5. Log in with your SmartLink credentials
  6. After authentication, you will be 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 user@example.com

# View routes
headscale routes list

3. Connectivity Testing

# 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"

Issue: 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-smartlink.link.vaultys.org/api/oidc/[appid]/.well-known/openid-configuration

Error "Invalid client credentials"

Issue: OIDC client credentials are invalid

Solution:

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

User is created but cannot connect

Issue: User exists in Headscale but connection fails

Solution:

  1. Check that the email domain is in allowed_domains
  2. If using allowed_groups, verify the user's groups
  3. Check ACLs if configured
  4. Try re-authenticating the user:
    headscale preauthkeys create --user user@example.com

Groups are not synchronized

Issue: SmartLink groups are not visible in Headscale

Solution:

  1. Ensure the groups scope is enabled in SmartLink
  2. Test the UserInfo endpoint:
    curl -H "Authorization: Bearer TOKEN" https://your-smartlink.link.vaultys.org/api/oidc/[appid]/userinfo
  3. Check the group claims in the Headscale config
  4. Groups should be an array of strings

SSL/TLS Error

Issue: Certificate error during OIDC connection

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. Mandatory HTTPS: Headscale requires HTTPS for OIDC
  2. Firewall: Limit access to the Headscale port (8080)
  3. Database: Use PostgreSQL in production
  4. Secrets: Securely store secrets
  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

Deploying 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-smartlink.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