Wealthfolio logo Wealthfolio
Download
Docs
Configuration Reference

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

VariableRequiredDefault
WF_SECRET_KEY✅ always
WF_AUTH_PASSWORD_HASH✅ for web access
WF_CORS_ALLOW_ORIGINS✅ when auth on* (rejected with auth on)
WF_LISTEN_ADDRrecommended0.0.0.0:8088
WF_DB_PATHrecommended./db/app.db
WF_AUTH_REQUIREDoptionaltrue
WF_AUTH_TOKEN_TTL_MINUTESoptional60
WF_COOKIE_SECUREoptionalauto
WF_OIDC_ISSUER_URLfor SSO
WF_OIDC_CLIENT_IDfor SSO
WF_OIDC_* (SSO options)optionalsee below
WF_REQUEST_TIMEOUT_MSoptional300000 (5 min)
WF_STATIC_DIRoptionaldist
WF_SECRET_FILEoptional<data-root>/secrets.json
WF_ADDONS_DIRoptional<data-root> (DB directory)
WF_LOG_FORMAToptionaltext

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_ADDR binds to anything other than 127.0.0.1 and WF_AUTH_PASSWORD_HASH is unset, the server panics. Set WF_AUTH_REQUIRED=false to 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 neither WF_OIDC_ALLOWED_EMAILS nor WF_OIDC_ALLOWED_SUBS is set, the server panics — an empty allowlist would grant every IdP-authenticated user access. Set an allowlist, or WF_OIDC_ALLOW_ANY=true to 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 32

Back 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, not echo -n. echo adds 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:

EnvironmentSyntaxNotes
Docker CLI --env-fileWF_AUTH_PASSWORD_HASH=$argon2id$...Docker CLI does not interpolate env files
Docker Compose env_file with format: rawWF_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 inlineWF_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 UIpaste raw hashUnraid handles escaping internally
Coolify env var (marked as secret)paste raw hashCoolify handles escaping internally

Common mistakes:

  • Double quotes in Docker CLI ("$argon..."): the shell expands $argon2id to empty.
  • Single quotes inside --env-file files: Docker keeps the quotes as part of the value.
  • Unquoted/unescaped $ in Compose YAML: Compose treats $argon as 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 days

Default: auto

Controls the Secure attribute on the auth session cookie. Accepted values:

ValueBehavior
auto (default)Sets Secure automatically based on whether the request was HTTPS.
true, 1, yesAlways sets Secure. Use behind a reverse proxy that terminates HTTPS.
false, 0, noNever 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:

ConfigurationLogin page shows
WF_OIDC_* onlySign in with SSO only
WF_AUTH_PASSWORD_HASH onlypassword field only (default)
bothpassword 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/callback

WF_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 email is only honored when the IdP asserts email_verified=true — an unverified email can be attacker-chosen on self-signup IdPs.
  • WF_OIDC_ALLOWED_SUBS is the stronger control: the sub is 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 port

Listening 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 path

WF_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-origin

Wildcard 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