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 (.env) │ ← Always loaded first
│ Infrastructure fields │ Cannot be changed at runtime
├─────────────────────────────────────┤
│ 2a. File (parako.yaml / .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. DEFAULT_FULL_CONFIG │ ← Fallback defaults
│ Lowest priority │ For any unset fields
└─────────────────────────────────────┘
PrioritySourceWhen used
1 (highest)Bootstrap (.env)Always — minimal config to start the application
2File (parako.yaml or 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)DEFAULT_FULL_CONFIGAlways — 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 .env file. Bootstrap fields cannot be changed at runtime via the admin panel — they require a restart.

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

Terminal window
cp .env.example .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./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, you can manage the full configuration in a single file instead of the database. This is the default setup when you first clone the repository.

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

Parako.ID searches for config files in this order:

  1. parako.yaml
  2. parako.yml
  3. parako.jsonc
  4. parako.json

Sample files are provided in the repository root:

Terminal window
# YAML format (recommended)
cp parako.sample.yaml parako.yaml
# Or JSONC format
cp parako.sample.jsonc parako.jsonc

You only need to provide the keys you want to override. Missing keys fall back to DEFAULT_FULL_CONFIG defaults. For example, to only customize branding:

branding:
companyName: My Company
logo: /images/my-logo.svg

All other sections (security, features, oidc, etc.) use their defaults.

Reference environment variables in your config file with ${VAR} syntax:

security:
secrets:
jwt_secret: ${JWT_SECRET}
cookie_secrets:
- ${COOKIE_SECRET_1}
- ${COOKIE_SECRET_2}
hmac_secret: ${HMAC_SECRET}

Default values are supported with ${VAR:-default} syntax:

features:
social_providers:
google:
client_id: ${GOOGLE_CLIENT_ID:-your-google-client-id}
  • Read-only at runtime — changes require restarting the development server.
  • Not supported in production — even if USE_FILE_CONFIG=true, it is ignored when DEPLOYMENT_ENVIRONMENT is staging or production.
  • No version history — unlike database config, file config has no rollback capability.
  • No admin panel editing — the admin panel shows a warning banner when file config is active, and changes made there will 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, DEFAULT_FULL_CONFIG 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.yaml 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-*.svgLogo paths for light/dark mode
logoIcon / logoIconDarkstring/images/logo-icon-*.svgIcon logo paths
faviconstring/favicon.svgFavicon 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. This section is built entirely from bootstrap environment variables (STORAGE_ADAPTER, OIDC_STORAGE_ADAPTER, REDIS_*, STORAGE_MONGODB_URI) at runtime. It cannot be set via file config or admin panel.

FieldTypeSource
oidc_adapter.typeenumOIDC_STORAGE_ADAPTER or STORAGE_ADAPTER
oidc_adapter.mongodb.uri / .databasestringExtracted from STORAGE_MONGODB_URI
oidc_adapter.redis.host / .port / .password / .databasemixedFrom REDIS_* env vars

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)

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() in DEFAULT_FULL_CONFIG 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. DEFAULT_FULL_CONFIG ← 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. DEFAULT_FULL_CONFIG ← 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 12 fields can only be set via .env. They 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.urlDEPLOYMENT_URL
3deployment.server.portDEPLOYMENT_SERVER_PORT
4storage.adapterSTORAGE_ADAPTER
5storage.mongodb.uriSTORAGE_MONGODB_URI
6storage.sqlite.pathSTORAGE_SQLITE_PATH
7storage.postgresql.urlSTORAGE_POSTGRESQL_URL
8features.multi_tenancy.extraction_priorityMULTI_TENANCY_EXTRACTION_PRIORITY
9features.multi_tenancy.tenant_headerMULTI_TENANCY_TENANT_HEADER
10features.multi_tenancy.provider_pool.max_sizeMULTI_TENANCY_PROVIDER_POOL_MAX_SIZE
11features.multi_tenancy.provider_pool.idle_ttl_msMULTI_TENANCY_PROVIDER_POOL_IDLE_TTL_MS
12features.multi_tenancy.provider_pool.cleanup_interval_msMULTI_TENANCY_PROVIDER_POOL_CLEANUP_INTERVAL_MS

Note: features.multi_tenancy.enabled is not in this list, but is effectively bootstrap-only — createRuntimeConfig() always overrides it from bootstrapConfig.multiTenancy.enabled regardless of the database value.


14 configuration field paths contain secrets that are encrypted at rest using ENCRYPTION_KEY:

#Field Path
1security.secrets.jwt_secret
2security.secrets.cookie_secrets
3integrations.email.smtp_password
4integrations.ipinfo.api_token
5integrations.ipqualityscore.api_key
6integrations.fingerprintjs.api_key
7notifications.channels.sms.api_key
8notifications.channels.sms.api_secret
9features.social_providers.google.client_secret
10features.social_providers.github.client_secret
11features.social_providers.microsoft.client_secret
12features.social_providers.linkedin.client_secret
13features.social_providers.facebook.client_secret
14oidc.secrets.pairwise_salt

Behavior:

  • Encrypted on saveSettingsService.encryptSensitiveFields() encrypts before writing to the database.
  • Decrypted on loadSettingsService.decryptSensitiveFields() decrypts when reading from the database.
  • Masked in admin UI — displayed as ••••••• in settings forms.
  • Reveal rate-limited — 10 reveals per minute per 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 the application logs an error.


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 must be notified. Parako.ID uses a multi-layer strategy:

If the MongoDB deployment supports Change Streams (replica set or sharded cluster), the DatabaseConfigProvider watches the settings collection for real-time change detection. This is the preferred method.

If Change Streams are unavailable (standalone MongoDB, or when using SQLite/PostgreSQL), the provider polls the database every 30 seconds to check for changes by comparing updated_at timestamps. This is the default for SQLite and PostgreSQL deployments.

When a configuration update occurs, ConfigManager publishes an invalidation message to:

{redis_prefix}:*:config:invalidated

All other processes subscribe to this pattern and react by:

  1. Clearing their in-memory config cache
  2. Clearing all tenant config caches
  3. Reloading config from the database

This ensures cross-process consistency even in clustered deployments.

  • Full config cache — the complete RuntimeConfig is cached in memory after load.
  • Section cache — individual sections are lazily cached with a 60-second TTL.
  • Tenant config cache — per-tenant merged configs are cached and invalidated on global config change.
  • Cache invalidation — all caches are cleared when a change is detected (via Change Streams for MongoDB replica sets, polling for all other setups, or Redis Pub/Sub).
Admin saves config
→ SettingsService encrypts + saves new version
→ DatabaseConfigProvider detects change (Change Stream, or poll for SQLite/PG)
→ ConfigManager rebuilds RuntimeConfig
→ Section cache cleared
→ Tenant config caches cleared
→ Redis Pub/Sub publishes invalidation
→ Other processes receive message
→ Each process reloads config 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 .env file. Never commit secrets to version control.


Symptom: Warning logs about multiple active configurations.

Cause: Race condition or interrupted save left more than one active record in the settings table.

Fix: Automatic — validateAndFixActiveConfigs() runs at startup and auto-heals by keeping the newest active config and deactivating older ones.

Symptom: Garbled or unreadable secrets in the admin panel, or decryption errors in logs.

Cause: ENCRYPTION_KEY was changed or is different from the key used to encrypt the stored configuration.

Fix: Restore the original ENCRYPTION_KEY. If lost, you must re-save all secrets through the admin panel with the new key.

Symptom: Application ignores your parako.yaml / parako.jsonc file.

Checklist:

  1. Verify USE_FILE_CONFIG=true in .env.
  2. Verify DEPLOYMENT_ENVIRONMENT=development in .env. File config is ignored in staging/production.
  3. Verify the config file exists in the project root with a supported name (parako.yaml, parako.yml, parako.jsonc, or parako.json).

Symptom: Fields like port, database URI, or environment are read-only in the admin panel.

Expected behavior: These are bootstrap-only fields that can only be changed in .env. This is by design for security.

Symptom: Fatal startup error: [FATAL] JWT_SECRET is not set.

Fix: Set all required secret environment variables in .env. Auto-generation is disabled in production to prevent running with non-persistent secrets.

Symptom: Configuration changes made in one process are not reflected in others.

Checklist:

  1. Redis — verify Redis is connected for Pub/Sub invalidation.
  2. Change detection — Change Streams only work with MongoDB replica sets. SQLite, PostgreSQL, and standalone MongoDB use 30-second polling instead.
  3. Caching — section caches have a 60-second TTL. Wait or restart.