Reference

Environment variables

Complete reference for all environment variables read by Auth Service.

All variables are validated at startup using Zod. If a required variable is missing or invalid, the process exits with an error listing every failing field.

Server

VariableRequiredDefaultDescription
PORTno3001TCP port the Fastify server binds to
HOSTno0.0.0.0Bind address
NODE_ENVnodevelopmentdevelopment, production, or test. Migrations run at startup whenever it is not development. In production, e-mail verification is required by default (the Compose file overrides this default with false, see REQUIRE_EMAIL_VERIFICATION) and a failed SMTP check at startup is fatal. The Compose stack forces production.
LOG_LEVELnodebug in development, info otherwisepino log level (trace, debug, info, warn, error, fatal)

BetterAuth (required)

VariableRequiredDefaultDescription
BETTER_AUTH_SECRETyes—Random secret used to sign sessions (min 16 chars). Generate with openssl rand -base64 32.
BETTER_AUTH_URLyes—Public base URL of Auth Service, e.g. https://auth.example.com. Used as the OIDC issuer and in email links.

Application identity

VariableRequiredDefaultDescription
APP_NAMEnoCIRCLE AuthDisplay name used as the TOTP issuer in authenticator apps and as the BetterAuth appName.
APP_LOGO_URLno—URL of a logo displayed in the UI (sidebar, login, consent). When unset, the bundled CIRCLE mark (an inline SVG that follows the theme) is used. Without APP_LOGO_DARK_URL, the UI adapts the logo to the active theme (inverts a black or white logo, adds a contrasting circle behind a coloured one; needs the image host to send CORS headers).
APP_LOGO_DARK_URLno—URL of the logo used on the dark theme. Requires APP_LOGO_URL, which is then used on the light theme (and in e-mails). Both are displayed as supplied: no inversion or background is applied.
SHOWCASEnofalseServes a public landing page at / presenting the service's features, with the instance logo. When false, / goes straight to the sign-in. The administration sign-in is at /auth in both cases; a bare /login (no OAuth parameters) leads to the landing page when SHOWCASE=true.

Database (required)

VariableRequiredDefaultDescription
DATABASE_URLyes—PostgreSQL connection string, e.g. postgres://user:pass@host:5432/dbname
POSTGRES_USERnoauthPostgres user — used by the postgres service and the DATABASE_URL of the Compose file
POSTGRES_PASSWORDnoauthPostgres password. Change it for any shared or production deployment
POSTGRES_DBnoauthDatabase name
POSTGRES_PORTno5433Host-side port published by the Postgres container

Bootstrap

VariableRequiredDefaultDescription
ADMIN_EMAILno—Email address for the superadmin created at first boot
ADMIN_PASSWORDno—Password for the superadmin (min 12 chars; in production the placeholder from .env.example and other known default passwords are refused and no superadmin is created). If either variable is absent, bootstrap is skipped.

CORS and sessions

VariableRequiredDefaultDescription
CORS_ORIGINSnohttp://localhost:5173Comma-separated list of allowed CORS origins
SESSION_DOMAINno—When set, enables cross-subdomain cookies. Set to the apex domain, e.g. example.com.
TRUST_PROXY_HOPSno0Number of trusted reverse-proxy hops in front of the service. Passed to Fastify's trustProxy so req.ip is derived from the correct entry in the X-Forwarded-For chain instead of the spoofable client-supplied value. Defaults to 0 (secure): set it to 1 when running behind a single reverse proxy, otherwise req.ip is the proxy address and per-IP rate limiting collapses into one bucket.

Redis (optional)

VariableRequiredDefaultDescription
REDIS_URLno—Shared state across instances. When set, the rate-limit buckets and the admin real-time event bus are shared through Redis (ioredis is loaded lazily); when unset, both are in-process and therefore per-instance. Compose ships a redis service behind the redis profile.

Email (SMTP)

SMTP_HOST is optional only while no e-mail flow is active. In production, e-mail verification is required by default, so the process refuses to start without SMTP_HOST unless REQUIRE_EMAIL_VERIFICATION=false and neither MAGIC_LINK_ENABLED nor EMAIL_OTP_ENABLED is on. The bundled Compose file passes REQUIRE_EMAIL_VERIFICATION=false when the variable is not set in .env, so that stack starts without SMTP until you enable it. When SMTP_HOST is unset, e-mails are dropped with a log line.

VariableRequiredDefaultDescription
SMTP_HOSTconditional—SMTP server hostname
SMTP_PORTno587SMTP port. Port 465 enables implicit TLS; all others use STARTTLS.
SMTP_USERno—SMTP authentication username. Authentication is used only when both SMTP_USER and SMTP_PASS are set
SMTP_PASSno—SMTP authentication password
SMTP_FROMnoauth-service <no-reply@localhost>From address for outgoing emails
MAIL_REPLY_TOno—Reply-To header added to every outgoing e-mail
REQUIRE_EMAIL_VERIFICATIONnotrue in production, false otherwiseRefuse to issue a session until the e-mail address is verified. The Compose file and .env.example both set false, so the Compose stack does not require verification unless you set true
MAGIC_LINK_ENABLEDnofalsePasswordless sign-in by e-mailed link, for existing accounts only. Needs SMTP and rate limits in place
EMAIL_OTP_ENABLEDnofalsePasswordless sign-in by e-mailed one-time code, for existing accounts only

Boolean flags accept true/false, 1/0, yes/no and on/off; any other value stops the startup.

Optional security features

VariableRequiredDefaultDescription
HAVEIBEENPWNED_ENABLEDnofalseReject passwords found in the Have I Been Pwned corpus (k-anonymity: only a 5-character hash prefix leaves the process). Adds an outbound call on every password write
LAST_LOGIN_METHOD_ENABLEDnotrueSet a cookie remembering the last sign-in method. The server-rendered sign-in page (see TEMPLATES_DIR) reads it to offer only the social provider a returning user last used.
DEVICE_AUTHORIZATION_ENABLEDnofalseOAuth 2.0 Device Authorization Grant (RFC 8628) and the /device approval page
CAPTCHA_ENABLEDnofalseRequire a CAPTCHA token (x-captcha-response header) on sign-up, sign-in and password reset. Needs the three variables below
CAPTCHA_PROVIDERwith CAPTCHA—cloudflare-turnstile, google-recaptcha, hcaptcha or captchafox
CAPTCHA_SECRET_KEYwith CAPTCHA—Provider secret key
CAPTCHA_SITE_KEYwith CAPTCHA—Public site key of the provider. It is not exposed to clients by the service: neither the admin SPA nor the built-in templates render a CAPTCHA widget
CAPTCHA_ENDPOINTSnosign-up, sign-in, password resetComma-separated paths relative to /api/auth (for example /sign-up/email)

A half-configured CAPTCHA stops the startup rather than locking sign-in out. Enable it only once your clients submit the token: the service ships no CAPTCHA widget yet, so with CAPTCHA_ENABLED=true the protected endpoints reject every request that lacks the x-captcha-response header.

Templates

VariableRequiredDefaultDescription
TEMPLATES_DIRno—Absolute path to a directory containing custom templates for the server-rendered login, register, verify-email, email-verified, select-org, two-factor, device and error pages (plus the invitation and billing pages) and for the transactional emails (emails/*.eml). When unset, the pages listed first are served by the admin SPA and every e-mail uses the built-in template. Only the files you override are needed: built-in templates ship in the image and are used as a fallback. The bundled docker-compose.yml passes the variable through and mounts ./templates at /templates (read-only): set TEMPLATES_DIR=/templates to use it, or mount your own directory. See Custom templates.

OAuth 2.1 resource audiences

VariableRequiredDefaultDescription
(removed)——Replaced by per-application protected resources (oauth_resource). A client obtains a JWT access token by sending resource=<application url>.

Stripe billing

VariableRequiredDefaultDescription
STRIPE_SECRET_KEYno—Stripe secret API key (sk_live_… or sk_test_…). When absent, Stripe features are disabled.
STRIPE_WEBHOOK_SECRETno—Stripe webhook signing secret (whsec_…). Required for the webhook endpoint to validate Stripe events; without it the endpoint answers 503 (fail-closed).
WALLET_TOPUP_MIN_EURno5Smallest online credit top-up, in whole euros
WALLET_TOPUP_MAX_EURno500Largest online credit top-up, in whole euros. Must be at least the minimum
WALLET_USD_TO_EUR_RATEno—Euros per US dollar (decimal, e.g. 0.92) used to price LLM usage. Starting value only: once a superadmin saves the pricing in the admin interface, the saved values apply. Deliberately without default: with neither, dollar-priced usage answers 503 WAL_007
WALLET_MARGIN_PERCENTno0Starting value of the percentage added to the provider cost (0–1000); see WALLET_USD_TO_EUR_RATE

Both Stripe values are environment-only and are never returned by the admin API. Audit and repair with pnpm billing:reconcile --check (read-only) and pnpm billing:reconcile; --recreate-missing recreates a confirmed-deleted subscription, and --cancel-superseded cancels a replaced one only when the replacement is live. See the Stripe integration guide.

Compatibility with another identity provider

One profile for the whole instance; see Compatibility profiles. The profile only supplies defaults: every variable below overrides it, and custom starts from the plain defaults. An invalid value stops the startup.

VariableRequiredDefaultDescription
COMPAT_PROFILEnodefaultdefault, okta, auth0, keycloak or custom
TOKEN_ENDPOINT_AUTH_METHODnofrom profile (basic; post for Auth0)How confidential applications authenticate at the token endpoint: basic (HTTP Basic) or post (client_secret in the body). A client accepts one method only. Applied to new applications and, at startup, to existing non-system ones
JWT_PRIMARY_ALGnofrom profile (EdDSA; RS256 for Okta, Auth0, Keycloak)Signature of access and ID tokens. Choose it before the first start: changing it later creates a key of the new algorithm at startup and logs a warning
DEFAULT_AUDIENCEnofrom profile (true for Okta, Keycloak)Issue a JWT even when the request carries no resource, using the application's url
AUDIENCE_PARAMnofrom profile (true for Auth0)Accept audience as an alias of resource
CLAIM_MAPPINGSnofrom profileJSON array of { "claim", "source", "scope"? }, source being roles, permissions, scope, email or clientId. Replaces the profile's list
COMPAT_ALIASESnofrom profileComma-separated: okta, auth0. Endpoint paths of that provider answered by this service

LiteLLM gateway (optional)

VariableRequiredDefaultDescription
LITELLM_URLno—Base URL of the LiteLLM administration API, used to keep one gateway team per subscription plan of every application with LiteLLM support. The Compose file sets http://litellm:4000
LITELLM_PROVISIONER_KEY_FILEno/run/litellm-provisioner/keyFile holding the restricted virtual key written by litellm-provision

See LiteLLM gateway for the full setup.

Social login providers

Each provider requires both CLIENT_ID and CLIENT_SECRET to be set. If either is missing, the provider is disabled.

VariableRequiredDefaultDescription
GOOGLE_CLIENT_IDno—Google OAuth app client ID
GOOGLE_CLIENT_SECRETno—Google OAuth app client secret
GITHUB_CLIENT_IDno—GitHub OAuth app client ID
GITHUB_CLIENT_SECRETno—GitHub OAuth app client secret
LINKEDIN_CLIENT_IDno—LinkedIn OAuth app client ID
LINKEDIN_CLIENT_SECRETno—LinkedIn OAuth app client secret
MICROSOFT_CLIENT_IDno—Microsoft OAuth app client ID
MICROSOFT_CLIENT_SECRETno—Microsoft OAuth app client secret
APPLE_CLIENT_IDno—Apple OAuth app client ID
APPLE_CLIENT_SECRETno—Apple OAuth app client secret

Every provider key is validated at startup, but only Google and GitHub are wired into BetterAuth today. The LinkedIn, Microsoft and Apple variables are accepted and shown in the admin configuration page, yet they do not enable a sign-in button.

Copyright © 2026