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
- Visit Google Cloud Console
- Create a new OAuth 2.0 client ID
- Add authorized redirect URI:
https://headscale.yourdomain.com/oidc/callback
- Update your
config.yaml
with the client credentials - 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.