Deploy and operate
Documentation

Version Latest

Troubleshooting

Symptoms, likely causes and fixes for a Govern installation that will not start, cannot reach its database, sheds load, fails to send mail, or reports a broken chain.

Start with three questions: does the process run, can it reach PostgreSQL, and what do the logs and selfguard say?

# Docker Compose
docker compose ps
docker compose logs server --since 10m | grep -iE "error|panic|fatal|refused"
curl -s http://localhost:8080/healthz

# Kubernetes
kubectl get pods
kubectl logs <server pod> --since=10m | grep -iE "error|panic|fatal|refused"

# Selfguard, even while the database is down
curl -sS -H "Authorization: Bearer $NI_AUTH_METRICS_TOKEN" https://<your Govern host>/metrics/selfguard

The server does not start

SymptomCauseFix
Compose refuses to start and names a variableA required variable is emptySet it; generation commands are in Docker Compose
The server exits naming a keyA key is missing, too short, all zero bytes or a placeholder (change-me, replace-with, ...)Generate a real value. NI_AUTH_DEPLOY_ENV=development accepts placeholders for local development only
DATABASE_OWNER_URL is required from migrateMigrations run only as keystone_ownerSet DATABASE_OWNER_URL; see Upgrades and migrations
The server refuses the runtime roleDATABASE_URL is a superuser, has BYPASSRLS, owns objects, or equals DATABASE_PLATFORM_URLPoint DATABASE_URL at keystone_app and DATABASE_PLATFORM_URL at keystone_platform; run the role bootstrap
The server refuses to start because tables are not isolatedkeystone_tenancy_problems() returns rows: a migration did not finish, a grant was made by hand, or a partition lacks its parent's privilegesRun the role bootstrap and the migrations as the owner; as the superuser, read SELECT * FROM keystone_tenancy_problems();; revoke hand-made grants. Never grant a runtime role more to make it start
Refused for a repository passwordDATABASE_URL or DATABASE_PLATFORM_URL uses a development password shipped in the repositorySet real role passwords and run the bootstrap
Stops when a KMS-sealed key cannot be openedKMS_MASTER_KEY differs from the one the keys were sealed underRestore the original KMS_MASTER_KEY
Migrations stop: "Keystone requires PostgreSQL 15 or later"PostgreSQL 14 or olderUpgrade PostgreSQL first; nothing was changed
"database is dirty at version N"A migration was interruptedSee the dirty migration steps in Upgrades and migrations
Stops: audit tiering bucket missingThe ledger already has entries in the cold tierConfigure NI_AUTH_AUDIT_TIERING_BUCKET again
Stops: backup cannot startNI_AUTH_BACKUP_ENCRYPTION_KEY set, but the key, region or credentials are invalidFix them, or unset the key
Stops on a client IP settingNI_AUTH_CLIENT_IP_HEADER set without NI_AUTH_TRUSTED_CDN_PROXIES, or a catch-all rangeSee Client IP addresses

It runs, but requests fail

SymptomCauseFix
Sign-in returns 500No OIDC private keySet NI_AUTH_OIDC_PRIVATE_KEY_FILE or NI_AUTH_OIDC_PRIVATE_KEY_PEM
Tokens fail validationNI_AUTH_JWT_ISSUER does not equal NI_AUTH_PUBLIC_URL exactlyMake them identical
Logs show no tenant for this statement (SQLSTATE 42501)A code path ran without a tenant and failed closedReport it with the request id; do not grant the runtime role more
503 with X-Keystone-Shed: node_overloaded, or 429 with tenant_share_exceededAdmission control is shedding loadScale out; check that the shed traffic is batch (keystone_selfguard_shed_requests_total{priority}); follow the findings in Monitoring
remaining connection slots are reserved or too many clients alreadyPostgreSQL is out of connectionsFind who holds them: SELECT usename, application_name, count(*) FROM pg_stat_activity GROUP BY 1, 2 ORDER BY 3 DESC;. Lower NI_AUTH_DB_MAX_OPEN across replicas, or put PgBouncer (session mode) in front
Rate limits and lockouts hit every user at onceAll requests appear to come from the proxySet NI_AUTH_TRUSTED_PROXIES (and CDN settings)
Every request in the audit log shows the console hostThe console's signed client context is not verifiedSet NI_AUTH_CONSOLE_CONTEXT_KEYS

Database incidents

  • PostgreSQL unreachable. Selfguard raises probe.failing/database and recycles connections; /readyz turns not ready. Its history is buffered in memory and written in order when the database returns. Check that PostgreSQL runs and accepts the runtime role's credentials.
  • Sessions idle in a transaction (db.idle_in_transaction). Selfguard asks for approval to end them. Look at what they hold before approving: SELECT pid, now() - state_change AS idle_for, left(query, 120) FROM pg_stat_activity WHERE state LIKE 'idle in transaction%';. Then set idle_in_transaction_session_timeout on Govern's role.
  • Lock waits or a long transaction. Find the blocker: SELECT pid, pg_blocking_pids(pid), wait_event, left(query, 120) FROM pg_stat_activity WHERE wait_event_type = 'Lock'; and end it with pg_cancel_backend if it is not a migration.
  • Tenancy drift while running (job.failing on tenancy.drift). The next restart will refuse to start. Fix as in the table above before you restart.

Mail

Every failed send is logged with event=email_delivery_failed and a reason; GET /api/v1/admin/email/health shows the last failure and POST /api/v1/admin/email/probe tests the configuration.

ReasonFix
not_configuredSet NI_AUTH_SMTP_HOST
connect, timeoutCheck host, port and firewall; localhost needs a local mail server
tlsSTARTTLS is required by default; check NI_AUTH_SMTP_STARTTLS and the server certificate
authCheck the SMTP credentials
rejectedThe provider refused the sender or recipient (for example an unverified domain or the SES sandbox)
circuit_openSends are paused after repeated failures; fix the cause and the breaker closes by itself

Evidence

SymptomCauseFix
Every existing audit chain fails verificationNI_AUTH_AUDIT_HMAC_KEY changedRestore the original key
Every context evidence chain reported broken (observability.chain.broken)NI_OBSERVABILITY_SIGNING_KEY changed, or earlier evidence was signed with the public development keyRestore the original key; evidence signed with the development key cannot be trusted and stays reported
Tiering routes answer 503 tiering_unavailableTiering is not configuredExpected without NI_AUTH_AUDIT_TIERING_BUCKET
The audit.tiering job fails and every entry stays hotThe bucket lacks versioning, Object Lock or replication, or a range did not verifyRead the job's error in /metrics/selfguard; fix the bucket, or investigate the range before anything moves
An export download answers 409 export_integrityThe stored file is missing or no longer matches its manifestCheck the object store; with several replicas it must be shared
GET /api/v1/audit-logs lacks old entriesThey moved to the cold tierUse GET /api/v1/audit-logs/query or an export

Context Platform

SymptomCauseFix
Retrieval answers 503 context.misconfiguredA Context setting is invalidRead the start log; GET /api/v1/context/status shows configurationValid
Retrieval answers 503 context.authz.unavailableA stored deny rule does not parse, or the database failedGET /api/v1/context/deny-rules shows valid: false and the parser's message; fix the rule
Adding a source answers 503 context.indexer.not_configuredKEYSTONE_CONTEXT_CHUNK_MAX_TOKENS=0Unset it or set a chunk size
A source is in errorNo GitHub App credentials, the App is not installed on the repository, or GitHub refused the tokenGET /api/v1/context/sources/{id}/status shows lastError and lastRun; fix and re-index
Saving the GitHub App answers 409 context.github_app.vault_not_readyNo KV version 2 engine at secret/ in the organisation's vaultMount one
The feed answers 503 context.events.unavailableA KEYSTONE_CONTEXT_EVENTS_* value is invalidFix it; only the feed is affected
Feed streams end after seconds behind a proxyThe proxy buffers or times out the streamDisable buffering and raise its read timeout above 30 seconds
pgvector backend answers 503The extension is not installedInstall pgvector in your PostgreSQL image, or use the default sql backend

Before you escalate

Collect: the request id of a failing request, the server log lines around it, /metrics/selfguard from the affected node, open selfguard findings, and a diagnostic bundle (POST /api/v1/admin/selfguard/actions with capture_diagnostics). Record what you did; every selfguard action and approval is also in the audit log.

Last reviewed 17 October 2026View as Markdown

Troubleshooting · Docs · Neural Inverse