FATAL: sorry, too many clients already (Postgres / Supabase)

Filed under Supabase & databases

Most likely causeAbout 10 min · 6 steps

Your app used up every Postgres connection slot — typically serverless instances each opening direct connections that pile up faster than they close.

Move app traffic to the pooler (port 6543)
Prisma: connection_limit=1 and pgbouncer=true
Use one shared database client in code

Fastest route

Give your AI this prompt

Copy it, replace the bracketed part with your error, and send it.

Preview the prompt
My app uses Supabase Postgres and is failing with "FATAL: sorry, too many clients already" (or "remaining connection slots are reserved", error 53300), usually when traffic picks up. My database connection is set up like this: [say where DATABASE_URL comes from and whether the host contains "pooler" and the port — do NOT paste the password].

Please: 1) check whether my app is connecting directly (db host, port 5432) instead of through the Supavisor pooler, and switch the app connection string to the pooler in transaction mode (port 6543), 2) if I use Prisma, set connection_limit=1 and pgbouncer=true on the app URL and add a DIRECT_URL for migrations, wired into the schema, 3) find any code that creates a new database client per request and replace it with one shared client.

Constraints: do not fix this by upgrading the database plan, do not paste or print my database password anywhere, and change one thing at a time — tell me how to watch the connection count in the Supabase dashboard to confirm each change worked.

Fix it yourself

Follow the repair

  1. Confirm it is connections, and see who holds them. The error text is unambiguous — "too many clients already" (code 53300) or "remaining connection slots are reserved". If you can still get into the Supabase dashboard, the Database section shows current connections and what is using them; a wall of connections from your app's address confirms the diagnosis. Do not start by upgrading the plan: a bigger instance raises the limit but multiplies the same leak, and pooled connections solve it at the current tier in most cases.
  2. Switch app traffic to the pooler address. In the Supabase dashboard, open your project's Database settings and find the connection pooling (Supavisor) connection string — a different host ending in pooler.supabase.com. Your app code should use the transaction mode string on port 6543. Transaction mode hands a real connection to your app only for the length of a transaction, then returns it to the shared pool, so thousands of app instances share a small number of actual database connections:
    # Direct (what causes the pile-up) — db.yourproject.supabase.co:5432
    # Pooler, transaction mode (what fixes it) — note the host and port 6543
    postgres://postgres.yourproject:[password]@aws-0-[region].pooler.supabase.com:6543/postgres
    Update this in your hosting provider's environment settings (for example DATABASE_URL) and redeploy.
  3. If you use Prisma, cap the pool and add the pooler flag. Two connection strings, two jobs: the pooled one for the app, a direct one for migrations. Set them up like this:
    # App traffic — pooled, one connection per instance
    DATABASE_URL="postgres://...pooler.supabase.com:6543/postgres?pgbouncer=true&connection_limit=1"
    
    # Migrations only — direct connection
    DIRECT_URL="postgres://...db.yourproject.supabase.co:5432/postgres"
    The pgbouncer=true flag tells Prisma the pooler is in charge (it adjusts features that transaction pooling cannot support), connection_limit=1 stops each serverless instance from opening a handful, and DIRECT_URL is referenced in your Prisma schema's datasource so migrations keep working.
  4. Stop opening extra clients in code. Search for places that construct a new database client per request or per function call — a fresh Prisma client inside a route handler is the classic. Create one client at module level and reuse it; in development, attach it to a global variable so hot reloads do not stack up new clients on every save. Each stray client is a pool of its own.
  5. Clear out the squatters. Old deployments, forgotten staging apps, local dev servers, and one-off scripts all hold slots. Restart or redeploy the app to drop its existing direct connections after switching URLs, shut down dev servers you are not using, and if connections from a dead process refuse to leave, the dashboard's database controls (or restarting the database as a last resort) will clear them. This is also the moment to check whether something is leaking slowly: if the count climbs steadily even at flat traffic, a code path opens connections without closing them — find it before it refills the pool.
  6. Verify under the conditions that broke it. Redeploy with the pooler settings, then reproduce the traffic pattern that caused the failure — the same pages, the same burst. Watch the connection count in the dashboard while you do: with pooling, app traffic should register as a small, steady number instead of a staircase to the ceiling. Keep the direct connection string only where it belongs — migrations and database admin tools — and leave app code on the pooler permanently, not as a temporary patch.
Why this happensOptional explanation

A database connection is not like a quick phone call; it is an open line that costs the database memory and attention for as long as it exists, so Postgres allows only a limited number — on hosted plans often somewhere between a few dozen and a couple of hundred, with a few slots reserved for the system itself (that is the "remaining connection slots are reserved" part of the message, error code 53300). A traditional server opens a handful of connections and reuses them for months, never coming close to the limit.

Serverless changed the arithmetic. On platforms like Vercel, each function invocation can spin up a fresh copy of your app — and if that copy opens a database connection or five and holds them while it lives, a burst of ordinary traffic creates hundreds of near-idle connections in minutes. Prisma and similar tools open several per instance by default, tuned for one long-lived server, not a fleet. Add local development running against the same database, a dashboard left open, and a migration tool, and the slots are gone. Critically, the error usually appears at the worst moment — when traffic is good — and once slots are full, even your health checks and admin tools cannot get in, so the whole app looks down rather than merely slow.

  • Serverless fan-out: many short-lived app instances each opening direct connections and holding them.
  • Per-instance pools: tools like Prisma defaulting to several connections per instance, multiplied by every instance.
  • Connecting to the wrong door: using the direct database address (port 5432) from app code instead of the pooler.
  • Leaks and leftovers: scripts, dev servers, and old deployments that opened connections and never closed them.

Quick answers

Why does this only happen when my site gets busy?

Because each function instance opens its own connections and holds them, and instances multiply with traffic. A traditional server reuses a few connections; a serverless fleet can create hundreds in minutes and exhaust a small plan's limit during an ordinary traffic burst.

Can't I just upgrade to a bigger database plan?

It raises the ceiling but does not change the behaviour filling it — serverless instances will consume the bigger allowance too, just later. Pooling through Supavisor lets a small number of real connections serve a large number of app instances, which fixes the arithmetic at the current tier in most cases.

What is the difference between pooler port 6543 and 5432?

Session mode (port 5432 on the pooler) lends a connection for the whole session, while transaction mode (port 6543) lends one only per transaction and returns it to the pool — far more sharing, which is what serverless apps need. A few advanced Postgres features need session mode, but standard app queries belong on 6543.

One fix, every Friday.

One real error, explained in plain English, with the fix — in your inbox every Friday. No spam, no jargon, unsubscribe anytime.