---
title: "Configuration reference"
---

# Configuration reference

The environment variables of the Neural Inverse Govern server, grouped by area, with defaults and what happens when a value is missing or invalid.

All configuration is environment variables. In Docker Compose they come from `.env` (start from `.env.example`); on Kubernetes from the chart's values and the Secrets in `secrets.*`. An invalid value usually stops the server with a message naming the variable.

**Production by default.** With `NI_AUTH_DEPLOY_ENV` unset, the server refuses to start when a required key is missing, shorter than required, all zero bytes, or a placeholder (anything containing `change-me`, `changeme`, `change-in-prod`, `replace-with`, `REPLACE_`, `dev-key` or `dev-secret`). `development`, `dev`, `local` or `test` accepts them with a warning at every start. Never set it on a shared deployment.

Specialised settings have their own pages: [Context settings](https://neuralinverse.com/docs/context/settings/), [audit tiering](https://neuralinverse.com/docs/evidence/retention-and-tiering/), [client events](https://neuralinverse.com/docs/evidence/client-events/), [selfguard](https://neuralinverse.com/docs/deploy/monitoring/), [telemetry pipeline](https://neuralinverse.com/docs/deploy/telemetry-pipeline/), [BYOC compute](https://neuralinverse.com/docs/deploy/byoc/).

## Server and URLs

| Variable | Default | Meaning |
|---|---|---|
| `NI_AUTH_HTTP_ADDR` | `:8080` | Listen address |
| `NI_AUTH_PUBLIC_URL` | `http://localhost:8080` | Public URL: OAuth redirects, the OIDC issuer, email links. HTTPS in production |
| `NI_AUTH_API_PUBLIC_URL` | `NI_AUTH_PUBLIC_URL` | Public URL of the API when it is on its own host; used for every client-facing URL that is not a sign-in path (model and MCP gateways, downloads, the API reference) |
| `NI_AUTH_JWT_ISSUER` | `http://localhost:8080` | Token `iss`; must equal `NI_AUTH_PUBLIC_URL` |
| `NI_AUTH_JWT_AUDIENCE` | `ni-auth-api` | Token `aud` |
| `NI_AUTH_REGISTRATION_MODE` | `invite` | `invite` (an invitation is required) or `open` |
| `NI_AUTH_SUPER_ADMIN_EMAILS` | empty | Emails that receive super admin on registration; empty: nobody, there is no first-user bootstrap |
| `NI_AUTH_CORS_ALLOWED_ORIGINS` | empty | Browser origins allowed cross-origin; empty disables CORS |
| `NI_AUTH_PASSKEY_RP_ID`, `NI_AUTH_PASSKEY_ORIGINS` | | Passkey relying party and allowed origins |
| `NI_AUTH_TERMINAL_ORIGINS` | empty | Origins the workspace web terminal accepts; unset, it is unavailable |
| `NI_AUTH_SAAS_ENABLED` | `false` | The hosted service's plans and billing; off for self-hosted |
| `NI_ASSISTANT_ENABLED` | `true` | `false` turns the console assistant off for the whole deployment |
| `NI_REGION_ID` | `local` | The cell this installation is; self-hosted keeps `local` |
| `NI_AUTH_DEPLOY_ENV` | unset (production) | See above |

## Database

| Variable | Meaning |
|---|---|
| `DATABASE_URL` | `keystone_app`, the runtime role, under row-level security. A superuser or `BYPASSRLS` role is refused |
| `DATABASE_PLATFORM_URL` | `keystone_platform`, for the named cross-organisation tasks. Required, must differ from `DATABASE_URL` |
| `DATABASE_OWNER_URL` | `keystone_owner`, for migrations and restores only. Never give it to the running server |
| `KEYSTONE_OWNER_PASSWORD`, `KEYSTONE_APP_PASSWORD`, `KEYSTONE_PLATFORM_PASSWORD` | The three role passwords the bootstrap sets: at least 16 characters, all different (`openssl rand -hex 24`) |
| `POSTGRES_DB`, `POSTGRES_USER`, `POSTGRES_PASSWORD`, `POSTGRES_PORT` | Docker Compose's PostgreSQL service; change `POSTGRES_PASSWORD` |
| `NI_AUTH_DB_MAX_OPEN` | Open connections per server process (default 25; the Helm chart sets `database.poolPerPod`, default 50). Keep replicas times this below PostgreSQL's `max_connections` |
| `NI_AUTH_DB_MAX_IDLE` | Idle connections (default: the open limit) |
| `NI_AUTH_DB_MAX_LIFETIME`, `NI_AUTH_DB_MAX_IDLE_TIME` | Connection lifetime (default `5m`) and idle time (default `1m`), Go durations |

Use PgBouncer in session mode only; transaction mode is not supported, because the tenant of each connection is a session setting.

## Keys and secrets

| Variable | Required | Generate | Purpose |
|---|---|---|---|
| `KMS_MASTER_KEY` | yes | `openssl rand -hex 32` | Seals the KMS keys, the audit signing keys and the manifest signing keys. Keep stable |
| `NI_AUTH_AUDIT_HMAC_KEY` | yes | `openssl rand -base64 48` | Audit hash chains. Keep stable |
| `NI_AUTH_AUDIT_SIGNING_KEY` | yes | `openssl rand -base64 48` | Audit signing |
| `NI_OBSERVABILITY_SIGNING_KEY` | yes | `openssl rand -base64 48` | Context and governance evidence chain. Keep stable |
| `NI_AUTH_API_KEY_PEPPER`, `NI_AUTH_OAUTH2_PEPPER`, `NI_AUTH_EMAIL_TOKEN_PEPPER` | yes | `openssl rand -base64 48` | Credential digests |
| `NI_AUTH_AGENT_PEPPER` | no | | Agent credential digests; empty uses `NI_AUTH_API_KEY_PEPPER` |
| `NI_AUTH_TOTP_ENCRYPTION_KEY`, `NI_AUTH_LDAP_ENCRYPTION_KEY`, `NI_AUTH_SOCIAL_ENCRYPTION_KEY`, `NI_AUTH_WEBHOOK_ENCRYPTION_KEY` | yes | `openssl rand -base64 32` | AES-256-GCM keys for secrets at rest |
| `NI_AUTH_VAULT_ENCRYPTION_KEY`, `NI_AUTH_SECRETS_ENCRYPTION_KEY` | yes | `openssl rand -base64 48` | AI provider keys and governed secrets; rotate by moving the old value to `*_RETIRED_KEYS` |
| `NI_AUTH_PASSPORT_KEY` | yes | `openssl rand -base64 48` | Model Passport signing |
| `NI_AUTH_APPROVAL_SECRET` | yes | `openssl rand -base64 48` | Approval tokens |
| `NI_AUTH_DLP_TOKEN_ENCRYPTION_KEY` | no | `openssl rand -base64 32` | DLP token vault; unset, tokenisation answers `503` |
| `NI_AUTH_MANIFEST_KEY_ROTATION_DAYS` | no | | Days between manifest signing key rotations (default 90) |

## Token signing

| Variable | Default | Meaning |
|---|---|---|
| `NI_AUTH_OIDC_PRIVATE_KEY_PEM`, `NI_AUTH_OIDC_PRIVATE_KEY_FILE` | Compose: `/run/secrets/oidc_private_key` | The OIDC signing key; the PEM takes precedence |
| `NI_AUTH_OIDC_KEY_ID` | | The `kid` of that key |
| `NI_AUTH_SIGNING_ALGORITHM` | `RS256` | `RS256` or `ES256` |
| `NI_AUTH_SIGNING_KEY_PEM`, `NI_AUTH_SIGNING_KEY_FILE`, `NI_AUTH_SIGNING_KEY_ID` | | An access token signing key instead of database-managed keys |
| `NI_AUTH_ACCESS_TOKEN_TTL`, `NI_AUTH_REFRESH_TOKEN_TTL` | `.env.example`: `15m`, `720h` | Token lifetimes |
| `NI_AUTH_SESSION_IDLE_TIMEOUT` | `168h` | Idle timeout of sessions |
| `NI_AUTH_CONSOLE_CLIENT_ID`, `NI_AUTH_CONSOLE_SESSION_IDLE_TIMEOUT`, `NI_AUTH_CONSOLE_SESSION_MAX_LIFETIME` | , `1h`, `12h` | The console's OAuth client and its session limits |

## Signing keys of optional features

Each is an Ed25519 PKCS#8 PEM (`openssl genpkey -algorithm ed25519`) with a key id kept stable until you rotate.

| Variables | Without them |
|---|---|
| `NI_AUTH_MODELS_SIGNING_KEY_PEM`, `NI_AUTH_MODELS_SIGNING_KEY_ID` | `/api/v1/models/resolved` answers `503` |
| `NI_AUTH_AGENTPOLICY_SIGNING_KEY_PEM`, `NI_AUTH_AGENTPOLICY_SIGNING_KEY_ID` | The resolved agent policy answers `503` |
| `KEYSTONE_MCP_SIGNING_KEY_PEM` (or `_FILE`), `KEYSTONE_MCP_SIGNING_KEY_ID` | `/api/v1/mcp/resolved` answers `503` |
| `NI_AUTH_HARNESS_SIGNING_KEY_PEM`, `NI_AUTH_HARNESS_SIGNING_KEY_ID` | Harness packs can be read and assigned but not published |

## Email

`NI_AUTH_SMTP_HOST`, `NI_AUTH_SMTP_PORT`, `NI_AUTH_SMTP_STARTTLS`, `NI_AUTH_SMTP_USERNAME`, `NI_AUTH_SMTP_PASSWORD`, `NI_AUTH_SMTP_FROM`, `NI_AUTH_SMTP_INSECURE_SKIP_VERIFY`, `NI_AUTH_SIGN_IN_URL`. See [Email](https://neuralinverse.com/docs/deploy/email/).

## Proxies and client addresses

`NI_AUTH_TRUSTED_PROXIES`, `NI_AUTH_TRUSTED_CDN_PROXIES`, `NI_AUTH_CLIENT_IP_HEADER`, `NI_AUTH_CLIENT_COUNTRY_HEADER`, `NI_AUTH_CONSOLE_CONTEXT_KEYS`. See [Client IP addresses](https://neuralinverse.com/docs/deploy/client-ip/).

## Outbound connections

| Variable | Default | Opens |
|---|---|---|
| `NI_AUTH_OUTBOUND_ALLOW_PRIVATE_CIDRS` | empty | Internal networks that webhooks, approval channels, SIEM integrations, LDAP and Active Directory, inference servers and vendor connectors may reach. Overrides every built-in rule, metadata addresses included: keep it narrow. An invalid value is logged and ignored |
| `NI_AUTH_MODELS_PRIVATE_NETWORKS` | empty | Model provider connections on internal networks |
| `KEYSTONE_MCP_ALLOW_PRIVATE_NETWORKS` | empty | MCP servers on internal networks |
| `NI_AUTH_WEBHOOK_ALLOW_PRIVATE` | `false` (server) | Organisation webhooks to private and loopback addresses. The Docker Compose file defaults it to `true` when unset; `.env.example` sets `false` |

## Storage

| Variable | Default | Meaning |
|---|---|---|
| `NI_OBJECT_STORE_DIR` | Compose: `/var/lib/keystone/objects` | Shared directory for exports and artifacts; with several replicas, a volume all of them mount |
| `NI_OBJECT_STORE_S3_BUCKET`, `_REGION`, `_KMS_KEY_ID`, `_PREFIX`, `_ENDPOINT` | | An S3 or S3-compatible bucket instead |
| `NI_AUTH_AUDIT_EXPORT_MAX_ROWS` | 1,000,000 | Largest audit export (1 to 50,000,000) |
| `NI_AUTH_AUDIT_EXPORT_RETENTION_DAYS` | 7 | Days an export file is kept (1 to 365) |
| `NI_AUTH_AUDIT_EXPORT_DIR` | | Where earlier releases kept export files; moved to the object store at start |

## Audit ledger

| Variable | Default | Meaning |
|---|---|---|
| `NI_AUDIT_CHAIN_LANES` | 8 | Lanes per organisation chain, unless the organisation sets its own |
| `NI_AUDIT_GROUP_COMMIT_WINDOW` | `2ms` | Group commit window, 1 to 5 ms |
| `NI_AUDIT_GROUP_COMMIT_MAX_IN_FLIGHT` | 8 | Group commit shards per replica |
| `NI_AUDIT_GROUP_COMMIT_SPLIT_BY_CHAIN` | | `true` commits each chain of a batch in its own transaction |
| `NI_AUTH_AUDIT_TIERING_*`, `NI_AUTH_AUDIT_HOT_WINDOW_DAYS`, `NI_AUTH_AUDIT_ARCHIVE_YEARS` | | See [Retention and tiering](https://neuralinverse.com/docs/evidence/retention-and-tiering/) |

## Backups

`NI_AUTH_BACKUP_ENCRYPTION_KEY`, `NI_AUTH_BACKUP_S3_BUCKET`, `NI_AUTH_BACKUP_S3_PREFIX`, `NI_AUTH_BACKUP_S3_REGION`, `NI_AUTH_BACKUP_S3_ENDPOINT`, `NI_AUTH_BACKUP_S3_ACCESS_KEY`, `NI_AUTH_BACKUP_S3_SECRET_KEY`. See [Backup and restore](https://neuralinverse.com/docs/deploy/backup-restore/).

## Monitoring

| Variable | Meaning |
|---|---|
| `NI_AUTH_METRICS_TOKEN` | Bearer token for Prometheus scrapes of `/metrics`, `/metrics/cloud`, `/metrics/selfguard` and the rollout gate; at least 32 bytes. Empty: only super admins can read them |
| `NI_AUTH_SELFGUARD_*` | See [Monitoring and self-healing](https://neuralinverse.com/docs/deploy/monitoring/) |

## Air gap

| Variable | Meaning |
|---|---|
| `NI_AIRGAP_LICENSE_PUBLIC_KEY`, `NI_AIRGAP_LICENSE_PUBLIC_KEY_FILE` | The Ed25519 public key offline licences are verified with; set at most one. See [Air-gapped installation](https://neuralinverse.com/docs/deploy/air-gapped/) |

## Workers

| Variable | Meaning |
|---|---|
| `WORKER_SHARED_SECRET` | Authenticates sandbox workers to the internal worker API (port 9090) |
| `WORKER_API_BIND` | Docker Compose: the address port 9090 is published on (default `127.0.0.1`). Never a public or all-interfaces address |
