{"record":{"id":"590de88b787a67cd","repo":"n8n-io/n8n","slug":"database-connection-timed-out","errorCode":null,"errorMessage":"Database connection timed out","messagePattern":"Database connection timed out","errorType":"exception","errorClass":"OperationalError","httpStatus":null,"severity":"error","filePath":"packages/@n8n/db/src/connection/db-connection-monitor.ts","lineNumber":280,"sourceCode":"\n\t/**\n\t * Races `work` against `timeoutMs`, defaulting to `pingTimeoutMs`. Throws\n\t * OperationalError on timeout so the \"don't report timeouts to Sentry\" rule in\n\t * `ping()` applies. The timer is always cancelled in `finally` so it never leaks\n\t * when `work` wins.\n\t */\n\tprivate async raceTimeout<T>(\n\t\twork: Promise<T>,\n\t\ttimeoutMs = this.databaseConfig.pingTimeoutMs,\n\t): Promise<T> {\n\t\tconst abortController = new AbortController();\n\t\ttry {\n\t\t\treturn await Promise.race([\n\t\t\t\twork,\n\t\t\t\tsetTimeoutP(timeoutMs, undefined, {\n\t\t\t\t\tsignal: abortController.signal,\n\t\t\t\t}).then(() => {\n\t\t\t\t\tthrow new OperationalError('Database connection timed out');\n\t\t\t\t}),\n\t\t\t]);\n\t\t} finally {\n\t\t\tabortController.abort();\n\t\t}\n\t}\n\n\t/** Destroys a pg pool client by releasing it with an error, immediately freeing its pool slot. Never throws. */\n\tprivate safeDestroyClient(client: PgPoolClient): void {\n\t\ttry {\n\t\t\tclient.release(new Error('n8n ping timed out; destroying connection to free pool slot'));\n\t\t} catch (error) {\n\t\t\tthis.logger.warn(\n\t\t\t\t`Failed to destroy timed-out ping connection: ${ensureError(error).message}`,\n\t\t\t);\n\t\t}\n\t}\n","sourceCodeStart":262,"sourceCodeEnd":298,"githubUrl":"https://github.com/n8n-io/n8n/blob/5ac6606e81f67bb9534255570cd4e86fd8101eee/packages/@n8n/db/src/connection/db-connection-monitor.ts#L262-L298","documentation":"OperationalError thrown by DbConnectionMonitor.raceTimeout when the database ping does not complete within databaseConfig.pingTimeoutMs. The ping and the timeout race via Promise.race; the timeout firing first means the connection is unresponsive. Uses AbortController so the timer is cleared if the work wins. OperationalError signals a transient/expected condition the system should handle gracefully.","triggerScenarios":"The monitor issues a periodic ping against a pg pool client; if the round-trip exceeds pingTimeoutMs (default configured in the DB config block), the timeout branch wins and rejects. Happens under network stalls, an overloaded Postgres, a saturated pool, DNS hiccups, or a firewall dropping idle connections.","commonSituations":"Latency between n8n and Postgres spiking (cross-AZ/region); Postgres max_connections reached so acquisition itself stalls; pg_bouncer stuck; a VPN/network partition; the pingTimeoutMs tuned too low for a high-latency link; cold starts after DB failover.","solutions":["Increase DB pingTimeoutMs in config to a value comfortably above observed p95 ping latency.","Check Postgres health: `SELECT * FROM pg_stat_activity;` for long-running queries holding connections, and confirm max_connections isn't exhausted.","Verify network path: `psql -h <host> -c 'SELECT 1'` with timing; look for packet loss or high latency on the link.","If using PgBouncer, confirm pool size and `pool_mode` are adequate and not queueing.","Treat as transient — DbConnectionMonitor initiates recovery; ensure the n8n process has enough headroom (memory/CPU) to ride through."],"exampleFix":"// before\nDB_POSTGRESDB_PING_TIMEOUT_MS=2000\n\n// after\nDB_POSTGRESDB_PING_TIMEOUT_MS=10000","handlingStrategy":"retry","validationCode":"// before relying on the connection, probe with a short timeout\nconst ok = await Promise.race([\n  pgPool.query('SELECT 1').then(() => true).catch(() => false),\n  setTimeoutP(pingTimeoutMs).then(() => false),\n]);\nif (!ok) { /* defer work, alert, or fall back */ }","typeGuard":"function isOperationalTimeout(err: unknown): boolean {\n  return err instanceof OperationalError && err.message === 'Database connection timed out';\n}","tryCatchPattern":"try {\n  await runWithDb(dbWork);\n} catch (err) {\n  if (isOperationalTimeout(err)) {\n    // back off and retry; the monitor is already recovering\n    await backoffRetry(dbWork);\n  } else throw err;\n}","preventionTips":["Size pingTimeoutMs to your real network p95 plus headroom.","Monitor pg_stat_activity for connection saturation before it becomes timeouts.","Keep an explicit retry budget so transient DB timeouts don't fail user requests."],"tags":["database","postgres","timeout","network","transient"],"backgroundTag":null,"analyzedSha":"5ac6606e81f67bb9534255570cd4e86fd8101eee","analyzedAt":"2026-08-12T05:26:35.080Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}