Environment variables are configured in the .env file at the root of the project. These variables control domain configuration, database credentials, and service settings.
Environment File
Create your environment file from the example:
Then edit .env with your configuration values.
Never commit .env to version control. It contains sensitive credentials.
Core Variables
Domain Configuration
The domain name for your Headscale server. Development : localhost or headscale.localProduction : Your actual domain (e.g., headscale.example.com)HEADSCALE_DOMAIN = headscale.example.com
This must match your DNS configuration and SSL certificate.
PostgreSQL Database
POSTGRES_DB
string
default: "headscale"
PostgreSQL database name. Must match database.postgres.name in config/config.yaml.
POSTGRES_USER
string
default: "headscale"
PostgreSQL username for Headscale. Must match database.postgres.user in config/config.yaml.
PostgreSQL password. POSTGRES_PASSWORD = changeme_to_secure_password
Critical : This password must match database.postgres.pass in config/config.yaml.Generate a secure password:
Timezone
Timezone for container logs and timestamps. Common values :
UTC (recommended for servers)
America/New_York
Europe/London
Asia/Tokyo
See full timezone list
Headplane Configuration
Headplane provides a web UI for managing Headscale. These variables configure authentication and security.
Headscale API key for Headplane authentication. HEADPLANE_API_KEY = your_headscale_api_key_here
Generate API key :docker exec headscale headscale apikeys create --expiration 999d
Use a long expiration (999d) for stable deployments.
Secret key for encrypting Headplane session cookies. HEADPLANE_COOKIE_SECRET = your_random_cookie_secret_here
Generate secure secret :Keep this secret secure. Changing it will invalidate all user sessions.
Complete .env Example
# Headscale Domain Configuration
# Change this to your actual domain
HEADSCALE_DOMAIN = headscale.example.com
# PostgreSQL Database Configuration
POSTGRES_DB = headscale
POSTGRES_USER = headscale
POSTGRES_PASSWORD = changeme_to_secure_password
# Timezone
TZ = UTC
# Headplane Configuration
# Generate API key: docker exec headscale headscale apikeys create
HEADPLANE_API_KEY = your_headscale_api_key_here
# Generate cookie secret: openssl rand -base64 24
HEADPLANE_COOKIE_SECRET = your_random_cookie_secret_here
Variable Usage in Docker Compose
Environment variables are referenced in docker-compose.yml:
services :
postgres :
environment :
POSTGRES_DB : ${POSTGRES_DB:-headscale}
POSTGRES_USER : ${POSTGRES_USER:-headscale}
POSTGRES_PASSWORD : ${POSTGRES_PASSWORD:-changeme}
headscale :
environment :
- TZ=${TZ:-UTC}
nginx :
environment :
- HEADSCALE_DOMAIN=${HEADSCALE_DOMAIN:-headscale.example.com}
headplane :
environment :
- TZ=${TZ:-UTC}
- HEADPLANE_API_KEY=${HEADPLANE_API_KEY}
- HEADPLANE_COOKIE_SECRET=${HEADPLANE_COOKIE_SECRET}
Default Values
The syntax ${VARIABLE:-default} provides fallback values:
${POSTGRES_DB:-headscale}
This uses:
$POSTGRES_DB if set in .env
headscale as fallback if not set
Security Best Practices
Use Strong Passwords
Generate cryptographically secure passwords: # PostgreSQL password (32 characters)
openssl rand -base64 32
# Headplane cookie secret (24 characters)
openssl rand -base64 24
Never Commit .env
Ensure .env is in .gitignore: .env
.env.local
.env.*.local
Restrict File Permissions
Limit access to environment file:
Rotate Credentials Regularly
Update passwords and API keys periodically: # Update PostgreSQL password
docker exec headscale-db psql -U headscale -c "ALTER USER headscale PASSWORD 'new_password';"
# Create new API key
docker exec headscale headscale apikeys create --expiration 365d
# Revoke old keys
docker exec headscale headscale apikeys list
docker exec headscale headscale apikeys expire < key-i d >
Verification
Check Environment Variables
Verify loaded variables:
# View all variables
docker compose config
# Check specific service
docker compose exec postgres env | grep POSTGRES
Test Database Connection
Verify PostgreSQL credentials:
docker exec -it headscale-db psql -U headscale -d headscale -c "SELECT 1;"
Expected output:
?column?
----------
1
(1 row)
Test Headplane Access
Verify Headplane configuration:
# Check Headplane is running
curl -I http://localhost:3001
# View Headplane logs
docker compose logs headplane
Troubleshooting
Variables Not Loading
Symptoms : Services use default values instead of .env values
Solutions :
Verify .env file exists in project root:
Check file format (no spaces around =):
# Correct
POSTGRES_PASSWORD = mypassword
# Incorrect
POSTGRES_PASSWORD = mypassword
Restart Docker Compose:
docker compose down
docker compose up -d
Password Mismatch
Symptoms : Headscale fails to connect to database
Error : password authentication failed for user "headscale"
Solution : Ensure password matches in both places:
.env file: POSTGRES_PASSWORD=value
config/config.yaml: database.postgres.pass: value
# Verify environment variable
echo $POSTGRES_PASSWORD
# Verify config.yaml
grep "pass:" config/config.yaml
Invalid API Key
Symptoms : Headplane shows authentication errors
Solution : Generate new API key:
# List existing keys
docker exec headscale headscale apikeys list
# Create new key
docker exec headscale headscale apikeys create --expiration 999d
# Update .env with new key
# Then restart
docker compose restart headplane
Domain Not Resolving
Symptoms : Cannot access via domain name
Solution : Verify DNS and domain configuration:
# Test DNS resolution
dig $HEADSCALE_DOMAIN
nslookup $HEADSCALE_DOMAIN
# Verify domain in nginx
docker compose exec nginx env | grep DOMAIN
Migration and Updates
Updating Environment Variables
Edit .env file with new values
Update matching config.yaml if needed (for database password)
Restart affected services :
# Restart all services
docker compose down
docker compose up -d
# Or restart specific service
docker compose restart postgres
docker compose restart headscale
Backing Up Configuration
Include .env.example in backups, but never .env:
# Backup configuration template
tar -czf config-backup.tar.gz .env.example config/ docker-compose.yml
Development vs Production
Development Settings
HEADSCALE_DOMAIN = localhost
POSTGRES_DB = headscale
POSTGRES_USER = headscale
POSTGRES_PASSWORD = dev_password
TZ = UTC
HEADPLANE_API_KEY = dev_api_key
HEADPLANE_COOKIE_SECRET = dev_cookie_secret
Production Settings
HEADSCALE_DOMAIN = headscale.example.com
POSTGRES_DB = headscale
POSTGRES_USER = headscale
POSTGRES_PASSWORD =< strong-random-password >
TZ = UTC
HEADPLANE_API_KEY =< long-lived-api-key >
HEADPLANE_COOKIE_SECRET =< secure-random-secret >
Production settings require strong, randomly generated credentials.
Additional Resources
Database Configuration Detailed PostgreSQL setup guide
Security best practices Security hardening recommendations