n8n-io/n8n · error · OperationalError

Database connection timed out

Error message

Database connection timed out

What it means

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.

Source

Thrown at packages/@n8n/db/src/connection/db-connection-monitor.ts:280

	/**
	 * Races `work` against `timeoutMs`, defaulting to `pingTimeoutMs`. Throws
	 * OperationalError on timeout so the "don't report timeouts to Sentry" rule in
	 * `ping()` applies. The timer is always cancelled in `finally` so it never leaks
	 * when `work` wins.
	 */
	private async raceTimeout<T>(
		work: Promise<T>,
		timeoutMs = this.databaseConfig.pingTimeoutMs,
	): Promise<T> {
		const abortController = new AbortController();
		try {
			return await Promise.race([
				work,
				setTimeoutP(timeoutMs, undefined, {
					signal: abortController.signal,
				}).then(() => {
					throw new OperationalError('Database connection timed out');
				}),
			]);
		} finally {
			abortController.abort();
		}
	}

	/** Destroys a pg pool client by releasing it with an error, immediately freeing its pool slot. Never throws. */
	private safeDestroyClient(client: PgPoolClient): void {
		try {
			client.release(new Error('n8n ping timed out; destroying connection to free pool slot'));
		} catch (error) {
			this.logger.warn(
				`Failed to destroy timed-out ping connection: ${ensureError(error).message}`,
			);
		}
	}

View on GitHub (pinned to 5ac6606e81)

Solutions

  1. Increase DB pingTimeoutMs in config to a value comfortably above observed p95 ping latency.
  2. Check Postgres health: `SELECT * FROM pg_stat_activity;` for long-running queries holding connections, and confirm max_connections isn't exhausted.
  3. Verify network path: `psql -h <host> -c 'SELECT 1'` with timing; look for packet loss or high latency on the link.
  4. If using PgBouncer, confirm pool size and `pool_mode` are adequate and not queueing.
  5. Treat as transient — DbConnectionMonitor initiates recovery; ensure the n8n process has enough headroom (memory/CPU) to ride through.

Example fix

// before
DB_POSTGRESDB_PING_TIMEOUT_MS=2000

// after
DB_POSTGRESDB_PING_TIMEOUT_MS=10000
Defensive patterns

Strategy: retry

Validate before calling

// before relying on the connection, probe with a short timeout
const ok = await Promise.race([
  pgPool.query('SELECT 1').then(() => true).catch(() => false),
  setTimeoutP(pingTimeoutMs).then(() => false),
]);
if (!ok) { /* defer work, alert, or fall back */ }

Type guard

function isOperationalTimeout(err: unknown): boolean {
  return err instanceof OperationalError && err.message === 'Database connection timed out';
}

Try / catch

try {
  await runWithDb(dbWork);
} catch (err) {
  if (isOperationalTimeout(err)) {
    // back off and retry; the monitor is already recovering
    await backoffRetry(dbWork);
  } else throw err;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Related errors


AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12). Data as JSON: /api/errors/590de88b787a67cd. Report an issue: GitHub.