Skip to main content
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:
cp .env.example .env
Then edit .env with your configuration values.
Never commit .env to version control. It contains sensitive credentials.

Core Variables

Domain Configuration

HEADSCALE_DOMAIN
string
required
The domain name for your Headscale server.Development: localhost or headscale.localProduction: Your actual domain (e.g., headscale.example.com)
.env
HEADSCALE_DOMAIN=headscale.example.com
This must match your DNS configuration and SSL certificate.

PostgreSQL Database

POSTGRES_DB
string
default:"headscale"
PostgreSQL database name.
.env
POSTGRES_DB=headscale
Must match database.postgres.name in config/config.yaml.
POSTGRES_USER
string
default:"headscale"
PostgreSQL username for Headscale.
.env
POSTGRES_USER=headscale
Must match database.postgres.user in config/config.yaml.
POSTGRES_PASSWORD
string
required
PostgreSQL password.
.env
POSTGRES_PASSWORD=changeme_to_secure_password
Critical: This password must match database.postgres.pass in config/config.yaml.Generate a secure password:
openssl rand -base64 32

Timezone

TZ
string
default:"UTC"
Timezone for container logs and timestamps.
.env
TZ=UTC
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.
HEADPLANE_API_KEY
string
required
Headscale API key for Headplane authentication.
.env
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.
.env
HEADPLANE_COOKIE_SECRET=your_random_cookie_secret_here
Generate secure secret:
openssl rand -base64 24
Keep this secret secure. Changing it will invalidate all user sessions.

Complete .env Example

.env
# 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:
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:
  1. $POSTGRES_DB if set in .env
  2. headscale as fallback if not set

Security Best Practices

1

Use Strong Passwords

Generate cryptographically secure passwords:
# PostgreSQL password (32 characters)
openssl rand -base64 32

# Headplane cookie secret (24 characters)
openssl rand -base64 24
2

Never Commit .env

Ensure .env is in .gitignore:
.gitignore
.env
.env.local
.env.*.local
3

Restrict File Permissions

Limit access to environment file:
chmod 600 .env
4

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-id>

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:
  1. Verify .env file exists in project root:
    ls -la .env
    
  2. Check file format (no spaces around =):
    # Correct
    POSTGRES_PASSWORD=mypassword
    
    # Incorrect
    POSTGRES_PASSWORD = mypassword
    
  3. 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:
  1. .env file: POSTGRES_PASSWORD=value
  2. 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

  1. Edit .env file with new values
  2. Update matching config.yaml if needed (for database password)
  3. 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

.env.dev
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

.env.prod
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

Build docs developers (and LLMs) love