Supabase: "Max client connections reached"
Under load — or seemingly at random — requests start failing with "Max client connections reached" or timeouts. A restart clears it, traffic brings it back, and the database itself doesn't look busy.
- Supabase
- Lovable
- Bolt
- Webhooks
- Scheduled jobs
Root cause, in plain English
Every Supabase project caps concurrent connections based on its compute size, with Supavisor pooling clients in front of Postgres. Serverless and edge functions break the assumption behind that pool: each invocation can open its own connection, and instances that exit without closing leave clients parked until the pooler refuses new ones. Long transactions in session mode pin connections the same way. The database isn't overloaded — the seats in front of it are all taken.
How to fix it
Confirm at the database: select count(*) from pg_stat_activity grouped by application_name/state shows who's holding connections and whether they're idle.
Point serverless code at the transaction-mode pooler (port 6543 connection string), which hands a connection out per transaction instead of per client — this is the single biggest fix.
Cap each serverless client at one connection (e.g. connection_limit=1 in Prisma/Drizzle URLs) and reuse a module-scoped client across invocations instead of constructing one per request.
Close what you open: long-lived scripts and workers should release clients explicitly; check for code paths (errors, early returns) that skip cleanup.
If genuinely needed after the leaks are fixed, raise the pool size in database settings or move to a larger compute — capacity is the last resort, not the first.
How Nightlamp detects this automatically
- API canary
- HTTP status
- Heartbeat
An api_canary hits a database-backed endpoint at a steady interval, which is exactly how an exhausted pool reveals itself: intermittent 5xx/timeout responses that pass a casual manual check. An http_status monitor tracks error rate and latency trends on the public surface, and heartbeat checks on cron workers catch jobs dying mid-run when they can't get a connection.
Catch this before your customers do
Nightlamp runs these checks continuously against your live app and sends a plain-English diagnosis — not a wall of logs — the moment this pattern shows up.
Related patterns
Frequently asked questions
- Why does restarting fix it temporarily?
- A restart force-closes every leaked client, returning all seats to the pool. The code that leaks them is still deployed, so the pool fills again at a rate proportional to traffic — which is why it feels random and always comes back.
- What's the difference between session mode (5432) and transaction mode (6543)?
- Session mode gives each client a dedicated connection for its whole lifetime — predictable, but each serverless instance occupies a seat. Transaction mode lends a connection only for the duration of each transaction, so thousands of short-lived clients can share a small pool. Serverless almost always belongs on transaction mode, with the caveat that session-level features (prepared statements, LISTEN/NOTIFY) don't carry across.
- Should I just upgrade my compute size?
- Upgrading raises the cap, and if the cause is a leak you'll hit the new cap a little later under a little more load. Fix pooling and client reuse first; upgrade when legitimate concurrent demand — not leakage — is what's consuming connections.
Newsletter
Get new incident patterns as we publish them
One email when new failure patterns, fixes, and monitoring recipes for no-code and AI-built apps land. No fluff, unsubscribe any time.
Double opt-in. One-click unsubscribe. No spam, ever.