Skip to content

Configuration

Parako.ID uses a multi-source configuration system that loads settings from environment variables, config files, and the database. This document is the definitive reference for every aspect of the configuration lifecycle.

Configuration is assembled from three sources, merged in priority order:

┌─────────────────────────────────────┐
│ 1. Bootstrap (runtime/.env) │ ← Always loaded first
│ Infrastructure fields │ Cannot be changed at runtime
├─────────────────────────────────────┤
│ 2a. File (runtime/parako.jsonc) │ ← Development only
│ OR │ USE_FILE_CONFIG=true
│ 2b. Database (settings table) │ ← Production default
│ Single source of truth │ Managed via admin panel or API
├─────────────────────────────────────┤
│ 3. Computed Fields │ ← Auto-generated secrets
│ Derived values (OIDC issuer, │ Always recomputed on load
│ MFA settings, integration URLs) │
├─────────────────────────────────────┤
│ 4. getDefaultFullConfig() │ ← Fallback defaults
│ Lowest priority │ For any unset fields
└─────────────────────────────────────┘
PrioritySourceWhen used
1 (highest)Bootstrap (runtime/.env)Always — minimal config to start the application
2File (runtime/parako.jsonc)Development only — when USE_FILE_CONFIG=true AND DEPLOYMENT_ENVIRONMENT=development
3Database (settings table/collection)Production — stored and managed via admin panel
4Computed fieldsAlways — auto-generated secrets and derived values
5 (lowest)getDefaultFullConfig()Always — fallback for any missing fields

Bootstrap fields always win for infrastructure settings. File config is a development convenience — in production, the database is the single source of truth.

All configuration is validated against Zod schemas at startup. Invalid configuration causes the application to fail fast with descriptive error messages.


These variables are set in your runtime/.env file. Bootstrap fields cannot be changed at runtime via the admin panel — they require a restart.

Copy .env.example to runtime/.env and update the values:

Terminal window
cp .env.example runtime/.env
VariableTypeDefaultDescription
DEPLOYMENT_ENVIRONMENTenumdevelopmentdevelopment, staging, or production
DEPLOYMENT_SERVER_PORTnumber9007Server port (1–65535)
DEPLOYMENT_URLstringPublic URL of your deployment (e.g., https://auth.example.com). Used to derive oidc.issuer, discovery URLs, and integration URLs (see Computed Fields). In multi-tenant mode, tenant URLs are derived as https://{tenant}.{base_domain} (e.g., https://acme.auth.example.com). Optional — if unset, falls back to deployment.url from the database or file config (default: http://localhost:9007). Read-only in admin panel.
STORAGE_ADAPTERenumsqlitePrimary database: sqlite, mongodb, or postgresql
OIDC_STORAGE_ADAPTERenum(same as STORAGE_ADAPTER)OIDC token and session storage: sqlite, mongodb, redis, or postgresql
VariableRequired whenDefaultDescription
STORAGE_MONGODB_URISTORAGE_ADAPTER=mongodbMongoDB connection URI
STORAGE_SQLITE_PATHSTORAGE_ADAPTER=sqlite./runtime/data/parako.dbPath to SQLite database file
STORAGE_POSTGRESQL_URLSTORAGE_ADAPTER=postgresqlPostgreSQL connection URL
PG_SSL_REJECT_UNAUTHORIZEDSTORAGE_ADAPTER=postgresqltrue (production)Set to false for self-signed certificates
DATABASE_URL(Prisma CLI only)Used by Prisma CLI commands (db:push, db:migrate). Not read by the application at runtime.
VariableTypeDefaultDescription
REDIS_HOSTstringlocalhostRedis host
REDIS_PORTnumber6379Redis port
REDIS_PASSWORDstringRedis password (optional)
REDIS_DATABASEnumber0Redis database index

Redis is used for OIDC session storage (when OIDC_STORAGE_ADAPTER=redis), pub/sub for cross-process config invalidation, and caching.

VariableTypeDefaultDescription
MULTI_TENANCY_ENABLEDbooleanfalseEnable multi-tenant mode. SQLite does not support multi-tenancy — use MongoDB or PostgreSQL.
MULTI_TENANCY_EXTRACTION_PRIORITYstringheader,subdomainComma-separated tenant extraction strategies. Valid values: header, subdomain
MULTI_TENANCY_TENANT_HEADERstringx-tenant-idHTTP header for tenant identification (when header strategy is active)
MULTI_TENANCY_PROVIDER_POOL_MAX_SIZEnumber50Max OIDC provider instances in pool
MULTI_TENANCY_PROVIDER_POOL_IDLE_TTL_MSnumber1800000Provider idle timeout (30 min)
MULTI_TENANCY_PROVIDER_POOL_CLEANUP_INTERVAL_MSnumber60000Pool cleanup interval (1 min)
VariableTypeDescription
ENCRYPTION_KEYstring64-character hex key (32 bytes) for encrypting secrets at rest. Critical: back up this key — losing it means losing access to all encrypted data.

Generate with:

Terminal window
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
VariableTypeDefaultDescription
SECURITY_LOGGING_ENABLEDbooleantrueEnable application logging
SECURITY_LOGGING_LEVELenuminfotrace, debug, info, warn, error, fatal
SECURITY_LOGGING_PRETTY_PRINTbooleantruePretty-print logs (recommended for development only)
SECURITY_LOGGING_FILE_LOGGING_ENABLEDbooleantrueWrite logs to files
SECURITY_LOGGING_FILE_LOGGING_DIRECTORYstringlogsLog file directory

These secrets are referenced in file configuration via ${VAR} interpolation. When using database configuration, they are auto-generated at startup if not set (development only — production requires them to be explicitly configured).

VariableMin LengthDescription
JWT_SECRET32JWT signing secret
COOKIE_SECRET_132Primary cookie encryption secret
COOKIE_SECRET_232Secondary cookie encryption secret
HMAC_SECRET32Cross-tenant OAuth state signing
PAIRWISE_SALTSalt for OIDC pairwise subject identifiers
SMTP_PASSWORDSMTP server password

Production behavior: Missing secrets (JWT_SECRET, COOKIE_SECRET_1, COOKIE_SECRET_2, HMAC_SECRET, PAIRWISE_SALT) cause a fatal startup error in production via resolveSecret(). You must set them explicitly. In development, they are auto-generated with a warning.

VariableDescription
IPINFO_API_TOKENipinfo.io API token for IP geolocation. Can also be set via admin panel.
IPQUALITYSCORE_API_KEYIPQualityScore API key for IP reputation. Can also be set via admin panel.
VariableTypeDefaultDescription
USE_FILE_CONFIGbooleantrueLoad config from file instead of database. Only effective when DEPLOYMENT_ENVIRONMENT=development. In staging or production, this variable is ignored and the database provider is always used.

For local development, manage the full configuration in a single file instead of the database. This is the default when you first clone the repository.

Requirements: USE_FILE_CONFIG=true AND DEPLOYMENT_ENVIRONMENT=development in runtime/.env.

Terminal window
cp parako.sample.jsonc runtime/parako.jsonc

Parako.ID searches runtime/parako.jsonc then runtime/parako.json. Provide only the keys you want to override — missing keys fall back to defaults. Reference env vars with ${VAR} or ${VAR:-default} syntax:

{
"branding": { "companyName": "My Company" },
"security": {
"secrets": {
"jwt_secret": "${JWT_SECRET}",
"cookie_secrets": ["${COOKIE_SECRET_1}", "${COOKIE_SECRET_2}"],
},
},
"features": {
"social_providers": {
"google": { "client_id": "${GOOGLE_CLIENT_ID:-placeholder}" },
},
},
}
LimitationBehavior
Read-only at runtimeChanges require restarting the dev server
Production-disabledIgnored when DEPLOYMENT_ENVIRONMENT is staging or production
No version historyNo rollback capability
No admin panel editingAdmin panel shows a warning banner; changes do not persist

In production, configuration is stored in the database settings table (or collection, when using MongoDB) under the key parako_config. This works with all supported storage adapters — MongoDB, SQLite, and PostgreSQL. The database is the single source of truth for all non-bootstrap settings.

On first startup, if no configuration exists in the database, the result of getDefaultFullConfig() is automatically flushed to the database. This ensures the database always has a complete configuration document.

Admin Panel — navigate to /admin/settings to view and edit all configuration sections. See Admin Panel for details.

Management API — use the parako:config:read and parako:config:write scopes to manage configuration programmatically. See Management API for endpoint details.

Changes made via the admin panel or API take effect immediately — no server restart is required for most settings. The exceptions are settings that require OIDC provider re-initialization (see impact analysis warnings in the admin panel).


The full configuration is organized into 9 top-level sections. For the complete schema with all fields and defaults, see parako.sample.jsonc in the repository root.

Core identity and metadata.

FieldTypeDefaultDescription
titlestringParako.IDApplication title shown in UI
descriptionstring(from package.json)Application description
locales.defaultstringenDefault locale
locales.availablestring[][en, fr, es, pt, de, it, ru, zh, ja, ko]Available locales

UI appearance and theming. See Branding for full customization guide.

FieldTypeDefaultDescription
companyNamestringYour OrganizationCompany name (also used as MFA issuer name)
logo / logoDarkstring/images/logo-*.pngLogo paths for light/dark mode
logoIcon / logoIconDarkstring/images/logo-icon-*.pngIcon logo paths
faviconstring/favicon.pngFavicon path
fonts.sans / .heading / .monostringSystem fontsFont family stacks
colors.light.* / colors.dark.*string(33 tokens each)Theme color hex values
ui.customizationobject{enabled: false}Custom Nunjucks view template overrides

Environment and infrastructure. Several fields are bootstrap-only.

FieldTypeDefaultDescription
urlstringhttp://localhost:9007Public URL (bootstrap-only)
server.allowed_originsstring*CORS allowed origins
server.proxybooleanfalseTrust proxy headers
redis_prefixstringparakoRedis key namespace prefix
cookiesobject(see sample)Cookie configuration (session, locale, theme)
routesobject(see sample)Route path customization (auth, accounts, API, home)

Security settings and authentication policies.

FieldTypeDefaultDescription
secrets.jwt_secretstring(auto-generated)JWT signing secret (min 32 chars)
secrets.cookie_secretsstring[](auto-generated)Cookie encryption secrets (2 required)
secrets.hmac_secretstring(auto-generated)HMAC signing secret
protection.rate_limitingobject{enabled: true, 100/15min}Rate limiting configuration
protection.device_matchingobject(see sample)Device fingerprint matching thresholds
key_storeobject{type: "database", 90d rotation}JWKS key store configuration
authentication.multi_factorobject(see sample)MFA settings (TOTP, email, SMS, WebAuthn)
authentication.sessionobject(see sample)Session binding, timeouts, limits
authentication.loginobject(see sample)Login methods, password policy
authentication.signupobject(see sample)Signup methods, email verification, auto-approval
authentication.rolesobject{default: "user"}Available roles and default role
authentication.recoveryobject(see sample)Account recovery (backup codes, secondary email, SMS)
authentication.password_breach_detectionobject{enabled: true}HaveIBeenPwned breach checking

Feature toggles and capabilities.

FieldTypeDefaultDescription
oidc.*object(see sample)OIDC feature toggles: device flow, PKCE, introspection, revocation, backchannel logout, dynamic registration, etc.
social_providers.enabledstring[][]Active social login providers
social_providers.availablestring[][google, github, microsoft, linkedin, facebook]Supported providers
social_providers.behaviorobject(see sample)Social login behavior (new user handling, manual linking)
social_providers.<provider>object(see sample)Per-provider OAuth credentials and endpoints
metricsobject{enabled: false}Prometheus metrics endpoint
multi_tenancyobject{enabled: false}Multi-tenancy settings (infrastructure fields are bootstrap-only)

OIDC protocol configuration.

FieldTypeDefaultDescription
issuerstring(computed)OIDC issuer URL — always computed from deployment.url + oidc.path
pathstring/oidc/v1OIDC provider base path
routesobject(see sample)OIDC endpoint paths (authorize, token, userinfo, etc.)
secrets.pairwise_saltstring(auto-generated)Salt for pairwise subject identifiers
token_ttlobject(see sample)Token TTLs in seconds (access_token: 3600, refresh_token: 86400, etc.)
discoveryobject(see sample)OIDC discovery document metadata
jwaobject(see sample)JWA algorithm configuration

Computed from bootstrap — never persisted. Built from STORAGE_ADAPTER, OIDC_STORAGE_ADAPTER, REDIS_*, and STORAGE_MONGODB_URI at runtime. Cannot be set via file config or admin panel.

External service connections.

FieldTypeDefaultDescription
email.smtp_host / .smtp_port / .smtp_username / .smtp_password / .frommixed(placeholder)SMTP configuration
urls.website / .privacy_policy / .terms_of_service / .contactstring(computed from deployment.url)Public URLs
ipinfoobject{enabled: false}IP geolocation service
ipqualityscoreobject{enabled: false}IP reputation service
fingerprintjsobject{enabled: false}Browser fingerprinting
file_storageobject{provider: "local"}File storage (local or S3)

Note: file_storage.upload_dir defaults to ./runtime/uploads, keeping mutable data under runtime/ for clean Docker mounts and backups. Set an absolute path (e.g. /var/lib/parako/uploads) to use a dedicated volume. For nginx X-Accel-Redirect deployments, the internal location /_internal_uploads/ must alias this directory.

Notification channels and preferences.

FieldTypeDefaultDescription
channels.email.enabledbooleantrueEmail notifications
channels.smsobject{enabled: false}SMS notifications (Twilio: provider, api_key, api_secret, from_number, rate_limits)
defaults.security_alertsbooleantrueSecurity alert notifications
defaults.new_session_alertsbooleantrueNew session login alerts
defaults.allow_user_preferencesbooleantrueLet users customize notification preferences

Computed fields are automatically generated or derived from other configuration values. They are managed by src/config/computed-fields.ts and applied on every config load and update.

These secrets are generated once if their value is null or undefined, then persisted. They are not regenerated if the value is an empty string (which indicates data corruption — delete the field to trigger regeneration).

Field PathLengthDescription
security.secrets.jwt_secret128 hex chars (64 bytes)JWT signing key
security.secrets.cookie_secretsArray of 2 × 128 hex charsCookie encryption keys
security.secrets.hmac_secret128 hex chars (64 bytes)HMAC signing key
oidc.secrets.pairwise_salt64 hex chars (32 bytes)OIDC pairwise subject salt

Production: If any of these secrets are missing at startup, resolveSecret() invoked by getDefaultFullConfig() throws a fatal error. Auto-generation only works in development.

These fields are always recomputed on every config load or update. They cannot be manually set — any value you provide will be overwritten.

Derived FieldComputed From
oidc.issuerdeployment.url + oidc.path
oidc.discovery.op_policy_urideployment.url + /privacy (only if empty)
oidc.discovery.op_tos_urideployment.url + /terms (only if empty)
oidc.discovery.service_documentationdeployment.url + /docs (only if empty)
security.authentication.multi_factor.totp.issuer_namebranding.companyName
security.authentication.multi_factor.webauthn.rp_namebranding.companyName
security.authentication.multi_factor.webauthn.rp_idHostname from deployment.url
integrations.urls.websitedeployment.url (only if empty)
integrations.urls.privacy_policydeployment.url + /privacy (only if empty)
integrations.urls.terms_of_servicedeployment.url + /terms (only if empty)
integrations.urls.contactdeployment.url + /contact (only if empty)

In multi-tenant mode, when computing derived fields within a tenant context, URLs use the tenant’s domain instead of the platform URL:

  1. Custom domain (if tenant_domain is set): https://{tenant_domain}
  2. Subdomain: https://{tenantId}.{base_domain} (preserves port if present)
  3. Fallback: platform deployment.url

For WebAuthn rp_id, custom domain tenants use the custom domain as the relying party ID, while subdomain tenants use the base domain (since rp_id must be an ancestor of the origin).


1. .env (bootstrap) ← highest priority
2. Database or file config
3. Computed defaults
4. getDefaultFullConfig() ← lowest priority
1. .env (bootstrap) ← highest priority
2. Per-tenant overrides (tenant_settings_override collection)
3. Platform config (global from database)
4. Computed defaults (tenant-aware)
5. getDefaultFullConfig() ← lowest priority

ConfigManager.getConfig() checks features.multi_tenancy.enabled:

  • If enabled: looks up the current tenant via tenantContext.getTenantIdSafe(), returns the tenant-specific cached config if available, otherwise falls back to the platform config.
  • If disabled: returns the cached platform config directly.

Tenant configs are loaded on demand via ensureTenantConfig() and cached in memory. Concurrent requests for the same uncached tenant coalesce on a single Promise (mutex pattern) to avoid duplicate database loads.


These 11 field paths are listed in BOOTSTRAP_ONLY_FIELDS (src/config/types.ts). They can only be set via runtime/.env, are stripped from any database or file config before persisting, and shown as read-only in the admin panel.

#Field PathSet By
1deployment.environmentDEPLOYMENT_ENVIRONMENT
2deployment.server.portDEPLOYMENT_SERVER_PORT
3storage.adapterSTORAGE_ADAPTER
4storage.mongodb.uriSTORAGE_MONGODB_URI
5storage.sqlite.pathSTORAGE_SQLITE_PATH
6storage.postgresql.urlSTORAGE_POSTGRESQL_URL
7features.multi_tenancy.extraction_priorityMULTI_TENANCY_EXTRACTION_PRIORITY
8features.multi_tenancy.tenant_headerMULTI_TENANCY_TENANT_HEADER
9features.multi_tenancy.provider_pool.max_sizeMULTI_TENANCY_PROVIDER_POOL_MAX_SIZE
10features.multi_tenancy.provider_pool.idle_ttl_msMULTI_TENANCY_PROVIDER_POOL_IDLE_TTL_MS
11features.multi_tenancy.provider_pool.cleanup_interval_msMULTI_TENANCY_PROVIDER_POOL_CLEANUP_INTERVAL_MS

Note:

  • deployment.url (DEPLOYMENT_URL) is not in BOOTSTRAP_ONLY_FIELDS. If unset in runtime/.env, it falls back to the persisted deployment.url from the database or file config (default http://localhost:9007) — see the Core Settings table for the full precedence rules.
  • features.multi_tenancy.enabled is bootstrap-only at runtime: the bootstrap value always wins over the database value.

14 configuration field paths are encrypted at rest using ENCRYPTION_KEY:

CategoryFields
App secretssecurity.secrets.jwt_secret, security.secrets.cookie_secrets, oidc.secrets.pairwise_salt
Email/SMSintegrations.email.smtp_password, notifications.channels.sms.api_key, notifications.channels.sms.api_secret
External servicesintegrations.ipinfo.api_token, integrations.ipqualityscore.api_key, integrations.fingerprintjs.api_key
Social providersfeatures.social_providers.{google,github,microsoft,linkedin,facebook}.client_secret

Sensitive fields are encrypted on save, decrypted on load, masked as ••••••• in the admin UI, and rate-limited to 10 reveals/minute/user (activity-logged for audit). If ENCRYPTION_KEY is missing, the application refuses to save or load configuration. If the key is wrong (e.g., after rotation without re-encrypting), decryption fails and an error is logged.


The admin panel at /admin/settings provides a web interface for managing all configuration sections. See Admin Panel for the full UI guide.

  • Section editors — dedicated forms for each config section: application, branding, deployment, security, OIDC, features, integrations, notifications.
  • Version history — view and rollback to previous configuration versions.
  • Health check — configuration validation status.
  • Import/Export — download or upload configuration as JSON.
  • Impact analysis — warnings about the effects of changes (e.g., “changing OIDC issuer invalidates all tokens”).

When USE_FILE_CONFIG=true is active, the admin panel displays a warning banner indicating that changes will not persist — the file is the source of truth in that mode.


Every configuration save creates a new document rather than updating in place. This provides a complete audit trail and safe rollback.

  1. Atomic save — existing active document is deactivated, then the new version is inserted (2-phase: deactivate old → insert new).
  2. Semver versioning — each save increments the patch version: 1.0.01.0.11.0.2.
  3. Automatic cleanup — only the last 10 versions are kept. Older versions are deleted by cleanupOldVersions().
  4. Optimistic locking — the _version counter prevents concurrent modification conflicts. If another user saves between your load and save, you get: “Configuration was modified by another user. Please refresh the page and try again.”
  5. MutexconfigUpdateLock serializes concurrent save operations within the same process.
  1. Open Version History in the admin panel.
  2. Select a previous version to inspect.
  3. Click Restore — this creates a new version with the old config values (not a destructive revert).

Each version stores:

  • last_modified_by — user who made the change
  • change_reason — description of why the change was made
  • tags — categorization labels (e.g., ["main", "configuration"])
  • environment — deployment environment at time of save

When configuration changes in the database, all running processes are notified through a multi-layer strategy:

LayerBehavior
MongoDB Change StreamsWhen the MongoDB deployment is a replica set or sharded cluster, the settings collection is watched for real-time change detection (preferred method).
Polling fallbackStandalone MongoDB, SQLite, and PostgreSQL poll every 30 seconds, comparing updated_at timestamps.
Redis Pub/SubUpdates publish an invalidation message on {redis_prefix}:*:config:invalidated. Subscribers clear in-memory caches and reload from the database, ensuring cross-process consistency in clustered deployments.
In-memory cachingFull RuntimeConfig is cached after load. Sections cache lazily with a 60-second TTL. Tenant configs are cached per-tenant and invalidated on global change.
Admin saves config
→ encrypt + persist new version
→ change detected (Change Stream or poll)
→ RuntimeConfig rebuilt, caches cleared
→ Redis Pub/Sub broadcasts invalidation
→ other processes reload from database

In multi-tenant mode, individual tenants can override a subset of the platform configuration. Overrides are stored in the tenant_settings_override table (or collection in MongoDB), scoped per tenant. See Multi-Tenancy for the overall tenant architecture.

Tenants can override fields in these sections:

application, branding, security, features, oidc, integrations, notifications

Tenants cannot override: deployment, storage, oidc_storage.

Only fields listed in ALLOWED_TENANT_FIELDS (~140 fields) are accepted. Any other path is silently stripped. This prevents tenants from modifying infrastructure or security-critical settings not intended for their scope.

The platform can set minimums that tenants cannot lower:

  • Boolean floors — if the platform enables a security feature (e.g., MFA, email verification, session binding), tenants cannot disable it.
  • Numeric floors — tenant values must be ≥ platform values (e.g., min_password_length, min_confidence_score).
  • Absolute minimum — password length cannot go below 8 regardless of platform setting (NIST SP 800-63B).
  • Ordered enum — WebAuthn user_verification uses ordered values (discouraged < preferred < required). Tenants cannot weaken below the platform level.

Tenants cannot exceed platform limits:

  • Token TTLs (all 10 types), session timeouts, rate limits, SMS rate limits, max credentials per user, trust duration.
  • Zero means unlimited — for max_concurrent_sessions, idle_timeout_minutes, and absolute_timeout_hours, a value of 0 means unlimited. If the platform sets a limit (non-zero), tenants cannot set 0 (unlimited).

Violations are silently adjusted (not rejected). The adjusted value is used, and a ConstraintViolation is logged for audit purposes with the original and adjusted values.

8 field paths are encrypted separately per tenant when stored in override documents:

  • integrations.email.smtp_password
  • notifications.channels.sms.api_key
  • notifications.channels.sms.api_secret
  • features.social_providers.google.client_secret
  • features.social_providers.github.client_secret
  • features.social_providers.microsoft.client_secret
  • features.social_providers.linkedin.client_secret
  • features.social_providers.facebook.client_secret

All configuration is validated against Zod schemas at startup and on every update. Invalid configuration causes a fail-fast with descriptive error messages.

Key validation rules:

  • Secrets (JWT, HMAC, cookie) — must be at least 32 characters.
  • Port — must be between 1 and 65535.
  • Routes — must start with / and be at least 2 characters.
  • Colors — must be valid 3 or 6-digit hex codes (e.g., #fff or #2563eb).
  • Redis prefix — allows only alphanumeric characters, hyphens, and underscores.
  • Application URL — must be a valid URL format.
  • ENCRYPTION_KEY — must be 64 hex characters (32 bytes) or 44 base64 characters.

If you see validation errors at startup, check the error message for the specific field and constraint that failed.


Generate all required secrets before your first deployment:

Terminal window
# Encryption key (required — 64 hex characters)
export ENCRYPTION_KEY=$(openssl rand -hex 32)
# JWT secret (min 32 characters)
export JWT_SECRET=$(openssl rand -hex 32)
# Cookie secrets (two required)
export COOKIE_SECRET_1=$(openssl rand -hex 32)
export COOKIE_SECRET_2=$(openssl rand -hex 32)
# HMAC secret (for cross-tenant OAuth state signing)
export HMAC_SECRET=$(openssl rand -hex 32)
# Pairwise salt (for OIDC pairwise subject types)
export PAIRWISE_SALT=$(openssl rand -hex 16)

Store these values in your runtime/.env file. Never commit secrets to version control.


SymptomCause and fix
Warning logs about multiple active configurationsRace condition or interrupted save left more than one active record. Auto-healed at startup — the newest active config is kept, older ones deactivated.
Garbled secrets in admin panel, decryption errorsENCRYPTION_KEY changed or differs from the key used to encrypt. Fix: restore the original key. If lost, re-save all secrets through the admin panel.
Application ignores runtime/parako.jsoncVerify USE_FILE_CONFIG=true AND DEPLOYMENT_ENVIRONMENT=development in runtime/.env, and that the file exists under runtime/.
Port, DB URI, or environment is read-only in admin panelExpected behavior — these are bootstrap-only fields, changeable only in runtime/.env.
Fatal startup: [FATAL] JWT_SECRET is not setSet all required secrets in runtime/.env. Auto-generation is disabled in production to prevent running with non-persistent secrets.
Config changes not propagating between processesVerify Redis is connected (Pub/Sub invalidation). Change Streams require MongoDB replica sets — SQLite, PostgreSQL, and standalone MongoDB use 30-second polling. Section caches have a 60-second TTL.