If you've ever stared at the Supabase .env.example file wondering what half those variables actually do, you're not alone. The self-hosted Supabase documentation covers the basics, but doesn't always explain why each variable matters or what happens when you get it wrong. This guide breaks down every critical environment variable, explains the security implications, and shows you how to configure them properly for production.
Understanding these variables is essential whether you're deploying Supabase for the first time or debugging a broken configuration in an existing setup.
The Foundation: Authentication and API Keys
Three environment variables form the security backbone of your entire Supabase deployment. Get these wrong, and you've either broken your instance or created a security vulnerability.
JWT_SECRET
This is the master key. PostgREST, GoTrue (the auth service), and other components use JWT_SECRET to sign and verify JSON Web Tokens. Every authenticated request to your Supabase instance depends on this value.
# Generate a secure JWT_SECRET (minimum 32 characters, 64+ recommended) openssl rand -base64 64
Critical rules:
- Never use the example value from
.env.example - Must be identical across all services that verify JWTs
- Changing this value invalidates all existing sessions and requires updating the database
If you change JWT_SECRET after initial deployment, you'll need to run:
ALTER DATABASE postgres SET "app.settings.jwt_secret" TO "your_new_secret_here";
ANON_KEY and SERVICE_ROLE_KEY
These aren't random strings—they're JWTs signed with your JWT_SECRET, encoding specific roles.
ANON_KEY: A JWT with the anon role. Safe for client-side use because Row Level Security (RLS) restricts what this key can access. Your frontend applications use this key.
SERVICE_ROLE_KEY: A JWT with the service_role role that bypasses RLS entirely. Never expose this in frontend code. Use it only in server-side applications, Edge Functions, or administrative scripts.
Generate both keys using the Supabase JWT generator tool or create them manually with a JWT library using your JWT_SECRET:
// Example payload for ANON_KEY
{
"role": "anon",
"iss": "supabase",
"iat": 1704067200,
"exp": 1861833600 // ~5 years from issuance
}
The generated keys expire in 5 years by default. Mark your calendar to rotate them before expiration.
Database Configuration
POSTGRES_PASSWORD
The superuser password for your PostgreSQL database. This grants full access to everything—schemas, data, extensions, roles.
# Generate a secure database password openssl rand -base64 32
Common mistake: Using a password with special characters that aren't properly escaped in the connection string. Stick to alphanumeric characters to avoid headaches.
POSTGRES_HOST and POSTGRES_PORT
Default to db (the Docker service name) and 5432. Change these only if you're using an external PostgreSQL instance or running multiple Supabase deployments on the same host.
POSTGRES_DB
The database name, defaulting to postgres. Most deployments keep this as-is, but you can change it if you're running multiple projects on shared infrastructure.
Service URLs: Getting the Networking Right
Misconfigured URLs cause more self-hosting headaches than any other variable category. These determine how services communicate internally and how external clients reach your instance.
SITE_URL
The URL where your frontend application lives. GoTrue uses this for:
- OAuth redirect callbacks
- Email confirmation links
- Password reset links
SITE_URL=https://myapp.example.com
Critical: This must match exactly what your users see in their browser, including the protocol (https://). Mismatches cause OAuth failures and broken email links.
API_EXTERNAL_URL
The public URL for your Supabase API. Clients connect here.
API_EXTERNAL_URL=https://api.example.com
If you're using a reverse proxy like Nginx or Traefik, this should be your proxy's public URL, not the internal Docker network address.
SUPABASE_PUBLIC_URL and KONG_HTTP_PORT
SUPABASE_PUBLIC_URL is the base URL for the Supabase Studio dashboard. KONG_HTTP_PORT (default 8000) is where Kong, the API gateway, listens.
For production behind a reverse proxy:
SUPABASE_PUBLIC_URL=https://supabase.example.com KONG_HTTP_PORT=8000 # Proxy forwards external 443 to internal 8000
Authentication Provider Configuration
Setting up OAuth providers for self-hosted Supabase requires environment variables since you don't have access to the cloud dashboard's OAuth settings UI.
GOTRUE_EXTERNAL_* Variables
Each OAuth provider follows a consistent pattern:
# Google OAuth GOTRUE_EXTERNAL_GOOGLE_ENABLED=true GOTRUE_EXTERNAL_GOOGLE_CLIENT_ID=your-client-id GOTRUE_EXTERNAL_GOOGLE_SECRET=your-client-secret GOTRUE_EXTERNAL_GOOGLE_REDIRECT_URI=https://api.example.com/auth/v1/callback # GitHub OAuth GOTRUE_EXTERNAL_GITHUB_ENABLED=true GOTRUE_EXTERNAL_GITHUB_CLIENT_ID=your-client-id GOTRUE_EXTERNAL_GITHUB_SECRET=your-client-secret GOTRUE_EXTERNAL_GITHUB_REDIRECT_URI=https://api.example.com/auth/v1/callback
The redirect URI must match what you've configured in each provider's developer console. The callback path is always /auth/v1/callback appended to your API_EXTERNAL_URL.
Email Configuration (SMTP)
Without SMTP configured, users can't verify emails, reset passwords, or receive magic links.
GOTRUE_SMTP_HOST=smtp.example.com GOTRUE_SMTP_PORT=587 GOTRUE_SMTP_USER=your-smtp-username GOTRUE_SMTP_PASS=your-smtp-password [email protected] GOTRUE_SMTP_SENDER_NAME=My App
Test email delivery immediately after configuration. Silent SMTP failures are a common source of "why can't users sign up" bug reports.
Storage Configuration
Supabase Storage needs its own set of variables, especially if you're using S3-compatible storage for production.
STORAGE_BACKEND
STORAGE_BACKEND=s3 # or 'file' for local storage
Local file storage works for development but creates backup complications in production. S3-compatible storage (AWS S3, MinIO, Backblaze B2, Cloudflare R2) is recommended.
S3 Configuration
GLOBAL_S3_BUCKET=your-bucket-name GLOBAL_S3_ENDPOINT=https://s3.amazonaws.com # or your S3-compatible endpoint GLOBAL_S3_REGION=us-east-1 AWS_ACCESS_KEY_ID=your-access-key AWS_SECRET_ACCESS_KEY=your-secret-key
Security Variables You Shouldn't Ignore
SECRET_KEY_BASE
Used by Realtime and Supavisor for encrypted communication. Must be at least 64 characters.
SECRET_KEY_BASE=$(openssl rand -base64 48)
VAULT_ENC_KEY
Supavisor uses this to encrypt stored configuration. Generate it the same way:
VAULT_ENC_KEY=$(openssl rand -base64 48)
DASHBOARD_USERNAME and DASHBOARD_PASSWORD
Credentials for accessing Supabase Studio. In production, use strong values:
DASHBOARD_USERNAME=admin DASHBOARD_PASSWORD=$(openssl rand -base64 24)
Consider adding IP restrictions at the reverse proxy level for additional security.
Common Pitfalls and How to Avoid Them
Data Loss After Variable Changes
A GitHub discussion highlighted users losing all tables after modifying environment variables and restarting. The cause: the default docker-compose.yaml recreates volumes if not configured for persistence.
Solution: Ensure your database volume is properly mounted:
volumes: - ./volumes/db/data:/var/lib/postgresql/data
And verify the directory exists with correct permissions before starting containers.
OAuth Callback Mismatches
OAuth providers are strict about redirect URIs. Common mistakes:
- Using
http://inGOTRUE_EXTERNAL_*_REDIRECT_URIwhen your site uses HTTPS - Trailing slashes (or lack thereof) not matching the provider's configuration
- Using
localhostin development but forgetting to update for production
Services Can't Communicate
If services fail to start or can't reach each other, check:
- Docker network configuration
- Service names in URLs match
docker-compose.yamlservice names - Ports aren't already bound by other applications
Moving Beyond Environment Variables
The Supabase team has acknowledged that storing secrets in environment variables isn't ideal. They're working on supporting file-based secrets (using *_FILE variable suffixes) to integrate with secret managers like Docker Secrets, HashiCorp Vault, or AWS Secrets Manager.
For now, best practices include:
- Never commit
.envfiles to version control - Use a password manager to generate and store secrets
- Consider Docker Secrets or external secret managers for production
- Rotate secrets periodically, especially after team member departures
Simplifying Configuration with Supascale
Managing dozens of environment variables across multiple Supabase instances quickly becomes tedious. Supascale handles this complexity for you:
- One-click deployment: Generates secure secrets automatically during project creation
- OAuth configuration UI: Set up Google, GitHub, Discord, and other providers without editing environment files
- Secure secret storage: Your credentials are encrypted, not sitting in plain text
- Automated backups: No manual backup script configuration required
At $39.99 for unlimited projects, it eliminates the operational burden of managing these configurations yourself while keeping you in full control of your infrastructure.
Conclusion
Environment variables are the nervous system of your self-hosted Supabase deployment. Understanding what each one does—and the consequences of misconfiguration—is essential for running a stable, secure instance.
The most critical variables to get right:
- JWT_SECRET, ANON_KEY, and SERVICE_ROLE_KEY for authentication
- SITE_URL and API_EXTERNAL_URL for proper routing
- GOTRUE_EXTERNAL_* for OAuth providers
- SMTP settings for email functionality
Take time to audit your current configuration against this guide. A single misconfigured variable can mean the difference between a secure deployment and an open vulnerability.
