Diagnosing node-postgres connection timeouts at startup
Covers distinguishing pool exhaustion from a genuine reachability/auth failure, which node-postgres reports identically, and the immediate config-side responses to each. Does not cover a pool that starts healthy and exhausts gradually under sustained traffic from a code-level leak — that's the missing-release playbook, a different root cause with the same visible error.
`Error: timeout exceeded when trying to connect` thrown from Pool.connect or a query
Happens right at startup or under a burst of concurrent requests (e.g. parallel health checks)
Works fine with a single connection or at low concurrency
The diagnostic path
7 steps. Every step is written out below in full — the interactive version simply follows the branches for you.
Step 1 · StartPool.connect (or a query) timed out
node-postgres uses this same message and the same connectionTimeoutMillis both for establishing a brand-new physical connection and, in recent versions, for a caller waiting in the pool's internal queue for one to become available — so the message alone doesn't tell you which happened.
What happens next
passed → step 2, Check whether the pool itself is the bottleneck
Step 2 · TestCheck whether the pool itself is the bottleneck
Sample the pool's own counters at (or just after) the moment of the timeout.
passed → step 3, The pool is maxed out and callers are queued
failed → step 4, The pool never got a connection at all
unknown → step 4, The pool never got a connection at all
Step 3 · Root causeThe pool is maxed out and callers are queued
idleCount is 0, totalCount is at (or near) max, and waitingCount is above 0 — every connection is checked out and this caller is genuinely waiting for one. This is most often a burst of concurrent callers at startup (e.g. several parallel health checks or a stampede of cold starts) rather than a leak, since the pool was otherwise healthy moments before.
What happens next
passed → step 5, Raise max to match real peak concurrency, or serialise the startup burst
Step 4 · Root causeThe pool never got a connection at all
If totalCount and idleCount are both near zero and nothing is genuinely waiting on pool capacity, the pool isn't the bottleneck — the underlying TCP/auth handshake itself is failing to complete in time. Check basic reachability from this exact environment (DNS resolution, firewall/security-group egress, TLS requirements) and that credentials are correct, e.g. with psql from the same host. That investigation is outside this playbook.
Step 5 · FixRaise max to match real peak concurrency, or serialise the startup burst
If many callers open connections concurrently at boot, either raise max to accommodate genuine peak concurrency, or make startup checks share a single connection/query rather than one each. Raising connectionTimeoutMillis alone is only a stopgap — it changes how long callers wait, not how many connections are available.
Read-onlyjavascript
const pool = new Pool({ max: 20, connectionTimeoutMillis: 5000 });
What happens next
passed → step 6, Re-check pool counters under the same load pattern
Step 6 · Verify the fixRe-check pool counters under the same load pattern
Reproduce the same startup burst and sample again.