Configuration Reference
Every WF_* environment variable Wealthfolio understands, plus how to escape Argon2 hashes for each environment.
All Wealthfolio configuration is done through environment variables prefixed
with WF_. This page is the source of truth. The platform-specific guides
link back here for the details.
Quick reference
| Variable | Required | Default |
|---|---|---|
WF_SECRET_KEY | ✅ always | — |
WF_AUTH_PASSWORD_HASH | ✅ for web access | — |
WF_CORS_ALLOW_ORIGINS | ✅ when auth on | * (rejected with auth on) |
WF_LISTEN_ADDR | recommended | 0.0.0.0:8088 |
WF_DB_PATH | recommended | ./db/app.db |
WF_AUTH_REQUIRED | optional | true |
WF_AUTH_TOKEN_TTL_MINUTES | optional | 60 |
WF_COOKIE_SECURE | optional | auto |
WF_OIDC_ISSUER_URL | for SSO | — |
WF_OIDC_CLIENT_ID | for SSO | — |
WF_OIDC_* (SSO options) | optional | see below |
WF_REQUEST_TIMEOUT_MS | optional | 300000 (5 min) |
WF_STATIC_DIR | optional | dist |
WF_SECRET_FILE | optional | <data-root>/secrets.json |
WF_ADDONS_DIR | optional | <data-root> (DB directory) |
WF_LOG_FORMAT | optional | text |
Startup safety checks
Wealthfolio refuses to start under two specific configurations to prevent accidentally exposing an unauthenticated instance to the network:
- Non-loopback listen + no auth: if
WF_LISTEN_ADDRbinds to anything other than127.0.0.1andWF_AUTH_PASSWORD_HASHis unset, the server panics. SetWF_AUTH_REQUIRED=falseto opt out (only do this if a reverse proxy authenticates for you). - Wildcard CORS + auth: if
WF_CORS_ALLOW_ORIGINS=*and auth is enabled, the server panics. Set explicit origins (e.g.https://wealthfolio.example.com). - OIDC with no allowlist: if OIDC is enabled (
WF_OIDC_ISSUER_URL+WF_OIDC_CLIENT_ID) but neitherWF_OIDC_ALLOWED_EMAILSnorWF_OIDC_ALLOWED_SUBSis set, the server panics — an empty allowlist would grant every IdP-authenticated user access. Set an allowlist, orWF_OIDC_ALLOW_ANY=trueto opt into open access (only safe on a dedicated single-user IdP).
Security
WF_SECRET_KEY
Required. A 32-byte key used to:
- Encrypt sensitive data at rest (broker credentials, API keys)
- Sign JWT access tokens
Generate once and persist it forever:
openssl rand -base64 32Back this up. Losing the secret key means losing access to all stored encrypted secrets. There’s no recovery. Treat it like a master password.
WF_SECRET_FILE
Default: <data-root>/secrets.json
Path to the encrypted secrets file. By default it sits next to your database. Override only if you have a reason (e.g. mounting secrets on a separate volume).
Authentication
WF_AUTH_PASSWORD_HASH
Required for web access (unless WF_AUTH_REQUIRED=false).
An Argon2id PHC string that defines the login password. Generate it with
the argon2 CLI:
printf 'your-password' | argon2 yoursalt16chars! -id -e- The first arg is the salt (use 16+ random characters).
- Use
printf, notecho -n.echoadds a trailing newline on some shells. - Output starts with
$argon2id$v=19$.... That’s the value you set.
Escaping dollar signs in your hash
Argon2 hashes contain $ which most shells and Compose interpolate as
variable references. Use the right syntax for your environment:
| Environment | Syntax | Notes |
|---|---|---|
Docker CLI --env-file | WF_AUTH_PASSWORD_HASH=$argon2id$... | Docker CLI does not interpolate env files |
Docker Compose env_file with format: raw | WF_AUTH_PASSWORD_HASH=$argon2id$... | Requires Docker Compose 2.30+ |
Docker Compose env_file (default) | WF_AUTH_PASSWORD_HASH='$argon2id$...' | Single quotes prevent Compose interpolation |
Docker Compose env_file (default) | WF_AUTH_PASSWORD_HASH=$$argon2id$$... | Alternative: double every $ |
| Docker Compose YAML inline | WF_AUTH_PASSWORD_HASH: '$$argon2id$$...' | Double every $ to escape Compose |
Docker CLI -e (single quotes) | -e WF_AUTH_PASSWORD_HASH='$argon2id$...' | Single quotes prevent shell expansion |
Docker CLI -e (double quotes) | -e WF_AUTH_PASSWORD_HASH="\$argon2id\$..." | Backslash-escape each $ |
| Unraid template UI | paste raw hash | Unraid handles escaping internally |
| Coolify env var (marked as secret) | paste raw hash | Coolify handles escaping internally |
Common mistakes:
- Double quotes in Docker CLI (
"$argon..."): the shell expands$argon2idto empty. - Single quotes inside
--env-filefiles: Docker keeps the quotes as part of the value. - Unquoted/unescaped
$in Compose YAML: Compose treats$argonas a substitution.
WF_AUTH_REQUIRED
Default: true
Set to false only if a reverse proxy handles authentication for you (e.g.
Authentik, Authelia, Coolify’s built-in auth). When false,
WF_AUTH_PASSWORD_HASH is ignored and the server starts without its own
login layer.
WF_AUTH_TOKEN_TTL_MINUTES
Default: 60
JWT access token lifetime in minutes. Users re-authenticate after this expires.
60 # 1 hour (default)
1440 # 24 hours
10080 # 7 daysWF_COOKIE_SECURE
Default: auto
Controls the Secure attribute on the auth session cookie. Accepted
values:
| Value | Behavior |
|---|---|
auto (default) | Sets Secure automatically based on whether the request was HTTPS. |
true, 1, yes | Always sets Secure. Use behind a reverse proxy that terminates HTTPS. |
false, 0, no | Never sets Secure. Only safe for local-only or testing setups. |
If you sit behind a reverse proxy doing TLS termination, auto works in
most cases, but force true if cookies aren’t sticking after login.
Single Sign-On (OIDC)
Optional. Sign in through any OpenID Connect provider (Authentik,
PocketID, Authelia, Keycloak, …) using Authorization Code + PKCE. OIDC is
authentication only and works alongside or instead of
WF_AUTH_PASSWORD_HASH:
| Configuration | Login page shows |
|---|---|
WF_OIDC_* only | Sign in with SSO only |
WF_AUTH_PASSWORD_HASH only | password field only (default) |
| both | password field and SSO |
A successful SSO login mints the same session cookie as password login, so sliding refresh, the JWT layer, and logout behave identically.
OIDC is enabled when both WF_OIDC_ISSUER_URL and WF_OIDC_CLIENT_ID
are set.
When OIDC is enabled you must set an allowlist (WF_OIDC_ALLOWED_EMAILS
and/or WF_OIDC_ALLOWED_SUBS) or the server refuses to start. An empty
allowlist would grant every account your IdP authenticates full access
— dangerous on a shared / multi-tenant / self-signup IdP. To intentionally
allow anyone (only safe on a dedicated single-user IdP), set
WF_OIDC_ALLOW_ANY=true.
WF_OIDC_ISSUER_URL
Provider base URL. Discovery hits
<issuer>/.well-known/openid-configuration at startup, so the issuer must
be reachable when the container starts.
https://auth.example.com/application/o/wealthfolio/WF_OIDC_CLIENT_ID
Client ID registered with your IdP.
WF_OIDC_CLIENT_SECRET
Optional. PKCE is always used, so a secret is only needed for confidential clients that require one.
WF_OIDC_REDIRECT_URL
Required when OIDC is enabled. Must be registered in the IdP and reachable by the browser:
https://your.host/api/v1/auth/oidc/callbackWF_OIDC_SCOPES
Default: openid email profile. Space-separated; openid is always
requested.
WF_OIDC_ALLOWED_EMAILS / WF_OIDC_ALLOWED_SUBS
Comma-separated allowlists matched against the ID token’s email / sub
claims. With neither set the server refuses to start (see the warning
above) unless WF_OIDC_ALLOW_ANY=true.
WF_OIDC_ALLOWED_EMAILS=you@example.com,partner@example.com
WF_OIDC_ALLOWED_SUBS=8f3b...,a91c...- An
emailis only honored when the IdP assertsemail_verified=true— an unverified email can be attacker-chosen on self-signup IdPs. WF_OIDC_ALLOWED_SUBSis the stronger control: thesubis stable and issuer-scoped. Prefer it on shared / multi-tenant IdPs.
WF_OIDC_ALLOW_ANY
Default: false. Set to true to allow any user your IdP
authenticates when no allowlist is configured. Only safe on a dedicated
single-user IdP; on a shared IdP this grants everyone access. A warning is
logged at startup when enabled.
WF_OIDC_POST_LOGOUT_REDIRECT_URL
Optional. When the IdP advertises an end_session_endpoint, sign-out
also ends the IdP session (RP-Initiated Logout); otherwise logout is
local-only. Set this to land back on the app after IdP logout — it must be
registered with the IdP (e.g. Keycloak’s “Valid post logout redirect
URIs”). If unset, the IdP shows its own logged-out page.
WF_OIDC_RP_LOGOUT
Default: true. Set to false to force local-only logout even when the
IdP supports RP-Initiated Logout.
Server
WF_LISTEN_ADDR
Default: 0.0.0.0:8088
Bind address. The default works for Docker out of the box. For local non-Docker use, switch to a loopback address.
0.0.0.0:8088 # Docker / network-accessible (default)
127.0.0.1:8080 # Local non-Docker
0.0.0.0:3000 # Custom portListening on a non-loopback address (anything other than 127.0.0.1)
without setting WF_AUTH_PASSWORD_HASH causes the server to refuse to
start. Set WF_AUTH_REQUIRED=false to opt out (only safe if a reverse
proxy authenticates for you).
WF_DB_PATH
Default: ./db/app.db
Path to the SQLite database. Either a file path or a directory (in which
case app.db is created inside).
/data/wealthfolio.db # Recommended for Docker (with /data volume mount)
/data # Same: app.db gets created inside
./database/app.db # Local relative pathWF_STATIC_DIR
Default: dist
Directory the server reads static frontend assets from. Only relevant if you’re serving a custom frontend build.
WF_REQUEST_TIMEOUT_MS
Default: 300000 (5 minutes)
HTTP request timeout in milliseconds. The default is generous to accommodate large broker syncs; lower it if you want stricter timeouts.
Network
WF_CORS_ALLOW_ORIGINS
Default: * (wildcard). Rejected at startup if auth is enabled.
Comma-separated list of allowed CORS origins. When you enable auth (which you should for any network-accessible deployment), you must set explicit origins matching the URL in your browser’s address bar exactly (scheme + host + port).
http://192.168.1.10:8088
https://wealthfolio.example.com
http://localhost:1420,http://localhost:3000 # multi-originWildcard CORS combined with cookie-based auth is a CSRF vector. That’s why the server refuses to start in that combination. If you see a startup panic mentioning CORS, set explicit origins.
Add-ons
WF_ADDONS_DIR
Default: parent directory of WF_DB_PATH
Path where Wealthfolio reads installable add-ons from. Defaults to the
same directory as your database, so a single /data mount holds
everything.
Logging
WF_LOG_FORMAT
Default: text
Log output format: text (human-readable, colored) or json (structured,
ship to log aggregators).
Complete .env example
# Server (default 0.0.0.0:8088 already works inside Docker; included for clarity)
WF_LISTEN_ADDR=0.0.0.0:8088
WF_DB_PATH=/data/wealthfolio.db
# Security (required, back up the secret key!)
WF_SECRET_KEY=replace-with-output-of-openssl-rand-base64-32
# Authentication (this is your login password)
WF_AUTH_PASSWORD_HASH='$argon2id$v=19$m=19456,t=2,p=1$...'
WF_AUTH_TOKEN_TTL_MINUTES=480
# Network: explicit origin required when auth is on
WF_CORS_ALLOW_ORIGINS=https://wealthfolio.example.com
# Single Sign-On (optional) — enabled when issuer + client id are both set.
# An allowlist is required, or set WF_OIDC_ALLOW_ANY=true to allow any IdP user.
# WF_OIDC_ISSUER_URL=https://auth.example.com/application/o/wealthfolio/
# WF_OIDC_CLIENT_ID=wealthfolio
# WF_OIDC_REDIRECT_URL=https://wealthfolio.example.com/api/v1/auth/oidc/callback
# WF_OIDC_ALLOWED_EMAILS=you@example.com
# Logging
WF_LOG_FORMAT=text