NEWS Earn Money with Onidel Cloud! Affiliate Program Details - Check it out

How to Self‑Host Headscale (Open‑Source Tailscale Control Plane) on an Ubuntu 24.04 VPS with Docker Compose, Caddy, and OIDC SSO (2025 Tutorial)

Introduction

While Tailscale offers excellent mesh networking capabilities, many organizations prefer self-hosting their control plane for enhanced privacy, compliance, or cost considerations. Headscale is an open-source implementation of Tailscale’s coordination server that provides the same mesh networking functionality while giving you complete control over your infrastructure.

In this comprehensive tutorial, you’ll learn how to deploy Headscale on an Ubuntu 24.04 VPS using Docker Compose, secure it with Caddy for automatic HTTPS, and integrate OIDC single sign-on for centralized authentication. This setup provides a production-ready alternative to Tailscale’s managed service while maintaining enterprise-grade security and scalability.

Prerequisites

Before starting this tutorial, ensure you have:

  • VPS Requirements: Ubuntu 24.04 LTS with minimum 2GB RAM, 1 vCPU, and 20GB storage
  • Domain Access: A domain or subdomain pointing to your VPS (e.g., headscale.yourdomain.com)
  • Root/Sudo Access: Administrative privileges on your VPS
  • Docker Knowledge: Basic understanding of Docker and container orchestration
  • OIDC Provider: Optional but recommended – Google OAuth, GitHub, or similar identity provider

Warning: This tutorial involves network configuration changes. Ensure you have alternative access methods to your VPS before proceeding.

Step 1: System Preparation and Docker Installation

First, update your Ubuntu 24.04 system and install Docker with Docker Compose:

# Update system packages
sudo apt update && sudo apt upgrade -y

# Install prerequisites
sudo apt install -y curl wget git ufw

# Install Docker
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh

# Add current user to docker group
sudo usermod -aG docker $USER

# Install Docker Compose
sudo apt install -y docker-compose-plugin

# Verify installation
docker --version
docker compose version

Log out and back in to apply group membership changes.

Step 2: Create Project Structure

Create a dedicated directory structure for your Headscale deployment:

# Create project directory
mkdir -p ~/headscale-stack/{config,data,caddy}
cd ~/headscale-stack

# Create subdirectories
mkdir -p config/headscale data/headscale caddy/config caddy/data

Step 3: Configure Headscale

Create the Headscale configuration file with OIDC integration:

# Generate Headscale configuration
cat > config/headscale/config.yaml << 'EOF'
server_url: https://headscale.yourdomain.com
listen_addr: 0.0.0.0:8080
metrics_listen_addr: 127.0.0.1:9090
grpc_listen_addr: 0.0.0.0:50443
grpc_allow_insecure: false

private_key_path: /var/lib/headscale/private.key
noise:
  private_key_path: /var/lib/headscale/noise_private.key

ip_prefixes:
  - fd7a:115c:a1e0::/48
  - 100.64.0.0/10

derp:
  server:
    enabled: false
  urls:
    - https://controlplane.tailscale.com/derpmap/default
  auto_update_enabled: true
  update_frequency: 24h

database:
  type: sqlite3
  sqlite:
    path: /var/lib/headscale/db.sqlite

oidc:
  only_start_if_oidc_is_available: true
  issuer: "https://accounts.google.com"
  client_id: "your-client-id.apps.googleusercontent.com"
  client_secret: "your-client-secret"
  scope: ["openid", "profile", "email"]
  extra_params: {}
  allowed_domains: ["yourdomain.com"]
  allowed_users: []
  strip_email_domain: true
  expiry: 180d

logtail:
  enabled: false

randommac: false
EOF

Important: Replace headscale.yourdomain.com, your-client-id, your-client-secret, and yourdomain.com with your actual values.

Step 4: Create Docker Compose Configuration

Create a comprehensive Docker Compose file that includes Headscale and Caddy as reverse proxy:

# Create docker-compose.yml
cat > docker-compose.yml << 'EOF'
version: '3.8'

services:
  headscale:
    image: headscale/headscale:0.23
    container_name: headscale
    restart: unless-stopped
    volumes:
      - ./config/headscale:/etc/headscale:ro
      - ./data/headscale:/var/lib/headscale
    ports:
      - "127.0.0.1:8080:8080"
      - "50443:50443"
    command: headscale serve
    environment:
      - TZ=UTC
    networks:
      - headscale-net

  caddy:
    image: caddy:2.7-alpine
    container_name: caddy
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"
    volumes:
      - ./caddy/Caddyfile:/etc/caddy/Caddyfile:ro
      - ./caddy/data:/data
      - ./caddy/config:/config
    networks:
      - headscale-net
    depends_on:
      - headscale

networks:
  headscale-net:
    driver: bridge

EOF

Step 5: Configure Caddy Reverse Proxy

Create a Caddyfile for automatic HTTPS and proxying:

# Create Caddyfile
cat > caddy/Caddyfile << 'EOF'
headscale.yourdomain.com {
    reverse_proxy headscale:8080 {
        health_uri /health
        health_interval 30s
        health_timeout 5s
    }
    
    encode gzip
    
    log {
        output stderr
        format console
        level INFO
    }
    
    header {
        # Security headers
        Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
        X-Content-Type-Options "nosniff"
        X-Frame-Options "DENY"
        X-XSS-Protection "1; mode=block"
        Referrer-Policy "strict-origin-when-cross-origin"
    }
}
EOF

Note: Replace headscale.yourdomain.com with your actual domain.

Step 6: Configure UFW Firewall

Set up firewall rules for secure access:

# Enable UFW and configure rules
sudo ufw --force enable
sudo ufw default deny incoming
sudo ufw default allow outgoing

# Allow HTTP/HTTPS for Caddy
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 443/udp  # HTTP/3 support

# Allow Headscale gRPC (if external access needed)
sudo ufw allow 50443/tcp

# Allow SSH (adjust port if needed)
sudo ufw allow 22/tcp

# Check status
sudo ufw status verbose

Step 7: Deploy and Initialize Headscale

Start the services and perform initial setup:

# Start the stack
docker compose up -d

# Check container status
docker compose ps

# View logs
docker compose logs -f

# Wait for services to start, then create initial user
docker compose exec headscale headscale users create admin

# Generate pre-auth key for first device
docker compose exec headscale headscale --user admin preauthkeys create --reusable --expiration 24h

Step 8: Connect Your First Device

Install and configure Tailscale client to use your Headscale server:

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

# Connect to your Headscale server
sudo tailscale up --login-server=https://headscale.yourdomain.com --authkey=YOUR_PREAUTH_KEY

# Verify connection
tailscale status

For other platforms, download clients from the official Tailscale download page and use the same --login-server parameter.

Step 9: OIDC Single Sign-On Setup

To enable OIDC authentication, configure your identity provider:

Google OAuth Setup

  1. Visit Google Cloud Console
  2. Create a new OAuth 2.0 client ID
  3. Add authorized redirect URI: https://headscale.yourdomain.com/oidc/callback
  4. Update your config.yaml with the client credentials
  5. Restart Headscale: docker compose restart headscale

Best Practices

Security Considerations

  • Regular Updates: Keep Headscale and Caddy images updated regularly
  • Backup Strategy: Implement automated encrypted backups for the SQLite database
  • Access Control: Use OIDC with domain restrictions for enhanced security
  • Monitoring: Set up log monitoring and health checks

Performance Optimization

  • Resource Allocation: Monitor memory usage and adjust container limits as needed
  • Network Segmentation: Consider using Kubernetes deployment for larger installations
  • Database Maintenance: Regularly vacuum SQLite database for optimal performance
# Database maintenance
docker compose exec headscale sqlite3 /var/lib/headscale/db.sqlite "VACUUM;"

# Check connected nodes
docker compose exec headscale headscale nodes list

# Monitor resource usage
docker stats

Conclusion

You’ve successfully deployed a self-hosted Headscale control plane with enterprise-grade features including automatic HTTPS, OIDC authentication, and containerized deployment. This setup provides the mesh networking benefits of Tailscale while maintaining complete control over your coordination server and user data.

Your Headscale deployment offers superior privacy, compliance capabilities, and cost-effectiveness compared to managed solutions. For hosting this infrastructure, consider Onidel VPS in Singapore or other regions, which provide high-performance EPYC Milan processors and NVMe storage ideal for networking applications.

To further enhance your setup, explore additional security measures like CrowdSec integration or post-quantum TLS encryption for future-proof security.

Share your love