Skip to content

Configuration

Two files and an environment. Knowing which is which saves a lot of searching:

Where What belongs there
environment / .env everything below — behaviour, secrets, limits
connections.yaml what to connect to: databases, caches, file stores, brokers
modules/ what the application is: resources, fields, views

Every variable is prefixed CRM_ and read once at startup. crm serve refuses to start on a configuration that is wrong in a way it can detect — see Production checks.

Identity

Variable Default Notes
CRM_APP_NAME CRM Shown in the navigation and page titles.
CRM_APP_TAGLINE — Optional subtitle on the sign-in page.
CRM_ENVIRONMENT development production enables the startup checks and forbids dev auth.
CRM_DEBUG false Also what adds X-Query-Count and Server-Timing to responses.

Security

Variable Default Notes
CRM_SECRET_KEY generated Set this in production. Signs sessions, CSRF tokens, flash messages and reset links. Generated per process when unset, so with several workers each signs differently and nobody stays signed in.
CRM_SESSION_COOKIE crm_session Cookie name.
CRM_SESSION_MAX_AGE 43200 Seconds. Also the upper bound on how long a compromised session lives, since a cookie session cannot be revoked early.
CRM_COOKIE_SECURE false Set true behind HTTPS.
CRM_COOKIE_SAMESITE lax lax, strict or none. A typo is a startup error rather than a cookie the browser silently drops.
CRM_CSRF_ENABLED true Off only for tests.

Authentication

Variable Default Notes
CRM_AUTH_PROVIDERS api_token,session Tried in order; the first to return an identity wins. proxy_header is deliberately absent — it does nothing without trusted networks.
CRM_LOGIN_PROVIDER local Which provider the sign-in form posts to: local or oidc.
CRM_DEV_AUTH false Signs every visitor in as an administrator. Refused when the environment is production.
CRM_DEV_AUTH_ROLES admin Roles that account gets.
CRM_OIDC_ISSUER — Discovery URL.
CRM_OIDC_CLIENT_ID / CRM_OIDC_CLIENT_SECRET —
CRM_OIDC_SCOPES openid email profile
CRM_OIDC_ROLES_CLAIM roles Claim carrying group membership.
CRM_OIDC_DEFAULT_ROLES user Given to anyone the claim does not map.
CRM_OIDC_KEEP_ACCESS_TOKEN false Keep the caller's access token in their session so a caller_token REST connection can act as them. The cookie is signed, not encrypted — see below.
CRM_PROXY_TRUSTED_IPS — Networks allowed to assert identity by header. Without this the provider stays off, because otherwise any client could set the header and be anyone.
CRM_PROXY_USER_HEADER X-Forwarded-User
CRM_PROXY_EMAIL_HEADER X-Forwarded-Email
CRM_PROXY_ROLES_HEADER X-Forwarded-Groups

Passwords

Only consulted by an auth provider that actually holds passwords. Under SSO or behind a gateway the rules that apply are the identity provider's, and these do nothing. See auth.md.

Variable Default Notes
CRM_PASSWORD_MIN_LENGTH 12 Length is the rule that matters; there are deliberately no character-class requirements and no expiry.
CRM_LOCKOUT_ATTEMPTS 8 Wrong guesses before an account locks. 0 disables locking.
CRM_LOCKOUT_MINUTES 15 How long a lock lasts.
CRM_LOCKOUT_WINDOW_MINUTES 60 Failures older than this stop counting, so occasional typos never accumulate into a lockout.
CRM_LOGIN_BURST 10 Sign-in attempts per source address before rate limiting bites. Guards the shape lockout cannot: one guess each against many accounts.
CRM_LOGIN_RATE_PER_MINUTE 6 Refill rate for that budget.
CRM_PASSWORD_RESET true Offer "forgot password". Needs the email channel — a link nobody receives is a support call, not a feature.
CRM_PASSWORD_RESET_MAX_AGE 3600 Seconds a reset link stays valid. Long enough to reach an inbox, short enough that a forwarded email ages out.

Data

Variable Default Notes
CRM_DATABASE_URL sqlite+aiosqlite:///./crm.db Read by connections.yaml, not directly by the app.
CRM_CONNECTIONS_FILE connections.yaml Where the connection definitions live.
CRM_MODULES — Optional modules to enable, comma separated. Additive: core_identity and core_access load regardless.
CRM_TIMELINE true Carry the activity panel -- notes and audit history -- on record pages. Needs a database of this application's own; turn it off wherever db.main is not there.
CRM_MAX_LOCAL_ROWS 5000 Rows the capability shim may hold in memory to emulate a query stage. Exceeding it raises rather than truncating.
CRM_DEFAULT_PAGE_SIZE 25
CRM_DB_POOL_SIZE / CRM_DB_MAX_OVERFLOW / CRM_DB_POOL_RECYCLE 10 / 20 / 1800 Read by connections.yaml. See scaling.md.
CRM_ARCHIVE_ENABLED false Turns on the second database connections.yaml defines. A disabled connection is never opened, so this costs nothing until you want it.
CRM_ARCHIVE_URL sqlite+aiosqlite:///./crm-archive.db Its URL. Each database keeps its own migration history; see multiple-databases.md.

Caching

Off by default. See scaling.md for what is cached and what deliberately is not.

Variable Default Notes
CRM_CACHE_BACKEND none none, memory or redis.
CRM_REDIS_URL redis://localhost:6379/0 Needs the redis package: uv add redis.

Files

Variable Default Notes
CRM_FILE_BACKEND local local, memory or s3. Local disk does not work across hosts.
CRM_FILE_ROOT var/files Where the local backend writes.

S3 credentials and bucket go in connections.yaml.

Notifications

Variable Default Notes
CRM_TASK_WATCHERS — Addresses told about a task that is due and assigned to nobody. Empty falls back to everybody in the staff directory (Registry.staff_directory), where a module declares one; with neither, nobody is told and the task waits.
CRM_NOTIFY_CHANNELS inapp inapp, email, webhook, console. The bell always works; the rest are opt-in.
CRM_JOBS_CONNECTION db.main Which configured connection holds the job queue. Point it at a second database to keep a busy queue's writes off the one serving requests; crm migrate --all then covers both.
CRM_NOTIFY_DELIVERY background How a notification reaches its channels. background is a task on this worker's event loop — immediate, and lost if the worker stops. queue hands it to the durable job queue, which survives a restart but needs crm worker running. inline delivers before the request returns.
CRM_NOTIFY_BASE_URL http://localhost:8000 Used to build links in messages that leave the application.
CRM_SMTP_HOST / CRM_SMTP_PORT localhost / 25
CRM_SMTP_USERNAME / CRM_SMTP_PASSWORD —
CRM_SMTP_TLS false
CRM_SMTP_SENDER crm@localhost
CRM_EMAIL_MIN_PRIORITY high Email only at this priority and above. A CRM that emails everything is one people filter into a folder they never read.
CRM_NOTIFY_WEBHOOK_URL —
CRM_NOTIFY_WEBHOOK_STYLE slack

Performance

Covered in scaling.md.

Variable Default Notes
CRM_WARN_QUERIES 25 A request costing more queries than this logs a line naming the statement that repeated most.
CRM_WARN_REQUEST_MS 1000 The same line, for elapsed time. Two thresholds because they catch different faults.
CRM_PERMISSION_CACHE_TTL 30 Without a shared cache, this is how long a permission change takes to reach other workers.
CRM_GZIP_MIN_SIZE 1000 Bytes. Below roughly a kilobyte, compression costs more than it saves.
CRM_GZIP_LEVEL 6 Not zlib's default of 9: on HTML that is 2% smaller for four times the CPU.
CRM_STATIC_MAX_AGE 3600 Short because assets have stable names. Raise it once filenames carry a content hash.
CRM_SHUTDOWN_TIMEOUT 10 Seconds to wait for in-flight background deliveries before exiting.

Presentation

Variable Default Notes
CRM_TEMPLATE_RELOAD true Turn off in production; crm serve insists.
CRM_TIMEZONE UTC For anyone with no preference of their own. Not the server's zone, which is an accident of where it runs.
CRM_DATE_FORMAT %d %b %Y
CRM_DATETIME_FORMAT %d %b %Y, %H:%M
CRM_CURRENCY_SYMBOL $
CRM_MAP_TILE_URL OpenStreetMap Tiles for map views. The public OSM server is fine while you are building and against its usage policy for a deployment of any size -- point it at your own or a commercial one. Empty turns a map into a list of located records, which is also what an air-gapped install wants: tiles are the only thing on any page that leaves the network.
CRM_MAP_ATTRIBUTION © OpenStreetMap contributors Credit shown on the map. Change it with the tile server.

Production checks

Settings.check() runs at startup and is what crm serve refuses on. It reports a generated secret key, dev auth left on, proxy_header without trusted networks, insecure cookies, and template reload — each of which is correct in development and wrong in production, which is exactly the class of mistake that survives review and fails in deployment.

uv run crm serve      # refuses, and says which settings are the problem