A production-ready Docker Compose setup for running your own Headscale server - an open-source, self-hosted implementation of the Tailscale control server.
All services are running and operational:
- β Headscale v0.27.0 - Running with SQLite database
- β Headplane Web GUI - Accessible at http://localhost:3001/admin/
- β nginx Reverse Proxy - HTTP proxy on port 8000
- β Health Check - Passing at http://localhost:8000/health
- β API Key - Generated and configured
- β ACL Policies - Tag-based security configured
- β Helper Scripts - Ready to use
Ready to connect devices!
Don't want to use command line?
π See QUICK_START_GUI.md for step-by-step GUI guide:
- Open Headplane web interface β Generate pre-auth key
- Download Tailscale app on your device
- Configure custom server in the app
- Connect with the key
- Done! No terminal needed! π
Perfect for: Windows users, Mac users, mobile devices, anyone who prefers graphical interfaces
- Headscale v0.27.0 - Pinned version with SQLite database
- Headplane Web GUI - Modern web interface for management
- nginx Reverse Proxy - HTTP/HTTPS support
- Best Practices - Security-focused configuration with ACL policies
- Tag-Based ACLs - Organized network access control
- Helper Scripts - Easy CLI management
- Docker and Docker Compose installed
- For local development: Create docker-compose.override.yml from example
- For production: A domain name with DNS pointing to your server and ports 80/443 open
- Recommended: Lefthook for Git hooks (prevents committing secrets)
See LEFTHOOK.md for details
# macOS brew install lefthook # After clone lefthook install
Once running, you can access:
- Headscale API: http://localhost:8000
- π¨ Headplane Web GUI: http://localhost:3001/admin/ β Use this to manage everything!
- Health Check: http://localhost:8000/health
- Metrics: http://localhost:8080 (direct to container)
Generate API Key: docker exec headscale headscale apikeys create
See GUI_SETUP.md for complete guide on:
- Using Headplane web interface for server management
- Using Tailscale desktop apps (Windows, Mac, Linux)
- Using Tailscale mobile apps (iOS, Android)
- No command-line needed!
This setup is ready to run locally on http://localhost:8000
docker compose up -dCheck logs:
docker compose logs -fVerify health:
curl http://localhost:8000/health
# Should return: {"status":"pass"}Open your browser to:
http://localhost:3001/admin/
Login with API Key:
Generate an API key:
docker exec headscale headscale apikeys create --expiration 999dCopy the key and paste it into the Headplane login page.
docker exec headscale headscale users create myuserOr use the helper script:
./scripts/headscale.sh users create myuserCreate a reusable pre-auth key for connecting devices:
docker exec headscale headscale preauthkeys create --user 1 --reusable --expiration 24hOr with helper script:
./scripts/headscale.sh keys create myuser --reusable --expiration 24hSave this key - you'll need it to connect devices.
Generate a pre-auth key:
docker exec headscale headscale preauthkeys create --user 1 --reusable --expiration 24hConnect any device:
# Linux/macOS
sudo tailscale up --login-server http://localhost:8000 --authkey YOUR_KEY --accept-routes
# Windows (PowerShell as Admin)
tailscale up --login-server http://localhost:8000 --authkey YOUR_KEY --accept-routesFor easier setup, use the pre-made configuration files in tailscale-configs/:
- Linux (systemd): Automated setup script
- macOS: LaunchDaemon for auto-start
- Windows: PowerShell script
- Docker: Docker Compose sidecar pattern
See tailscale-configs/README.md for complete documentation.
Quick start:
cd tailscale-configs
# Choose your platform and follow the README- Download Tailscale from https://tailscale.com/download
- Install and open Tailscale
- Open CMD as Administrator and run:
tailscale up --login-server https://headscale.yourdomain.com --authkey YOUR_PREAUTH_KEYAdd this to any Docker Compose service:
services:
myapp:
image: myapp:latest
network_mode: "service:tailscale"
depends_on:
- tailscale
tailscale:
image: tailscale/tailscale:latest
hostname: myapp-container
environment:
- TS_AUTHKEY=YOUR_PREAUTH_KEY
- TS_STATE_DIR=/var/lib/tailscale
- TS_LOGIN_SERVER=https://headscale.yourdomain.com
volumes:
- tailscale-data:/var/lib/tailscale
- /dev/net/tun:/dev/net/tun
cap_add:
- NET_ADMIN
- SYS_MODULE
restart: unless-stopped
volumes:
tailscale-data:List all users:
docker exec headscale headscale users listCreate a new user:
docker exec headscale headscale users create USERNAMEDelete a user:
docker exec headscale headscale users destroy USERNAMEList all nodes:
docker exec headscale headscale nodes listRegister a node (if using manual registration):
docker exec headscale headscale nodes register --user USERNAME --key NODEKEYDelete a node:
docker exec headscale headscale nodes delete --identifier NODE_IDList all pre-auth keys:
docker exec headscale headscale preauthkeys list --user USERNAMECreate a reusable key that expires in 1 week:
docker exec headscale headscale preauthkeys create --user USERNAME --reusable --expiration 168hCreate a single-use ephemeral key:
docker exec headscale headscale preauthkeys create --user USERNAME --ephemeralExpire a pre-auth key:
docker exec headscale headscale preauthkeys expire --user USERNAME --key KEYList all routes:
docker exec headscale headscale routes listEnable a route:
docker exec headscale headscale routes enable --route-id ROUTE_IDAdvertise subnet routes (on the client):
sudo tailscale up --advertise-routes=10.0.0.0/24 --login-server https://headscale.yourdomain.comCreate an ACL policy file to control traffic between nodes:
nano config/acl.jsonExample ACL:
{
"acls": [
{
"action": "accept",
"src": ["*"],
"dst": ["*:*"]
}
]
}Update config/config.yaml:
acl_policy_path: /etc/headscale/acl.jsonRestart Headscale:
docker compose restart headscaleTo use your own DERP server, edit config/config.yaml:
derp:
server:
enabled: true
region_id: 999
region_code: "home"
region_name: "Home DERP"
stun_listen_addr: "0.0.0.0:3478"
urls: []MagicDNS is enabled by default. Configure custom DNS in config/config.yaml:
dns:
magic_dns: true
base_domain: yourdomain.net
nameservers:
global:
- 1.1.1.1
- 8.8.8.8# Stop Headscale first to ensure consistent backup
docker compose stop headscale
# Backup everything (database + configuration)
tar -czf headscale-backup-$(date +%Y%m%d).tar.gz config/ data/ headplane/
# Restart Headscale
docker compose start headscale# Stop services
docker compose down
# Restore backup
tar -xzf headscale-backup-YYYYMMDD.tar.gz
# Restart services
docker compose up -dNote: For production with PostgreSQL, see BEST_PRACTICES.md for database-specific backup procedures.
docker compose ps
docker compose logs -f headscale
docker compose logs -f headplaneIf you get a 404, make sure you're accessing the correct path:
β
http://localhost:3001/admin/ (with trailing slash)
β http://localhost:3001 (wrong - will 404)
Test Headscale health:
curl http://localhost:8000/health
# Should return: {"status":"pass"}Check if nodes can reach server:
sudo tailscale status
sudo tailscale netcheck- Check if API key is configured in
headplane/config.yaml - Verify Headscale is running:
curl http://localhost:8000/health - Check Headplane logs:
docker logs headplane --tail 50 - Ensure cookie_secret is exactly 32 characters
Check SQLite database:
# Verify database file exists
ls -lh data/db.sqlite
# Check database size
du -h data/db.sqlite
# View tables (from within container)
docker exec headscale sqlite3 /var/lib/headscale/db.sqlite ".tables"docker compose pull
docker compose up -dHeadscale exposes Prometheus metrics on port 8080 (localhost only):
curl http://localhost:8080/metricsRemove expired pre-auth keys and offline nodes:
docker exec headscale headscale nodes expire --all-offline- Use strong passwords - Change the default PostgreSQL password
- Limit pre-auth key lifetime - Use short expiration times
- Enable ACLs - Implement least-privilege access
- Regular updates - Keep Docker images updated
- Monitor logs - Check logs regularly for suspicious activity
- Backup regularly - Automate database backups
- Use Git hooks - Install Lefthook to prevent committing secrets (see LEFTHOOK.md)
Internet
|
v
nginx Reverse Proxy (HTTP on :8000)
|
v
Headscale Server (with SQLite)
|
+-- Headplane Web GUI (:3001/admin/)
.
βββ docker-compose.yml # Production-ready compose file
βββ .env # Environment variables
βββ nginx.conf # Production nginx (SSL/TLS)
βββ nginx.dev.conf # Development nginx (HTTP only)
βββ scripts/
β βββ nginx.sh # nginx management script
β βββ headscale.sh # Headscale management script
β βββ setup.sh # Initial setup script
β βββ backup.sh # Backup script
βββ config/
β βββ config.yaml # Headscale configuration (SQLite)
β βββ policy.json # ACL policies with tags
βββ headplane/
β βββ config.yaml # Headplane web GUI config
βββ docs/ # Documentation
β βββ QUICK_START_GUI.md # GUI-only quick start guide
β βββ GUI_SETUP.md # Complete GUI guide
β βββ BEST_PRACTICES.md # Production best practices
β βββ NETWORKING.md # Advanced networking guide
βββ data/ # Headscale data (SQLite DB here)
The included headscale.sh script simplifies management:
# User management
./scripts/headscale.sh users list
./scripts/headscale.sh users create username
# Pre-auth keys
./scripts/headscale.sh keys create username --reusable --expiration 24h
./scripts/headscale.sh keys list username
# Node management
./scripts/headscale.sh nodes list
./scripts/headscale.sh nodes delete <node-id>
# Routes
./scripts/headscale.sh routes list
./scripts/headscale.sh routes enable <route-id>
# View status
./scripts/headscale.sh status
./scripts/headscale.sh health
./scripts/headscale.sh logs 100This stack configuration is provided as-is under MIT license.