configuration-reference
Core & database configuration
Server, networking, PostgreSQL, Redis, observability, and egress variables — the foundation every Kutrion instance needs to boot.
Updated Oct 7, 2026
These variables wire Kutrion to its runtime, its datastores, and the network. At minimum a working
instance needs DATABASE_URL and REDIS_URL; everything else has a sensible default. PostgreSQL holds
all durable state (encrypted secrets, tenants, audit trail) and Redis backs sessions, rate limiting,
and ephemeral broker state. See Configuration overview for how these fit
together and where the .env lives.
NODE_ENV=production
PORT=4000
LOG_LEVEL=info
APP_BASE_URL=https://vault.example.com
DATABASE_URL=postgres://vaultterm:[email protected]:5432/vaultterm
REDIS_URL=redis://cache.example.com:6379
REDIS_KEY_PREFIX=prod:
METRICS_ENABLED=true
METRICS_TOKEN=replace-with-a-scrape-token
Server & networking
| Variable | Type | Default | Description |
|---|---|---|---|
NODE_ENV | string | development | Runtime mode (development or production); production enables the boot guards. |
PORT | number | 4000 | Tenant API + web portal port. |
LOG_LEVEL | string | info | Pino log level (trace, debug, info, warn, error, fatal, silent). |
WEB_DIST | path | (empty) | Built web portal directory to serve; empty means the portal is not served (dev uses Vite). |
APP_BASE_URL | URL | http://localhost:5173 | Public origin (including any base path) for user-facing links (invites, verification); WebAuthn and cookies pin to it. |
API_PUBLIC_URL | URL | (APP_BASE_URL origin) | Override when the API is on a different host than APP_BASE_URL. |
HTTP_HANDLER_TIMEOUT_MS | number | 90000 | Longest an ordinary request may run before it is answered 503 with request_timeout. |
HTTP_LONG_HANDLER_TIMEOUT_MS | number | 21600000 | The same bound for uploads, server-side transfers, remote commands and AI calls, which run as long as their work takes. |
HTTP_REQUEST_TIMEOUT_MS | number | 21600000 | Longest the server waits to receive a whole request, body included. Never lower than the long handler bound. |
HTTP_CONNECTION_TIMEOUT_MS | number | 900000 | Closes a connection that sends and receives nothing for this long. Open terminal sessions are not affected. |
Database, cache & observability
| Variable | Type | Default | Description |
|---|---|---|---|
DATABASE_URL | string (required) | (empty) | PostgreSQL connection URL. |
PG_POOL_MAX | number | 8 | Max Postgres connections per pool. |
PG_IDLE_TIMEOUT_MS | number | 10000 | Idle connection timeout. |
PG_STATEMENT_TIMEOUT_MS | number | 30000 | Postgres statement_timeout on every pooled connection. A statement cut off by it answers 503 with database_timeout. |
PG_JOB_STATEMENT_TIMEOUT_MS | number | 300000 | The statement bound for background sweeps and maintenance scripts, which scan whole tables. |
PG_LOCK_TIMEOUT_MS | number | 15000 | Postgres lock_timeout: how long a statement waits for a lock. |
PG_IDLE_IN_TRANSACTION_TIMEOUT_MS | number | 60000 | Postgres ends a session left idle inside an open transaction for this long. |
REDIS_URL | string (required) | (empty) | Redis connection URL. |
REDIS_KEY_PREFIX | string (required) | (empty) | Namespace every Redis key is written under. Must end with :. |
REDIS_COMMAND_TIMEOUT_MS | number | 3000 | A Redis command with no reply by then fails, and the request answers 503 with cache_timeout. |
REDIS_CONNECT_TIMEOUT_MS | number | 5000 | How long a connection attempt to Redis may take. |
METRICS_ENABLED | boolean | true | Enable the Prometheus /metrics endpoint. |
METRICS_TOKEN | string | (empty) | Bearer token required to scrape /metrics; under production the endpoint fails closed without it. |
DATABASE_URL, REDIS_URL and REDIS_KEY_PREFIX are all required — the server will not serve
traffic without them.
REDIS_KEY_PREFIX has no default on purpose. Two instances pointed at one Redis logical database —
a staging and a production deployment, or two tenants of an operator running both — would otherwise
share one keyspace, and the keys at stake are session revocation, step-up proofs, WebSocket tickets,
SSO state and WebAuthn challenges. A default would make that collision the out-of-the-box behavior
and a silent one, so an unset value is refused at boot instead. Give each deployment its own value,
ending in :. Redis ACLs match on key pattern and have no per-database selector, so this prefix is
also what lets you scope a Redis user to ~prod:* rather than granting it the whole instance.
Tune PG_POOL_MAX to your database’s connection budget; the value is per pool, so account for multiple
application processes. For the metrics endpoint and scrape setup, see Observability.
Security and egress
These control how the instance treats cross-origin browser requests and server-side outbound fetches. The egress guard is part of the production hardening described in Configuration overview: it prevents a server-side request (a webhook, an SSO metadata fetch, a call to an organization’s own AI endpoint) from reaching internal infrastructure unless you have explicitly allowed it.
| Variable | Type | Default | Description |
|---|---|---|---|
EGRESS_ALLOW_PRIVATE | boolean (0/1) | 0 | Allow outbound fetches to RFC1918/loopback targets (internal SSO, webhooks). Off unless set to 1, under every NODE_ENV, to prevent SSRF. |
BROKER_PRIVATE_TARGETS | allow / block | allow | Whether brokered SSH, SFTP and RDP connections may reach private addresses (RFC1918, CGNAT, IPv6 ULA). Unset or allow permits them, which is what a self-hosted instance reaching its own hosts needs. Any other value is treated as block, so a typo fails closed. |
BROKER_ALLOW_LOOPBACK | boolean (0/1) | 0 | Allow brokered SSH, SFTP and RDP connections to loopback addresses, for a local test server in development. Ignored when NODE_ENV=production. Leave it unset on any instance users reach. |
SSH_BROKER_ALLOW_CIDRS | comma-separated CIDRs | (empty) | Private ranges that stay reachable for brokered connections while BROKER_PRIVATE_TARGETS blocks private targets. Has no effect when private targets are allowed. |
CORS_ORIGINS | comma-separated | (empty) | Browser CORS allowlist only; leave empty when the portal is served same-origin. Empty denies cross-origin in production (browser extensions are still admitted). Does not affect passkey origins (RP_ORIGIN) or the MCP origin guard (MCP_ALLOWED_ORIGINS). |
CSP_DIRECTIVES | string | (built-in) | Override the default Content-Security-Policy directives. |
If you run your SSO provider, webhook receivers, or an organization-level AI endpoint on a private
network reachable only from the Kutrion host, set EGRESS_ALLOW_PRIVATE=1 so those legitimate internal
calls succeed. The instance-wide OLLAMA_URL is operator configuration and bypasses the guard, so it
does not need this setting. Leave it at
the default if all your integrations are reachable over the public internet. For how tenant boundaries
are enforced once a request is inside the server, see Tenant isolation.
EGRESS_ALLOW_PRIVATE governs HTTP(S) and file-protocol fetches. Brokered SSH, SFTP and RDP connections
follow the broker target policy instead, described in Configuration overview:
private addresses there are decided by BROKER_PRIVATE_TARGETS and SSH_BROKER_ALLOW_CIDRS, and
loopback by BROKER_ALLOW_LOOPBACK. EGRESS_ALLOW_PRIVATE has no effect on them.
RDP gateway
Brokered RDP (Enterprise) needs the Kutrion RDP gateway, a separate service that runs guacd and carries
the remote desktop to the browser viewer. The server keeps the connection profile and the vault
credential, resolves the password when a session starts, and hands the gateway an encrypted connection
descriptor; the browser never receives the RDP password. Without the gateway, RDP connection profiles
can still be managed, but starting a session returns gateway_unconfigured.
The gateway listens on loopback only, so it must run on the same host as the Kutrion server, and the
server must be able to reach it at RDP_GATEWAY_URL.
RDP_GATEWAY_URL=http://127.0.0.1:8089
RDP_GATEWAY_CRYPT_KEY=replace-with-a-long-random-secret
RDP_GATEWAY_TOKEN=replace-with-a-different-long-random-secret
RDP_GATEWAY_RECORDING_PATH=/var/lib/kutrion-rdp/recordings
| Variable | Type | Default | Description |
|---|---|---|---|
RDP_GATEWAY_URL | URL | (unset) | Base URL of the gateway. The server uses it for the health check, the live viewer bridge (the same address over ws://) and fetching recordings. Unset disables RDP sessions. |
RDP_GATEWAY_CRYPT_KEY | secret | (unset) | Shared secret, any length. Both sides derive the AES-256 key that encrypts the connection descriptor, password included, from it. Required for the live viewer; the gateway refuses to start without it. Must be identical on the server and the gateway. |
RDP_GATEWAY_TOKEN | secret | (unset) | Shared bearer token. The server presents it to the gateway when fetching recordings, and the gateway presents it when reporting session start and end back to the server. Must be identical on the server and the gateway. |
RDP_GATEWAY_RECORDING_PATH | path | (unset) | Directory on the gateway host where guacd writes session recordings. Unset, RDP sessions are not recorded. |
Set RDP_GATEWAY_TOKEN. If it is empty, the server refuses the gateway’s session callbacks, so
sessions are never marked active or closed and no recording is linked to them, and the gateway serves
its recordings endpoint without any authentication to anything that can reach it. Generate it and
RDP_GATEWAY_CRYPT_KEY as separate random values (for example openssl rand -hex 32), and never reuse
either as another secret.
On the gateway side, RDP_BROKER_CALLBACK_URL must point at the server’s
/rdp/gateway/sessions route so session lifecycle reports reach it.