Environment Variables
Last Updated: 2026-09-17
Complete reference for all environment variables used to configure Porta.
See also: Quick Start for minimal setup, Deployment Guide for production guidance.
Editable Global Configuration
Porta's closed catalog contains exactly 18 database-backed editable keys. Values are native JSONB integers, except the native locale string. Defaults and inclusive validation bounds come from code; administrators can update values but cannot create, rename or delete arbitrary keys. Internal super_admin_user_id is not editable or exposed through the configuration API.
| Key | Default | Inclusive range / choices | Unit | Application mode |
|---|---|---|---|---|
access_token_ttl | 3600 | 60..86400 | seconds | restart-required |
id_token_ttl | 3600 | 60..86400 | seconds | restart-required |
refresh_token_ttl | 2592000 | 300..31536000 | seconds | restart-required |
authorization_code_ttl | 600 | 30..3600 | seconds | restart-required |
session_ttl | 86400 | 300..2592000 | seconds | restart-required |
magic_link_ttl | 900 | 60..3600 | seconds | runtime |
password_reset_ttl | 3600 | 300..86400 | seconds | runtime |
invitation_ttl | 604800 | 300..2592000 | seconds | runtime |
rate_limit_login_max | 10 | 1..100 | attempts | runtime |
rate_limit_login_window | 900 | 60..86400 | seconds | runtime |
rate_limit_magic_link_max | 5 | 1..100 | attempts | runtime |
rate_limit_magic_link_window | 900 | 60..86400 | seconds | runtime |
rate_limit_password_reset_max | 5 | 1..100 | attempts | runtime |
rate_limit_password_reset_window | 900 | 60..86400 | seconds | runtime |
max_failed_logins | 5 | 1..100 | attempts | runtime |
lockout_duration_seconds | 900 | 60..604800 | seconds | runtime |
audit_retention_days | 90 | 1..3650 | days | runtime |
default_locale | en | en only | locale | runtime |
Manage this policy with the Configuration API, porta config list|get|set, or System Configuration… in porta admin. Duration editors store exact seconds; inline help may show an equivalent whole number of minutes, hours or days.
After a successful save commits, Porta clears the local process cache; subsequent runtime reads see the saved value immediately. Other healthy server instances pick up runtime changes on their next read within the existing at-most-60-second cache lifetime. Cache expiry is checked on reads; there is no broadcast, watcher or polling worker.
The five restart-required lifetime keys above require restarting every Porta server instance. Saving them persists policy but does not reconfigure a running OIDC provider. Porta does not automatically restart servers or instantly invalidate every instance's cache. Existing absolute artifact expiries and Redis counter expiries are not rewritten. Recovery/invitation creation uses the current lifetime; current limits apply at the next decision, and lockout eligibility uses the current duration with the existing lock timestamp. Locale en is the final fallback after request, user and organization choices. Audit cleanup uses the current default unless explicitly overridden.
External Bootstrap Settings and Secrets
These settings remain external, supplied through environment variables or a secret manager. They must be available before database startup and are outside the editable catalog/configuration API. The detailed environment sections below retain their existing startup validation rules.
| External setting | Source and purpose |
|---|---|
DATABASE_URL | Environment/secret manager; PostgreSQL connection and credentials |
REDIS_URL | Environment/secret manager; Redis connection and credentials |
ISSUER_BASE_URL | Environment; public issuer/bootstrap URL |
SMTP_HOST, SMTP_PORT, SMTP_FROM | Environment; SMTP connection and sender |
SMTP_USER, SMTP_PASS | Secret manager/environment; SMTP credentials |
COOKIE_KEYS | Secret manager/environment; cookie signing key ring |
SIGNING_KEY_ENCRYPTION_KEY | Secret manager/environment; separate signing-key encryption root |
TWO_FACTOR_ENCRYPTION_KEY | Secret manager/environment; separate TOTP encryption root |
NODE_ENV, HOST, PORT, LOG_LEVEL | Environment; process bootstrap and logging |
TRUST_PROXY, ADMIN_CORS_ORIGINS | Environment; trusted-proxy and authenticated CORS policy |
| TLS certificate/private key | Reverse-proxy files/secret manager; HTTPS termination outside the config API |
Migration 030_global_configuration_catalog.sql resets canonical operational values to these native defaults, removes obsolete public keys and preserves internal rows. Its Down is a no-op; use the development reset workflow (yarn admin:env reset) rather than restoring retired public values.
Server
| Variable | Default | Required | Description |
|---|---|---|---|
NODE_ENV | development | No | Runtime mode (development, production, test). Controls log format, cookie defaults, and other behavior. |
PORT | 3000 | No | HTTP listen port. |
HOST | 0.0.0.0 | No | HTTP listen address. Use 127.0.0.1 to restrict to localhost. |
Database & Cache
| Variable | Default | Required | Description |
|---|---|---|---|
DATABASE_URL | — | Yes | PostgreSQL connection string. Example: postgresql://porta:secret@localhost:5432/porta |
REDIS_URL | — | Yes | Redis connection string. Supports optional authentication: redis://[user:password@]host:port[/db]. Examples: redis://localhost:6379, redis://:secret@redis:6379/0 |
OIDC
| Variable | Default | Required | Description |
|---|---|---|---|
ISSUER_BASE_URL | — | Yes | The public-facing URL of your Porta instance. Must match the URL users see in their browser (e.g., https://auth.example.com). OIDC tokens embed this as the iss claim — clients validate it, so it must be correct. |
COOKIE_KEYS | — | Yes | Cookie signing key(s). Must be at least 32 random characters. For key rotation, use comma-separated values with the newest key first (e.g., new-key,old-key). See Cookie Key Rotation. |
Email (SMTP)
| Variable | Default | Required | Description |
|---|---|---|---|
SMTP_HOST | — | Yes (prod) | SMTP relay hostname. Use localhost with MailHog for development. |
SMTP_PORT | 587 | No | SMTP port. Common values: 587 (STARTTLS), 465 (implicit TLS), 25 (unencrypted), 1025 (MailHog). |
SMTP_USER | — | No | SMTP authentication username. Leave empty for MailHog. |
SMTP_PASS | — | No | SMTP authentication password. Leave empty for MailHog. |
SMTP_FROM | noreply@porta.local | No | Sender email address for magic links, password resets, and invitations. |
Monitoring
| Variable | Default | Required | Description |
|---|---|---|---|
METRICS_ENABLED | false | No | Set to true to enable the Prometheus-compatible GET /metrics endpoint. When disabled (default), the endpoint returns 404. |
Logging
| Variable | Default | Required | Description |
|---|---|---|---|
LOG_LEVEL | info (prod), debug (dev) | No | Log verbosity. Values: debug, info, warn, error, silent. |
Porta uses pino for structured logging:
NODE_ENV | Format | Behavior |
|---|---|---|
development | Pretty-printed (pino-pretty) | Human-readable, colorized |
production | JSON (one line per entry) | Machine-parseable for log aggregators |
test | Silent | No log output |
Covered administrative and public-authentication requests also emit one terminal security.decision.v1 record. It contains a server-generated request ID, normalized route template, final status/outcome, and a closed decision reason. Raw paths, query strings, request bodies, credentials, cookies, email addresses, network addresses, user agents, and error details are excluded. Protected actor, tenant, resource, or source references are domain-separated keyed digests rather than raw identifiers.
Reverse Proxy
| Variable | Default | Required | Description |
|---|---|---|---|
TRUST_PROXY | true | No | true for the shipped proxy deployment. Set to false when Porta is directly exposed without a TLS-terminating reverse proxy (nginx, Traefik, Caddy, cloud load balancer, etc.). |
TRUST_PROXY_HOPS | 1 | No | Number of trusted reverse-proxy hops in front of Porta. Set it to the exact number of proxies that append to X-Forwarded-For. |
Why TRUST_PROXY Matters
Porta sets the Secure flag on authentication cookies based on the actual connection protocol (ctx.secure in Koa). In a typical production setup, a reverse proxy terminates TLS and forwards requests to Porta over plain HTTP:
Browser ──HTTPS──▶ Reverse Proxy ──HTTP──▶ Porta (port 3000)Without TRUST_PROXY=true, Porta sees only the internal HTTP connection and sets cookies without the Secure flag. Modern browsers then silently drop these cookies on HTTPS pages, causing OIDC login flows to fail — the interaction session is lost between redirects.
When TRUST_PROXY=true, Koa reads the X-Forwarded-Proto header from the proxy to determine the original protocol. This makes ctx.secure return true when the browser connected via HTTPS, so cookies are correctly flagged as Secure.
Affected features:
- CSRF tokens (login forms)
- OIDC interaction sessions (login/consent)
- Magic link sessions
Direct Exposure Requires TRUST_PROXY=false
TRUST_PROXY defaults to true because Porta ships behind a TLS-terminating reverse proxy. If you expose Porta directly, without such a proxy, you must set TRUST_PROXY=false; otherwise a client can spoof X-Forwarded-* headers and control the resolved protocol and client address.
Common Scenarios
| Setup | TRUST_PROXY | Notes |
|---|---|---|
| Direct HTTP (dev/eval) | false | Set explicitly — required when directly exposed |
| Behind nginx/Traefik/Caddy with TLS | true | Proxy must send X-Forwarded-Proto: https |
| Behind a cloud load balancer (AWS ALB, GCP LB) | true | Cloud LBs typically set X-Forwarded-Proto |
| Direct HTTPS (TLS on Porta itself) | false | Porta sees TLS directly — no proxy headers needed |
Trusted Proxy Hops
TRUST_PROXY_HOPS tells Porta how many trusted proxies sit in front of it. Koa reads the client IP from the trusted end of the X-Forwarded-For list instead of the leftmost value that a client can supply.
The default of 1 matches the common deployment of exactly one TLS-terminating proxy. Set it to 0 only when you deliberately trust the whole header, or to the exact number of chained trusted proxies.
Set the Exact Hop Count
The value must equal the real number of trusted proxies that append to X-Forwarded-For.
- Too low: every client behind the nearest proxy shares one rate-limit budget and one audit address. One client can exhaust the budget for everyone.
- Too high: the client-controlled leftmost value is used again, so a client can rotate
X-Forwarded-Forand evade IP-based rate limiting.
This setting assumes a trusted proxy always appends to the header and that Porta itself is not reachable directly. If Porta can be reached without the proxy, a client can still control the resolved address.
Security
| Variable | Default | Required | Description |
|---|---|---|---|
TWO_FACTOR_ENCRYPTION_KEY | — | Yes (prod) | AES-256-GCM key for encrypting TOTP secrets. Must be exactly 64 hexadecimal characters (32 bytes). Development/test installations may omit it. |
SIGNING_KEY_ENCRYPTION_KEY | — | Yes | AES-256-GCM key for encrypting ES256 signing key private keys at rest. Must be exactly 64 hexadecimal characters (32 bytes). Always required — Porta will not start without it. |
PORTA_SKIP_PROD_SAFETY | false | No | Emergency escape hatch to bypass production config safety checks. When true, Porta logs an ERROR instead of exiting on startup. Do not use in normal production — this is intended only for disaster recovery or migration scenarios. See Production Safety Checks. |
Production Safety Checks
When NODE_ENV=production, Porta validates your configuration at startup and exits with a clear error if any safety rule fails. This prevents accidental deployment with development-only placeholder values.
| Rule | What It Checks |
|---|---|
| R1 | COOKIE_KEYS does not contain a dev placeholder ("change-me" pattern) |
| R2 | COOKIE_KEYS is at least 32 characters |
| R3 | TWO_FACTOR_ENCRYPTION_KEY is set (required in production) |
| R4 | TWO_FACTOR_ENCRYPTION_KEY is not the development placeholder (0123456789abcdef…) |
| R5 | SIGNING_KEY_ENCRYPTION_KEY is not the development placeholder (fedcba9876543210…) |
| R6 | DATABASE_URL does not contain the dev password (porta_dev) |
| R7 | ISSUER_BASE_URL uses https:// for non-localhost hosts |
| R8 | LOG_LEVEL is not debug (prevents verbose logging in production) |
| R9 | SMTP_HOST is not localhost / 127.x.x.x (catches MailHog dev inbox) |
| R10 | The signing-key and two-factor encryption keys contain different values |
If you need to temporarily bypass the operational checks (e.g., during disaster recovery), set PORTA_SKIP_PROD_SAFETY=true. Porta will still require different signing-key and two-factor root keys because the escape hatch cannot disable cryptographic domain separation.
DANGER
PORTA_SKIP_PROD_SAFETY=true should never be used in normal production. It exists only for emergency situations where you need to start Porta with an incomplete configuration.
Generating Secrets
# Cookie signing key (random 64-char string)
node -e "console.log(require('crypto').randomBytes(32).toString('base64url'))"
# Two-factor encryption key (64 hex chars)
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
# Signing key encryption key (64 hex chars)
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
# Database password
node -e "console.log(require('crypto').randomBytes(24).toString('base64url'))"Run the two encryption-key commands separately and do not reuse either result. Production requires both external root keys to contain different values; Porta does not store them in PostgreSQL.
Startup Behavior
| Variable | Default | Required | Description |
|---|---|---|---|
PORTA_AUTO_MIGRATE | false | No | Entrypoint migration switch for development or one-time initialization. Keep false in production and run migrations explicitly during deployment. |
PORTA_WAIT_TIMEOUT | 60 | No | Maximum seconds the Docker entrypoint waits for PostgreSQL and Redis to become available before exiting. |
Test Environment
These variables are used by the test suite and should not be set in production.
| Variable | Default | Description |
|---|---|---|
TEST_DATABASE_URL | — | PostgreSQL connection string for the test database, keeping test data isolated from development data. |
TEST_REDIS_URL | — | Redis connection string (typically a different DB index) for test isolation. |
Example .env Files
Porta ships with two example files:
.env.example— Development defaults (local PostgreSQL, Redis, MailHog).env.docker— Local Docker Compose defaults (service hostnames and development services)
Copy the appropriate file and customize:
# For local development
cp .env.example .env
# For Docker Compose
cp .env.docker .env.docker.local