Skip to content

Settings

z4j reads settings from env vars (primary). See env vars for the exhaustive reference; this page groups the operator-facing settings by topic for skimming.

Setting Default Notes
Z4J_DATABASE_URL - (required) postgresql+asyncpg://user:pw@host/db
Z4J_DATABASE_STATEMENT_CACHE_SIZE 50 Per-connection cap on prepared statements, applied to both asyncpg's statement cache and SQLAlchemy's. 0 disables both.
Z4J_DATABASE_MAX_INACTIVE_CONNECTION_LIFETIME_SECONDS 60 SQLAlchemy pool_recycle. Shorter values rotate per-connection caches faster under sustained load.
Z4J_AUTO_MIGRATE true Read by z4j serve only, not a Settings field: run Alembic head migrations before the brain starts. Only false (in any case) skips them; set it for orchestrators that handle migrations separately.
Z4J_STARTUP_VERIFY_LOCK_TIMEOUT_MS 600000 How long a starting worker waits its turn for the audit-chain startup walk while sibling workers verify the same database. Scoped to that one transaction; the per-request Z4J_DB_LOCK_TIMEOUT_MS is unchanged. See scaling.

The connection pool is configurable through Z4J_DATABASE_POOL_SIZE (default 20) and Z4J_DATABASE_MAX_OVERFLOW (default 10). On PostgreSQL the brain refuses to start when the total is below what its enabled leader-gated workers need, because a pool that cannot seat them deadlocks rather than degrades. See environment variables.

Setting Default Notes
Z4J_SECRET - (required) Master HMAC signing key. Derives the per-project frame HMAC key, hashes agent tokens, and encrypts stored TOTP secrets. It does not sign sessions (that is Z4J_SESSION_SECRET below) and does not sign the audit log (that is Z4J_AUDIT_CHAIN_SECRET). At least 32 bytes; 64 hex chars recommended.
Z4J_SESSION_SECRET - (required) Session-cookie signing key, at least 32 bytes. Independent of Z4J_SECRET.
Z4J_PUBLIC_URL http://localhost:7700 Full public URL (https://z4j.example.com). The default only passes validation when Z4J_ENVIRONMENT is exactly dev; outside dev the value must use https:// (or Z4J_ALLOW_HTTP_PUBLIC_URL must be set), so it is effectively required. Validated: no whitespace, no userinfo, http(s) only.
Z4J_PREVIOUS_SECRETS - Comma-separated previous master secrets still accepted when verifying agent bearer tokens and decrypting stored secrets (TOTP secrets and notification channel configs). Writes use the new Z4J_SECRET. It does NOT cover frame signing, so rotation still requires re-credentialing every agent. Run z4j secrets rewrap to a clean report before dropping an entry; that re-encrypts every stored secret under the current master in one run. See incident response.
Z4J_PREVIOUS_SESSION_SECRETS - Comma-separated previous session secrets still accepted during cookie-rotation.
Z4J_AUDIT_CHAIN_SECRET - (required outside development) Dedicated audit-chain signing key, at least 32 bytes. Independent of Z4J_SECRET, with no fallback: the brain refuses to start without it outside development.
Z4J_AUDIT_CHAIN_PREVIOUS_SECRETS - Comma-separated previous audit-chain keys still accepted by the verifier during rotation. Writes use the current Z4J_AUDIT_CHAIN_SECRET.
Z4J_REDACTION_EXTRA_KEY_PATTERNS [] JSON array of extra key-name regexes the brain's redaction pass applies to every inbound event on top of the built-in patterns. Each entry is validated as a regex when settings load. See redaction.

Z4J_SECRET, Z4J_SESSION_SECRET and Z4J_AUDIT_CHAIN_SECRET must each be at least 32 bytes, and so must every entry in the three previous-secret lists; a shorter value refuses to load. A URL-valued setting that is not itself a secret (Z4J_PUBLIC_URL) refuses to load with user:password@ embedded in it, and the read-only settings page strips any such userinfo from every URL it renders.

The audit-log HMAC chain is signed with Z4J_AUDIT_CHAIN_SECRET, deliberately separate from Z4J_SECRET. See HMAC audit chain for why the separation matters and how activation works on an existing deployment.

Setting Default Notes
Z4J_DASHBOARD_IP_ALLOWLIST [] CIDRs the login route and every session-cookie request must come from.
Z4J_API_IP_ALLOWLIST [] CIDRs every Bearer API-key request must come from. Each key can carry its own allowed_cidrs on top; both must admit the request.
Z4J_AGENT_IP_ALLOWLIST [] CIDRs the agent transports (WebSocket and long-poll) must come from.

All three are JSON arrays of IPv4 or IPv6 CIDRs; a bare address means that one host, and a malformed entry refuses to start. An empty list means no restriction. The address matched is the one the brain resolved after Z4J_TRUSTED_PROXIES, so an X-Forwarded-For header only counts when it arrived from a declared proxy; from anywhere else the socket peer is what gets matched. Loopback is never implicitly exempt: a list that omits 127.0.0.1/32 or ::1/128 refuses the brain's own host too. Every refusal counts in z4j_auth_ip_denied_total{surface}. On the dashboard and API surfaces a refusal is 403 with error: "ip_denied" and writes an auth.ip_denied audit row each time. On the agent surface the long-poll routes answer the same 403 and the WebSocket hello is closed with code 4403, and the audit row is written at most once per source address every 10 minutes, shared between the two transports. See threat model for what the lists do and do not protect.

Setting Default Notes
Z4J_PASSWORD_MIN_LENGTH 12 Default 12; configurable down to the supported floor of 8.
Z4J_ARGON2_TIME_COST 3 argon2id time cost (iterations).
Z4J_ARGON2_MEMORY_COST 65536 64 MiB, in KiB.
Z4J_ARGON2_PARALLELISM 4 Threads.

SMTP servers are not configured via env vars. Each notification channel record carries its own smtp_host, smtp_port (default 587), smtp_user, smtp_pass, smtp_tls, from_addr and to_addrs fields. See notifications and smtp-presets.

Setting Default Notes
Z4J_RECONCILIATION_SWEEP_SECONDS 300 Seconds between reconciliation passes (default 5 min).
Z4J_RECONCILIATION_STALE_THRESHOLD_SECONDS 900 Minimum age of a task still in started, pending or retry before it is eligible for reconciliation, measured from started_at (falling back to received_at, then created_at). 60 to 86400 (default 15 min).
Setting Default Notes
Z4J_EVENT_RETENTION_DAYS 30 On PostgreSQL, days raw events rows live: the partition worker drops the daily partitions past this age. SQLite never deletes events rows. On both, the same value bounds the agent_status_history purge.
Z4J_AUDIT_RETENTION_DAYS 90 Days audit_log rows live before the retention worker or z4j audit prune removes them under the authenticated prune boundary. Minimum 1, no ceiling; above 3650 days the brain logs one startup WARNING and honours the value. Environment only, no write-back. See audit retention.
Z4J_AUDIT_RETENTION_BY_CLASS {} JSON object mapping an action class (auth, command, schedule, ...) to its own window in days; unlisted classes use Z4J_AUDIT_RETENTION_DAYS. Validated when settings load. A longer class window holds every row written after the oldest row it keeps; a shorter one only reaches rows older than every retained neighbour, because the chain only loses a contiguous prefix. See audit retention.
Z4J_AUDIT_CHAIN_VERIFY_ENABLED false Run the scheduled audit-chain verification worker. Off by default: verification walks every retained row, so an operator who has not asked for it does not pay for it. Leader-gated.
Z4J_AUDIT_CHAIN_VERIFY_INTERVAL_SECONDS 86400 Cadence for the scheduled verification. Floor 900 (15 minutes), ceiling 604800 (one week).

Background audit exports of any size and the scheduled chain-head anchor; see audit exports for the full list of sink settings.

Setting Default Notes
Z4J_EXPORT_SINK none none or local (a directory, Z4J_EXPORT_SINK_PATH). none disables export jobs and the head export. Copying the directory to other storage is your own tool's job.
Z4J_AUDIT_HEAD_EXPORT_INTERVAL_SECONDS 0 Write the authenticated chain head to the sink on this cadence, under a stable key plus a dated copy, for z4j audit verify --known-head. 0 disables; otherwise 60 to 604800. Needs a sink.

Off unless Z4J_AUDIT_WEBHOOK_URL is set. A leader-gated worker mirrors audit rows to the receiver from a durable cursor, in chain order and at least once for every row still in the audit table. Rows that retention prunes before they are delivered are never forwarded, and the unsigned development path (no audit-chain key) can skip a row committed late with an earlier timestamp; see audit webhook forwarding.

Setting Default Notes
Z4J_AUDIT_WEBHOOK_URL - Receiver URL; empty disables. Secret, because SIEM intake URLs embed tokens.
Z4J_AUDIT_WEBHOOK_HMAC_SECRET - Signs every row. Required with the URL, at least 32 bytes.
Z4J_AUDIT_WEBHOOK_BATCH_SIZE 100 Rows per pass; a full batch runs the next pass at once.
Z4J_AUDIT_WEBHOOK_POLL_INTERVAL_SECONDS 5.0 Poll cadence once caught up.
Z4J_AUDIT_WEBHOOK_MAX_BACKOFF_SECONDS 300.0 Ceiling on the doubling wait after consecutive failures.
Setting Default Notes
Z4J_METRICS_AUTH_TOKEN - (auto-minted on packaged SQLite) Bearer for /metrics. Auto-minted into ~/.z4j/secret.env by the packaged SQLite bootstrap only when the database file does not exist yet and no configuration source supplies it; an existing SQLite database whose configuration lacks it refuses to start. PostgreSQL deployments must set it explicitly or /metrics answers 401. z4j metrics-token prints; z4j metrics-token rotate rotates.
Z4J_METRICS_PUBLIC false 1 leaves /metrics open. Use only with a firewalled or proxy-authenticated endpoint.

Sentry is opt-in: pip install "z4j[sentry]" and set Z4J_SENTRY_DSN; without a DSN it is off even when the SDK is installed. OpenTelemetry tracing works the same way with z4j[otel] and Z4J_OTEL_EXPORTER_OTLP_ENDPOINT. See the Sentry and OpenTelemetry sections of environment variables. Application logs go to stdout as JSON; ship them with Fluent Bit / Vector / Loki / Datadog.

Setting Default Notes
Z4J_BOOTSTRAP_ADMIN_EMAIL - Skip the setup URL and provision an admin automatically.
Z4J_BOOTSTRAP_ADMIN_PASSWORD - Required with the email above. Eagerly popped from os.environ after use.