Supabase Self-Hosted Environment Variables: A Complete Guide

Master every environment variable in self-hosted Supabase. Learn secure configuration, common pitfalls, and best practices for production.

Cover Image for Supabase Self-Hosted Environment Variables: A Complete Guide

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:// in GOTRUE_EXTERNAL_*_REDIRECT_URI when your site uses HTTPS
  • Trailing slashes (or lack thereof) not matching the provider's configuration
  • Using localhost in development but forgetting to update for production

Services Can't Communicate

If services fail to start or can't reach each other, check:

  1. Docker network configuration
  2. Service names in URLs match docker-compose.yaml service names
  3. 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 .env files 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:

  1. JWT_SECRET, ANON_KEY, and SERVICE_ROLE_KEY for authentication
  2. SITE_URL and API_EXTERNAL_URL for proper routing
  3. GOTRUE_EXTERNAL_* for OAuth providers
  4. 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.


Further Reading