Skip to content
Kutrion
Browse docs

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

VariableTypeDefaultDescription
NODE_ENVstringdevelopmentRuntime mode (development or production); production enables the boot guards.
PORTnumber4000Tenant API + web portal port.
LOG_LEVELstringinfoPino log level (trace, debug, info, warn, error, fatal, silent).
WEB_DISTpath(empty)Built web portal directory to serve; empty means the portal is not served (dev uses Vite).
APP_BASE_URLURLhttp://localhost:5173Public origin (including any base path) for user-facing links (invites, verification); WebAuthn and cookies pin to it.
API_PUBLIC_URLURL(APP_BASE_URL origin)Override when the API is on a different host than APP_BASE_URL.
HTTP_HANDLER_TIMEOUT_MSnumber90000Longest an ordinary request may run before it is answered 503 with request_timeout.
HTTP_LONG_HANDLER_TIMEOUT_MSnumber21600000The same bound for uploads, server-side transfers, remote commands and AI calls, which run as long as their work takes.
HTTP_REQUEST_TIMEOUT_MSnumber21600000Longest the server waits to receive a whole request, body included. Never lower than the long handler bound.
HTTP_CONNECTION_TIMEOUT_MSnumber900000Closes a connection that sends and receives nothing for this long. Open terminal sessions are not affected.

Database, cache & observability

VariableTypeDefaultDescription
DATABASE_URLstring (required)(empty)PostgreSQL connection URL.
PG_POOL_MAXnumber8Max Postgres connections per pool.
PG_IDLE_TIMEOUT_MSnumber10000Idle connection timeout.
PG_STATEMENT_TIMEOUT_MSnumber30000Postgres statement_timeout on every pooled connection. A statement cut off by it answers 503 with database_timeout.
PG_JOB_STATEMENT_TIMEOUT_MSnumber300000The statement bound for background sweeps and maintenance scripts, which scan whole tables.
PG_LOCK_TIMEOUT_MSnumber15000Postgres lock_timeout: how long a statement waits for a lock.
PG_IDLE_IN_TRANSACTION_TIMEOUT_MSnumber60000Postgres ends a session left idle inside an open transaction for this long.
REDIS_URLstring (required)(empty)Redis connection URL.
REDIS_KEY_PREFIXstring (required)(empty)Namespace every Redis key is written under. Must end with :.
REDIS_COMMAND_TIMEOUT_MSnumber3000A Redis command with no reply by then fails, and the request answers 503 with cache_timeout.
REDIS_CONNECT_TIMEOUT_MSnumber5000How long a connection attempt to Redis may take.
METRICS_ENABLEDbooleantrueEnable the Prometheus /metrics endpoint.
METRICS_TOKENstring(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.

VariableTypeDefaultDescription
EGRESS_ALLOW_PRIVATEboolean (0/1)0Allow outbound fetches to RFC1918/loopback targets (internal SSO, webhooks). Off unless set to 1, under every NODE_ENV, to prevent SSRF.
BROKER_PRIVATE_TARGETSallow / blockallowWhether 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_LOOPBACKboolean (0/1)0Allow 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_CIDRScomma-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_ORIGINScomma-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_DIRECTIVESstring(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
VariableTypeDefaultDescription
RDP_GATEWAY_URLURL(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_KEYsecret(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_TOKENsecret(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_PATHpath(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.