Configuration
Everything is an environment variable in .env. No YAML files and no settings in the database, except the admin preferences managed in the UI.
General
Explained in Install with Docker Compose.
| Variable | Description |
|---|---|
SECRET_KEY Required |
Signs admin sessions (and encrypts data when ENCRYPTION_KEY is unset). Minimum 32 characters (the app refuses to start otherwise). Generate with openssl rand -hex 32. |
ENCRYPTION_KEY Recommended |
Root key for all encryption at rest, ≥ 32 characters. Unset: SECRET_KEY is used for encryption too. On an existing install, do not just set it: follow Rotating the encryption key — the current key must go into ENCRYPTION_KEY_PREVIOUS. |
ENCRYPTION_KEY_PREVIOUS Optional |
Keys still accepted for decryption during a rotation: comma-separated, no spaces, each ≥ 32 characters. |
SETUP_TOKEN Optional |
Token the first-run wizard at /setup asks for, at least 32 characters (the app refuses to start with a shorter one; openssl rand -hex 16). After MAX_LOGIN_ATTEMPTS wrong tokens, /setup refuses every token for LOGIN_LOCKOUT_MINUTES. Unset: a random token is created at startup and written once to the log (docker compose logs app | grep "Setup token"). Default: random |
DATABASE_URL Required |
PostgreSQL connection string. Must use the async driver. Format: postgresql+asyncpg://user:password@host:port/dbname |
REDIS_URL Required |
Redis connection string. Format: redis://host:port/db, with a password redis://:password@host:port/db |
DRAFT_REDIS_MEMORY_PERCENT Optional |
New attachments in a submission draft are refused while Redis uses more than this share of its maxmemory; the whistleblower can still submit without them. No effect when Redis has no maxmemory. See Redis sizing. Default: 80 |
APP_NAME Optional |
Display name shown in the application UI and browser title. Default: OpenWhistle |
DEMO_MODE Optional |
Set to true to enable demo mode. See Demo mode. Default: false |
LOCAL_REVIEW_LOGIN Optional |
Local review only — never in production. Requires DEMO_MODE=true, SECURE_COOKIES=false and a loopback APP_PUBLIC_URL (the app refuses to start otherwise). Shows a one-click "Enter the local review" button on /admin/login that signs in as the seeded demo admin with no password or MFA check, so an agent can review every admin page without a human typing credentials. Maintainer tooling for the release review: an operator never sets it. Default: false |
SECURE_COOKIES Optional |
Set to false when the app is served over plain HTTP (e.g. local network without TLS). Must be true behind HTTPS. Browsers block Secure cookies over HTTP, causing session failures when accessed from other devices. Default: true |
SESSION_MAX_HOURS Optional |
Absolute admin session lifetime in hours, counted from login; extending a session never passes it. Default: 12 |
ACCESS_TOKEN_EXPIRE_MINUTES Optional |
Lifetime of one admin session token; Stay signed in renews it, up to SESSION_MAX_HOURS. Default: 60 |
ALGORITHM Optional |
JWT signing algorithm of admin sessions. Leave it. Default: HS256 |
MAX_ACCESS_ATTEMPTS Optional |
Wrong PINs for one case number before the status page asks the reporter to wait. A correct PIN always works. Default: 5 |
ACCESS_LOCKOUT_MINUTES Optional |
How long that wait notice lasts. Default: 15 |
MAX_LOGIN_ATTEMPTS Optional |
Wrong admin passwords for one username before that username is locked. Default: 10 |
LOGIN_LOCKOUT_MINUTES Optional |
How long an admin username stays locked. Default: 30 |
APP_VERSION Optional |
Informational; set by the image. Do not override. Default: the image's version |
ADMIN_FAILED_LOGIN_ALERT_THRESHOLD Optional |
Password-spraying alarm. Failed admin password attempts are counted across all accounts (no username, no IP stored). When this many fail within the window, one alert goes to the email and webhook notification channels and the audit log records auth.password_spraying_suspected. 0 disables it. Per-account lockout and MFA stay in force either way. Default: 50 |
ADMIN_FAILED_LOGIN_ALERT_WINDOW_MINUTES Optional |
Sliding window for ADMIN_FAILED_LOGIN_ALERT_THRESHOLD, in minutes. At most one alert per window. Default: 15 |
SUBMISSION_MODE_ENABLED Optional |
Set to false to hide the anonymous/confidential mode selection step from the submission wizard. When disabled, all reports are treated as anonymous. Default: true |
OIDC_ENABLED Optional |
Set to true to enable OIDC login for administrators. Default: false |
OIDC_SERVER_METADATA_URL Optional |
OIDC provider discovery URL (e.g. https://accounts.google.com/.well-known/openid-configuration). Required when OIDC_ENABLED=true. Login uses PKCE (S256) and accepts only an ID token signed with an asymmetric key from the provider's jwks_uri, with matching issuer, audience (the client ID), expiry and nonce. |
OIDC_CLIENT_ID Optional |
OAuth 2.0 client ID from your OIDC provider. Required when OIDC_ENABLED=true. |
OIDC_CLIENT_SECRET Optional |
OAuth 2.0 client secret from your OIDC provider. Required when OIDC_ENABLED=true. |
OIDC_REDIRECT_URI Optional |
OAuth 2.0 callback URL. Must match the redirect URI registered with your OIDC provider. Example: https://yourdomain.com/admin/oidc/callback. Required when OIDC_ENABLED=true. |
BRAND_PRIMARY_COLOR Optional |
Primary brand colour as a hex value. Used for buttons, accents, and links throughout the UI. Default: #0c7253 |
BRAND_LOGO_URL Optional |
URL to a company logo image. When set, the logo is shown in the navigation bar instead of the default OpenWhistle shield icon. |
APP_PUBLIC_URL Optional |
Public base URL of the application. Used to build dashboard links inside notification emails and webhooks. Set to your domain (e.g. https://whistleblower.example.com). Default: http://localhost |
ONION_LOCATION Optional |
Tor onion (v3) address of this instance, e.g. http://<56 chars>.onion, no path or query string. When set, every response carries an Onion-Location header (Tor Browser offers to switch) and the submit page shows the address to reporters on a monitored network. Never sent to a visitor already on the onion service itself. See Offering an onion address. |
LOG_LEVEL Optional |
Logging verbosity. Accepted values: DEBUG, INFO, WARNING, ERROR, CRITICAL. Default: INFO |
LOG_FORMAT Optional |
Log output format. Use json for structured JSON (recommended for log aggregation pipelines) or text for human-readable output. Default: json |
UPDATE_CHECK_ENABLED Optional |
Set to true to let OpenWhistle check GitHub once a day for a newer release, shown on the Admin → System page. Off by default (no external calls); sends no instance data to GitHub — only a standard request. Default: false |
TELEMETRY_ENABLED Optional |
The daily installation count. Unset or empty: the answer from the setup wizard / Admin → System decides, off until given. false: always off. true: always on. The switch is locked either way. DEMO_MODE is never counted. Default: (empty) |
Notification digests
Explained in Notifications.
| Variable | Description |
|---|---|
NOTIFICATION_BATCH_MINUTES Optional |
Minutes between digests of new reports and whistleblower messages. All replicas fire at the same wall-clock moments; exactly one sends. 0 sends each event at once. Default: 1440 |
Email (SMTP)
Explained in Notifications.
| Variable | Description |
|---|---|
NOTIFY_EMAIL_ENABLED Optional |
Set to true to send an email notification when a new report arrives. Default: false |
NOTIFY_EMAIL_TO Optional |
Comma-separated list of recipient addresses (e.g. admin@example.com,compliance@example.com). |
NOTIFY_EMAIL_FROM Optional |
Sender (From) address for notification emails. Default: openwhistle@localhost |
NOTIFY_SMTP_HOST Optional |
Hostname of the SMTP server. Default: localhost |
NOTIFY_SMTP_PORT Optional |
SMTP port. Use 587 for STARTTLS or 465 for SMTPS. Default: 587 |
NOTIFY_SMTP_USER Optional |
SMTP authentication username. Leave blank for unauthenticated relay. |
NOTIFY_SMTP_PASSWORD Optional |
SMTP authentication password. |
NOTIFY_SMTP_TLS Optional |
Use STARTTLS on the SMTP connection. Set to false when using SMTPS (port 465). Default: true |
NOTIFY_SMTP_SSL Optional |
Use direct TLS (SMTPS, port 465). When true, also set NOTIFY_SMTP_TLS=false. Default: false |
Webhook
Explained in Notifications.
| Variable | Description |
|---|---|
NOTIFY_WEBHOOK_ENABLED Optional |
Set to true to POST a JSON notification to a webhook URL on new reports. Default: false |
NOTIFY_WEBHOOK_URL Optional |
Target URL for webhook POST requests (e.g. a Slack incoming webhook or a custom endpoint). |
NOTIFY_WEBHOOK_SECRET Optional |
HMAC-SHA256 signing secret. When set, each request carries an X-OpenWhistle-Signature header for verification. |
NOTIFY_WEBHOOK_TYPE Optional |
Payload format for webhook notifications. generic sends {"event": "new_activity", "new_reports": 2, "new_messages": 1, "message": "..."} (new_messages: cases with new messages) (counts, never case numbers); slack sends a Slack Block Kit message; teams sends a Microsoft Teams Adaptive Card (v1.4). The SLA reminder webhook uses the same three formats with {"event": "sla_reminder", "ack_due": ..., "feedback_due": ..., "message": "..."} for generic. Default: generic |
SLA reminders
Explained in SLA reminders.
| Variable | Description |
|---|---|
REMINDER_ENABLED Optional |
Set to true to enable automatic SLA reminder notifications. Default: false |
REMINDER_ACK_WARN_DAYS Optional |
Send an acknowledgement reminder this many days before the 7-day deadline expires. Default: 2 |
REMINDER_FEEDBACK_WARN_DAYS Optional |
Send a feedback reminder when this many days or fewer remain before the 3-month feedback deadline. Default: 30 |
Data retention
Explained in Data retention.
| Variable | Description |
|---|---|
RETENTION_ENABLED Optional |
Daily automatic deletion of closed reports older than RETENTION_DAYS. Set to false only on legal advice (e.g. pending litigation). Default: true |
RETENTION_DAYS Optional |
Days after a report is closed before it is automatically deleted. Default matches HinSchG §11 Abs. 5: documentation is deleted 3 years after the procedure ends. Change only on legal advice. Default: 1095 |
Organisations (multi-tenancy)
Explained in Multi-tenancy.
| Variable | Description |
|---|---|
MULTI_TENANCY_ENABLED Optional |
Set to true to activate multi-organisation support. Requires at least one organisation to exist; the default org is auto-created at setup. Default: false |
DEFAULT_ORG_SLUG Optional |
Slug of the default organisation auto-created during setup. Used for single-tenant deployments and as the fallback for legacy data. Default: default |
LDAP / Active Directory
Explained in LDAP / Active Directory login.
| Variable | Description |
|---|---|
LDAP_ENABLED Optional |
Set to true to allow admin login via LDAP/AD. Default: false |
LDAP_SERVER Optional |
Hostname or IP of the LDAP server (e.g. ldap.example.com). |
LDAP_PORT Optional |
LDAP port. Use 389 with LDAP_START_TLS or 636 for LDAPS. Plain 389 without either sends passwords in clear. Default: 389 |
LDAP_USE_SSL Optional |
Set to true to use LDAPS (TLS from the start). Set port to 636. The server certificate is verified against the system CA store; for a private CA, mount its PEM file and set LDAPTLS_CACERT to its path. Default: false |
LDAP_START_TLS Optional |
Set to true to upgrade a plain connection on port 389 with StartTLS before any bind, so neither the service account's nor the user's password crosses the network in clear. The certificate is verified exactly as for LDAPS; a server that refuses the upgrade makes the login fail. Ignored when LDAP_USE_SSL=true. Default: false |
LDAP_BIND_DN Optional |
Distinguished name of the service account used to search the directory (e.g. cn=svc-openwhistle,ou=service,dc=example,dc=com). |
LDAP_BIND_PASSWORD Optional |
Password for the service account bind DN. |
LDAP_BASE_DN Optional |
Search base for user lookups (e.g. ou=users,dc=example,dc=com). |
LDAP_USER_FILTER Optional |
LDAP search filter to locate a user entry. {username} is replaced with the entered username at runtime. Default: (uid={username}) |
LDAP_ATTR_USERNAME Optional |
LDAP attribute to use as the username in the provisioned admin record. Default: uid |
LDAP_ATTR_EMAIL Optional |
LDAP attribute to read the user's email address from. Default: mail |
S3-compatible storage
Explained in S3-compatible attachment storage.
| Variable | Description |
|---|---|
STORAGE_BACKEND Optional |
Storage backend for new attachments. db stores data in PostgreSQL; s3 stores data in an S3-compatible bucket. Default: db |
S3_ENDPOINT_URL Optional |
Custom endpoint URL for S3-compatible services (e.g. MinIO, Hetzner Object Storage). Leave blank for AWS S3. |
S3_BUCKET_NAME Optional |
Name of the target S3 bucket. Required when STORAGE_BACKEND=s3. |
S3_ACCESS_KEY_ID Optional |
AWS access key ID (or MinIO equivalent). Required when STORAGE_BACKEND=s3. |
S3_SECRET_ACCESS_KEY Optional |
AWS secret access key (or MinIO equivalent). Required when STORAGE_BACKEND=s3. |
S3_REGION Optional |
AWS region for the bucket (e.g. eu-central-1). Ignored for custom endpoints. Default: us-east-1 |
S3_PREFIX Optional |
Key prefix for all stored objects (e.g. attachments/). Useful when sharing a bucket with other applications. Default: attachments/ |
Virus scanning
Explained in Scanning uploads for viruses.
| Variable | Description |
|---|---|
CLAMAV_HOST Optional |
Hostname of the clamd daemon. Empty disables scanning entirely — no connection is ever made. |
CLAMAV_PORT Optional |
clamd's TCP port. Default: 3310 |
CLAMAV_TIMEOUT_SECONDS Optional |
Connect and reply timeout for one scan. Exceeding it refuses the upload (fail closed). Default: 30 |