Porta v1.11.0
Skip to content

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.

KeyDefaultInclusive range / choicesUnitApplication mode
access_token_ttl360060..86400secondsrestart-required
id_token_ttl360060..86400secondsrestart-required
refresh_token_ttl2592000300..31536000secondsrestart-required
authorization_code_ttl60030..3600secondsrestart-required
session_ttl86400300..2592000secondsrestart-required
magic_link_ttl90060..3600secondsruntime
password_reset_ttl3600300..86400secondsruntime
invitation_ttl604800300..2592000secondsruntime
rate_limit_login_max101..100attemptsruntime
rate_limit_login_window90060..86400secondsruntime
rate_limit_magic_link_max51..100attemptsruntime
rate_limit_magic_link_window90060..86400secondsruntime
rate_limit_password_reset_max51..100attemptsruntime
rate_limit_password_reset_window90060..86400secondsruntime
max_failed_logins51..100attemptsruntime
lockout_duration_seconds90060..604800secondsruntime
audit_retention_days901..3650daysruntime
default_localeenen onlylocaleruntime

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 settingSource and purpose
DATABASE_URLEnvironment/secret manager; PostgreSQL connection and credentials
REDIS_URLEnvironment/secret manager; Redis connection and credentials
ISSUER_BASE_URLEnvironment; public issuer/bootstrap URL
SMTP_HOST, SMTP_PORT, SMTP_FROMEnvironment; SMTP connection and sender
SMTP_USER, SMTP_PASSSecret manager/environment; SMTP credentials
COOKIE_KEYSSecret manager/environment; cookie signing key ring
SIGNING_KEY_ENCRYPTION_KEYSecret manager/environment; separate signing-key encryption root
TWO_FACTOR_ENCRYPTION_KEYSecret manager/environment; separate TOTP encryption root
NODE_ENV, HOST, PORT, LOG_LEVELEnvironment; process bootstrap and logging
TRUST_PROXY, ADMIN_CORS_ORIGINSEnvironment; trusted-proxy and authenticated CORS policy
TLS certificate/private keyReverse-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 ​

VariableDefaultRequiredDescription
NODE_ENVdevelopmentNoRuntime mode (development, production, test). Controls log format, cookie defaults, and other behavior.
PORT3000NoHTTP listen port.
HOST0.0.0.0NoHTTP listen address. Use 127.0.0.1 to restrict to localhost.

Database & Cache ​

VariableDefaultRequiredDescription
DATABASE_URL—YesPostgreSQL connection string. Example: postgresql://porta:secret@localhost:5432/porta
REDIS_URL—YesRedis connection string. Supports optional authentication: redis://[user:password@]host:port[/db]. Examples: redis://localhost:6379, redis://:secret@redis:6379/0

OIDC ​

VariableDefaultRequiredDescription
ISSUER_BASE_URL—YesThe 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—YesCookie 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) ​

VariableDefaultRequiredDescription
SMTP_HOST—Yes (prod)SMTP relay hostname. Use localhost with MailHog for development.
SMTP_PORT587NoSMTP port. Common values: 587 (STARTTLS), 465 (implicit TLS), 25 (unencrypted), 1025 (MailHog).
SMTP_USER—NoSMTP authentication username. Leave empty for MailHog.
SMTP_PASS—NoSMTP authentication password. Leave empty for MailHog.
SMTP_FROMnoreply@porta.localNoSender email address for magic links, password resets, and invitations.

Monitoring ​

VariableDefaultRequiredDescription
METRICS_ENABLEDfalseNoSet to true to enable the Prometheus-compatible GET /metrics endpoint. When disabled (default), the endpoint returns 404.

Logging ​

VariableDefaultRequiredDescription
LOG_LEVELinfo (prod), debug (dev)NoLog verbosity. Values: debug, info, warn, error, silent.

Porta uses pino for structured logging:

NODE_ENVFormatBehavior
developmentPretty-printed (pino-pretty)Human-readable, colorized
productionJSON (one line per entry)Machine-parseable for log aggregators
testSilentNo 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 ​

VariableDefaultRequiredDescription
TRUST_PROXYtrueNotrue 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_HOPS1NoNumber 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 ​

SetupTRUST_PROXYNotes
Direct HTTP (dev/eval)falseSet explicitly — required when directly exposed
Behind nginx/Traefik/Caddy with TLStrueProxy must send X-Forwarded-Proto: https
Behind a cloud load balancer (AWS ALB, GCP LB)trueCloud LBs typically set X-Forwarded-Proto
Direct HTTPS (TLS on Porta itself)falsePorta 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-For and 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 ​

VariableDefaultRequiredDescription
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—YesAES-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_SAFETYfalseNoEmergency 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.

RuleWhat It Checks
R1COOKIE_KEYS does not contain a dev placeholder ("change-me" pattern)
R2COOKIE_KEYS is at least 32 characters
R3TWO_FACTOR_ENCRYPTION_KEY is set (required in production)
R4TWO_FACTOR_ENCRYPTION_KEY is not the development placeholder (0123456789abcdef…)
R5SIGNING_KEY_ENCRYPTION_KEY is not the development placeholder (fedcba9876543210…)
R6DATABASE_URL does not contain the dev password (porta_dev)
R7ISSUER_BASE_URL uses https:// for non-localhost hosts
R8LOG_LEVEL is not debug (prevents verbose logging in production)
R9SMTP_HOST is not localhost / 127.x.x.x (catches MailHog dev inbox)
R10The 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 ​

bash
# 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 ​

VariableDefaultRequiredDescription
PORTA_AUTO_MIGRATEfalseNoEntrypoint migration switch for development or one-time initialization. Keep false in production and run migrations explicitly during deployment.
PORTA_WAIT_TIMEOUT60NoMaximum 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.

VariableDefaultDescription
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:

bash
# For local development
cp .env.example .env

# For Docker Compose
cp .env.docker .env.docker.local

Released under the MIT License.