Wealthfolio logo Wealthfolio
Download
Docs
Configuration Reference

Configuration Reference

Every WF_* environment variable Wealthfolio understands, plus how to escape Argon2 hashes for each environment.


Last updated

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_DATA_DIRoptionalUnset (uses WF_DB_PATH parent)
WF_DB_PATHoptional./db/app.db
WF_DB_REQUIRE_ENCRYPTIONoptionalfalse
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 in the installation directory. An explicit WF_SECRET_FILE is independent of WF_DATA_DIR; keep the override when changing storage settings.

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 varpaste raw hash, check Is Literal?Without it Coolify interpolates $ as variable refs

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.
  • Coolify: Is Secret only masks the value in the UI. It does not stop $ interpolation — you must also check Is Literal?.

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_DATA_DIR

Optional. Directory holding profiles.json, profile databases under profiles/<uuid>/app.db, and the default encrypted secrets.json file. Relative paths use the server’s working directory; ~ is not expanded.

Existing installations do not need this setting. WF_DB_PATH continues to select the installation directory through its parent. If you set both, WF_DATA_DIR must be the same directory as the parent of WF_DB_PATH, or startup fails before writing data. Existing profile registry entries keep their saved database paths; setting WF_DATA_DIR does not move files.

For a fresh installation using only WF_DATA_DIR, the legacy database candidate is <WF_DATA_DIR>/app.db. Docker images supply WF_DB_PATH=/data/wealthfolio.db by default, so explicitly set WF_DB_PATH= to clear it when using the directory-only form. Keep the old database path when upgrading an existing installation.

WF_DB_PATH

Default: ./db/app.db

Legacy SQLite file path. Its parent selects the installation directory when WF_DATA_DIR is unset. New profiles have separate databases under that directory. Keep this path when upgrading an existing installation.

/data/wealthfolio.db   # Docker image default (with /data volume mount)
./database/app.db      # Local relative path

WF_DB_REQUIRE_ENCRYPTION

Default: false. Requires encrypted profile databases at server startup. For a fresh installation, set it before starting to create encrypted databases. Changing this setting does not encrypt an existing database. Stop the server, run wealthfolio-server db encrypt for the default profile and wealthfolio-server db encrypt --profile <UUID> for each additional profile, then enable the setting and restart. The offline commands need the same master key, storage settings, and data mounts as the server. Keep the master key: it is needed to open encrypted databases and saved encrypted snapshots. See Export & Backup for how this differs from a portable backup password.

Existing databases must be encrypted before enabling WF_DB_REQUIRE_ENCRYPTION. The variable enforces the database state; it does not convert a plaintext database. If startup reports a mismatch, the database is not damaged. Either unset the variable to remain plaintext, or stop the service, back up the full data directory, run wealthfolio-server db encrypt with the same volume, service user, and master key, then restart with the flag set. Releases before 3.9 ignored the variable, so upgraded databases can be plaintext even when it was already configured.

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: installation directory (WF_DATA_DIR or parent of WF_DB_PATH)

Path where Wealthfolio reads installable add-ons from. Defaults to the installation directory, 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
# Optional with the path above: WF_DATA_DIR=/data

# 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

To let external AI agents reach a self-hosted instance, enable the endpoint with WF_MCP_ENABLED=true and follow the MCP Server guide — it covers token scopes and the auth requirements for network-bound servers.