Skip to content

Configuration

Every setting lives in an environment variable using Nuxt's runtime-config convention:

  • NUXT_<KEY> — server-only, never sent to the browser
  • NUXT_PUBLIC_<KEY> — exposed to the client

No rebuild is needed to change config — the app reads these at startup.

Configurator in the admin UI

Logged-in admins can view and export the running instance's env-var mapping at /admin/configurator — pre-filled values, one-click switch between docker-compose.yml, .env, and shell export formats, secrets redacted. See Admin panel → Configurator.

Required

VariableDescription
NUXT_DATABASE_URLPostgreSQL: postgresql://… — SQLite: sqlite:./data.db or :memory:
NUXT_ISSUEROIDC issuer URL, must end with /oidc
NUXT_APP_URLBrowser-reachable base URL
NUXT_OIDC_CLIENTS_JSONJSON array of OIDC client definitions (schema)
NUXT_RP_IDWebAuthn relying party domain (no scheme, no port)
NUXT_RP_NAMEWebAuthn RP display name
NUXT_RP_ORIGINWebAuthn origin URL, full scheme + port

Optional — core

VariableDefaultDescription
NUXT_JWT_SECRETauto≥ 32 chars, HMAC for session JWTs. Auto-generated and persisted to data/jwt-secret.txt on first start.
NUXT_TOTP_ENCRYPTION_KEYautoEncrypts authenticator-app (TOTP) secrets at rest. Auto-generated to data/totp-encryption-key.txt if unset; back it up with the data dir. Rotating it makes existing TOTP enrolments undecryptable (users must re-enrol).
NUXT_JWT_PRIVATE_KEYautoRSA 2048 PKCS8 PEM. Auto-generated and persisted to data/jwt-private-key.pem. \n-escape for .env.
NUXT_JWT_PRIVATE_KEY_PREVIOUS(empty)Retired signing key kept in the JWKS (verify-only) during a no-downtime key rotation. Same PEM format. Empty = single-key.
NUXT_TRUST_PROXYfalseSet true behind a reverse proxy that terminates TLS and sets X-Forwarded-*.
NUXT_COOKIE_SECUREautoDefaults to true when NODE_ENV=production. Set false over plain HTTP for local dev.
NUXT_REGISTRATION_ENABLEDtrueSet false to disable new user signups.
NUXT_AUTO_ADMIN_FIRST_USERtrueFirst registered user is promoted to admin.
NUXT_TWO_FACTOR_ENABLEDfalseEnables email-code 2FA after password login. Passkeys and authenticator-app (TOTP) work as 2FA too. TOTP is per-user opt-in and needs no env var (it works even with this off / no SMTP).
NUXT_REQUIRE_TWO_FACTORfalseForce every account to have a second factor. A user with no usable factor is corralled to /setup-2fa (hard server-enforced gate) until they enrol TOTP or a passkey. Any factor satisfies it — including the emailed code — so forced enrolment only triggers when email-2FA isn't available. See Security → Mandatory two-factor.
NUXT_REQUIRE_TWO_FACTOR_SCOPEallWho the mandatory-2FA gate applies to: all or admins (only admin accounts). Ignored when require-2FA is off.
NUXT_REQUIRE_TWO_FACTOR_GRACE_DAYS0New-account grace (days) before the gate applies, measured from account creation. 0 = gate immediately. Accounts older than N days are gated regardless (no bypass window when the policy is enabled).
NUXT_SUSPICIOUS_LOGIN_ALERTSfalseEmail the user a "new sign-in" alert on a login from a new IP or device. Opt-in; requires SMTP. IP accuracy behind a proxy needs NUXT_TRUST_PROXY. See Security → New-sign-in alerts.
NUXT_SUSPICIOUS_LOGIN_STEP_UPfalseForce an email-code challenge before granting the session when a login is flagged suspicious (new IP/device). Opt-in; requires SMTP. Independent of the alert. See Security → New-sign-in alerts.
NUXT_APP_ROLES_REQUIRE_GRANTfalseRestrict the app-roles write API (PUT /api/apps/roles): only write for users who already have an OIDC grant with the calling client (else 404, uniform with an unknown user). See OIDC → App-managed roles.
NUXT_OIDC_RESOURCE_AUDIENCE(empty)Comma-separated audience URIs (RFC 8707). Empty = all access tokens stay opaque. When set, a client requesting resource=<audience> gets a JWT access token. See OIDC → JWT access tokens.
NUXT_OIDC_RESOURCE_SERVERS_JSON(empty)Richer JWT-AT resource config: JSON array of { audience, scopes?, accessTokenTTL? } giving a resource its own scope set and/or shorter token TTL (seconds). Merged with (overriding, per audience) NUXT_OIDC_RESOURCE_AUDIENCE. See OIDC → JWT access tokens.
NUXT_BACKCHANNEL_LOGOUT_RETRY_DELAYS_MS2000,15000Comma-separated backoff delays (ms) before each back-channel-logout re-attempt after the inline first attempt. Empty = single attempt. Bounded + in-memory (lost on restart).
NUXT_LOG_LEVELinfopino level: fatal error warn info debug trace.

SMTP (optional — email features degrade when absent)

If NUXT_SMTP_HOST is empty, email verification and 2FA are disabled, users are auto-verified on registration, and password reset via email is unavailable. Useful for local dev without Mailcatcher.

VariableExample
NUXT_SMTP_HOSTsmtp.sendgrid.net / mailcatcher
NUXT_SMTP_PORT587 / 1025
NUXT_SMTP_SECUREtrue for port 465
NUXT_SMTP_USERapikey
NUXT_SMTP_PASS<api-key>
NUXT_SMTP_FROMSchleuse <noreply@…>
NUXT_SMTP_REQUIRE_TLSauto (default) / true / false
NUXT_SMTP_TLS_REJECT_UNAUTHORIZEDtrue (default)
NUXT_EMAIL_RETRY_DELAYS_MS30000,120000 (default)

Transport security

NUXT_SMTP_REQUIRE_TLS decides what happens on a non-465 port:

ValueBehaviour
auto (default)Upgrade to STARTTLS whenever the relay advertises it. If the relay doesn't offer it — or rejects the command — delivery continues unencrypted. Never blocks mail.
trueRequire STARTTLS and a valid certificate. No upgrade means no delivery.
falseNever attempt STARTTLS, even when advertised. Legacy behaviour; only for a relay that mis-advertises it.

Ignored when NUXT_SMTP_SECURE=true — implicit TLS encrypts the session from the first byte.

Certificates are validated only where TLS is mandatory (true, or implicit TLS). Under auto they deliberately are not: nodemailer cannot fall back from a failed TLS handshake, so checking there would stop mail for every self-signed relay rather than downgrading. That is the standard opportunistic-security posture (RFC 7435) — encryption without authentication still beats cleartext, and the admin page labels it honestly as certificate not verified.

NUXT_SMTP_TLS_REJECT_UNAUTHORIZED=false relaxes validation in mandatory mode, for a self-hosted relay with a self-signed certificate. The hop stays encrypted against passive capture but is no longer proof against an active man-in-the-middle. Only a literal false disables it — a typo keeps validation on.

Check what your deployment actually negotiated on the admin Email & SMTP page; it reports the measured result, not the configured intent.

NUXT_EMAIL_RETRY_DELAYS_MS controls automatic retry of verification, password-reset and resend emails: a comma-separated list of backoff delays (in milliseconds) before each re-attempt. The default 30000,120000 means three attempts — immediate, then +30 s, then +2 min. Set it to an empty string to disable retry (a single attempt). Retries are in-memory and not durable: pending retries are lost if the process restarts. Time-sensitive 2FA login codes are never retried — a failed code send surfaces immediately so the user can request a fresh one.

Password policy

VariableDefaultNotes
NUXT_PASSWORD_POLICYstandardPresets: simple (6+ chars), standard (8+ with upper/lower/number/special), strong (12+ with all)
NUXT_PASSWORD_MIN_LENGTHfrom presetOverride individual rule
NUXT_PASSWORD_REQUIRE_UPPERCASEfrom preset
NUXT_PASSWORD_REQUIRE_LOWERCASEfrom preset
NUXT_PASSWORD_REQUIRE_NUMBERfrom preset
NUXT_PASSWORD_REQUIRE_SPECIALfrom preset
NUXT_PASSWORD_HISTORY_COUNT0Prevent reuse of the last N passwords on change/reset. 0 disables it. See below.

Password reuse prevention

Set NUXT_PASSWORD_HISTORY_COUNT=N (an integer ≥ 1) to reject a password change or reset that reuses one of the user's last N passwords — the current password counts as the most recent of the N. For example, 3 blocks the current password and the two before it.

  • 0 disables the check entirely, including allowing a change back to the current password.
  • Default coupling: when NUXT_PASSWORD_HISTORY_COUNT is left unset, the strong password preset turns reuse prevention on (count 3) automatically; other presets default to 0. Setting the variable explicitly (including 0) always wins.
  • Only hashed retired passwords are stored (in a password_history table, argon2id like the live credential); they are deleted with the account and are never part of the GDPR data export.
  • Increasing N takes effect going forward; previously pruned history is not recovered.

Rate limiting

Format: maxAttempts/windowMinutes. In-memory per-IP counters (resets on restart — acceptable for a single-instance sidecar).

VariableDefault
NUXT_RATE_LIMIT_LOGIN10/15
NUXT_RATE_LIMIT_REGISTER10/60
NUXT_RATE_LIMIT_PASSWORD_RESET5/60

Observability (metrics)

VariableDefaultNotes
NUXT_METRICS_TOKEN(unset)Bearer token that enables GET /metrics (Prometheus). Unset → the endpoint returns 404 (feature off). When set it must be ≥ 32 characters (startup fails otherwise), and every scrape must send Authorization: Bearer <token> (constant-time compared; never logged). Treated as a secret — the Configurator never echoes its value. Rotate by changing the env var and restarting.

Provisioning API

VariableDefaultNotes
NUXT_ADMIN_API_TOKENS_JSON(unset)JSON array of bearer tokens for the provisioning API: [{"id":"migration-importer","token":"…"}]. Unset or [] → every /api/provisioning/** route returns 404 (feature off). Each token must be ≥ 32 characters and each id a lowercase slug ([a-z0-9_-]); duplicate ids or tokens fail startup. The id is not secret — it labels the caller in the audit log. Treated as a secret: the Configurator never echoes the value.

The id exists so rotation needs no coordinated restart: add a second entry, deploy, move your importer across, then remove the first — both are accepted in between.

WARNING

This token can create sign-in-capable accounts. Keep the endpoints off the public internet where you can, and remove the token once a migration is finished. It cannot set is_admin. See Migrating users in.

Metrics are in-memory counters/histograms (reset on restart): login success/failure, registrations, 2FA challenges, rate-limit hits, an HTTP request-duration histogram, and standard Node/process metrics. See Deployment for the scrape config and exposure guidance.

Localization

See Multilingual for the full story.

VariableDefaultDescription
NUXT_DEFAULT_LOCALEenBCP-47 tag. Must be listed in NUXT_SUPPORTED_LOCALES.
NUXT_SUPPORTED_LOCALESenComma-separated allow-list (e.g. en,de,fr,it). The UI ships en, de, fr, it; any other tag is email-only.

Theme

All optional — defaults produce a dark neon-modern look. Variables use NUXT_PUBLIC_THEME_* and are exposed to the client. See the THEMES.md ready-to-use presets.

VariableDefaultNotes
NUXT_PUBLIC_THEME_NAMESchleuseApp name shown in the UI and page titles.
NUXT_PUBLIC_THEME_PRIMARY_COLOR#06d6a0Primary hex colour; the full 50–950 palette is auto-derived via HSL.
NUXT_PUBLIC_THEME_BACKGROUND_COLOR#0a0e17Page background hex colour.
NUXT_PUBLIC_THEME_TEXT_COLOR#f1f5f9Base text hex colour.
NUXT_PUBLIC_THEME_BORDER_RADIUS12pxGlobal border radius (CSS length).
NUXT_PUBLIC_THEME_DARK_MODEtrueWhether the theme is dark-first (true | false).
NUXT_PUBLIC_THEME_SURFACE_COLOR(auto)Card / surface background. Auto-derived when empty.
NUXT_PUBLIC_THEME_SURFACE_SECONDARY_COLOR(auto)Secondary surface background. Auto-derived when empty.
NUXT_PUBLIC_THEME_BORDER_COLOR(auto)Border colour. Auto-derived when empty.
NUXT_PUBLIC_THEME_MUTED_COLOR(auto)Muted / secondary text colour. Auto-derived when empty.
NUXT_PUBLIC_THEME_LOGO_URL(empty)URL or path to a logo shown on the login pages and in the admin sidebar. Empty falls back to the bundled Schleuse mark — see the note below.
NUXT_PUBLIC_THEME_BACKGROUND_IMAGE(empty)URL or path to a background image for the auth pages.
NUXT_PUBLIC_THEME_BACKGROUND_LAYOUTsplitAuth-page layout with a background image: split (form left, image right) or full.

Empty does not mean "no logo"

Leaving NUXT_PUBLIC_THEME_LOGO_URL empty shows the bundled Schleuse mark on the login pages, in the admin sidebar and as the browser-tab icon.

The one exception is deliberate: if you also set NUXT_PUBLIC_THEME_NAME to something other than Schleuse, the mark is not shown and your name is rendered as text instead. Schleuse will not put its own logo above your brand. To show your own mark, set NUXT_PUBLIC_THEME_LOGO_URL.

Email is separate — the mail header uses this variable only when you set it explicitly, and it needs an absolute URL to a PNG or JPEG. See Email templates.

Public UI flags

Also NUXT_PUBLIC_* (exposed to the client), but not part of the colour theme:

VariableDefaultNotes
NUXT_PUBLIC_CUSTOM_DISPLAY_NAMEfalseAllow users to set a custom display name. When false, it is auto-composed from first + last name.
NUXT_PUBLIC_HOME_REDIRECT_URL(empty)When set, visitors to / are redirected here (internal path or external URL).
NUXT_PUBLIC_PASSWORD_HINT(empty)Override the auto-generated password-complexity hint shown under password fields.

JWT keys

Both NUXT_JWT_SECRET and NUXT_JWT_PRIVATE_KEY are auto-generated on first start when not set. Generated values are persisted to the data/ directory:

FileContentsPurpose
data/jwt-secret.txt48-byte random base64urlHMAC for session JWTs
data/jwt-private-key.pemRSA 2048 PKCS8 PEMOIDC ID-token signing
data/totp-encryption-key.txt48-byte random base64urlEncrypts TOTP secrets at rest

Files are created with mode 0600. Mount /app/data as a Docker volume to persist keys across container restarts.

Priority: ENV variable > persisted file > generate new.

To generate keys manually:

bash
# JWT secret (≥ 32 chars)
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"

# RSA private key (newlines escaped for .env)
node -e "
  const {generateKeyPairSync} = require('crypto');
  const {privateKey} = generateKeyPairSync('rsa', {modulusLength: 2048});
  const pem = privateKey.export({type:'pkcs8',format:'pem'});
  console.log(pem.replace(/\n/g,'\\\\n'));
"

Startup validation

The app validates the environment once at startup and refuses to boot on contradictory configuration — so operator typos surface immediately in the logs instead of as a mysterious outage months later. Fatal checks (the process exits with a clear level: fatal log naming the variable):

  • NUXT_ISSUER must end with /oidc — the OIDC endpoints (discovery, authorize, token, jwks) are mounted there.
  • NUXT_COOKIE_SECURE=true together with an http:// NUXT_APP_URL — secure cookies are never sent over plain HTTP, so no session would survive. Use HTTPS, or set NUXT_COOKIE_SECURE=false for local HTTP.

Non-fatal (logged as level: warn, app still starts):

  • NUXT_APP_URL and NUXT_RP_ORIGIN resolving to different origins — WebAuthn rejects assertions whose origin doesn't match the relying-party origin. Make them match unless the split is intentional.

These are in addition to the per-variable validation (URL shape, required values, secret length, etc.). If the container exits on boot, check the startup logs for a fatal line.

A small but polished sidecar identity provider.