DatabasesJune 26, 20267 min read

PostgreSQL "FATAL: sorry, too many clients already" — What It Means and How to Fix It

Your app is suddenly throwing 500s and the database logs are full of "too many clients already." Here's what the error really means, how to see what's hogging your connections, and the fix that actually holds: pooling, not a bigger number.

RThe Runsite Team

A deploy goes out, traffic picks up, and your app starts throwing 500s. You open the logs and there it is, over and over: "FATAL: sorry, too many clients already." Nothing changed in your queries, the database is barely breaking a sweat on CPU, and yet new requests can't get in. The error reads like the database is overloaded. It almost never is. What's actually full is the connection pool, and once you see why, the fix is straightforward and permanent.

What the error actually means

PostgreSQL accepts a fixed number of simultaneous client connections, set by the `max_connections` parameter (100 by default). When connection number 101 tries to open while the first 100 are still held, the server doesn't queue it or slow it down. It refuses outright with this error. You'll sometimes see its sibling, "remaining connection slots are reserved for non-replication superuser connections," which is the same wall hit a few slots earlier, because Postgres keeps a handful in reserve so an admin can still log in to fix the mess.

The key thing: this is a count of open connections, not a measure of load. An app running ten trivial `SELECT 1` queries can exhaust the limit while one running a single heavy report stays well under it. The error is about how many doors are open, not how much work is going through them.

Why Postgres has a connection ceiling at all

It helps to know why the limit exists, because it explains why "just raise it" is a trap. PostgreSQL uses a process-per-connection model: every connection forks a dedicated backend process on the server, and each one reserves memory for its work area, query plans, and buffers, idle or not. A hundred connections doing nothing still cost real RAM. Push `max_connections` to 500 on a small instance and you can starve the database of the memory it needs to actually run queries, trading a clean refusal for thrashing and OOM kills. The ceiling isn't arbitrary; it's the database protecting itself.

Find out what's eating your connections

Before changing anything, look at who's actually connected. Postgres exposes this in the `pg_stat_activity` view. Group it by state to get the shape of the problem in one query:

sql
-- How many connections, and what are they doing?
SELECT state, count(*)
FROM pg_stat_activity
GROUP BY state
ORDER BY count(*) DESC;

The state column is the tell, and each value points at a different fix:

  • `active` — real concurrent work in flight. The answer is pooling, so fewer connections do the same amount of work.
  • `idle` — your app opened the connection and walked away without closing it. The answer is fixing how it manages connections.
  • `idle in transaction` — the worst of the three: a connection that ran a query, never committed or rolled back, and is now holding both a slot and its locks hostage. A few of these can wedge a whole app.

The usual culprit: connections multiply with instances

If each app instance opens its own pool of, say, 20 connections, then four instances plus a couple of background workers and a cron job quietly add up to well past 100, even though no single piece looks unreasonable. Connection math is per-fleet, not per-process. Count every instance, worker, and scheduled job that talks to the database.

The real fix: pool your connections

Opening a fresh connection per request is the root cause behind most of these incidents. Connections are expensive to create and strictly limited, so the fix is to open a small set once and reuse them. There are two layers to do it at, and busy apps want both.

1. Pool inside your app

Every serious database driver ships a pool. Use it, and cap it deliberately instead of leaving it on a generous default. The cap is per process, so multiply it by how many instances you run and keep the total comfortably under `max_connections`:

javascript
import { Pool } from 'pg';

// One pool per process, reused across requests.
const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
  max: 10, // ceiling PER instance: 10 x instances must stay under max_connections
});

// Borrow and return; never open a connection per request.
const { rows } = await pool.query('SELECT * FROM users WHERE id = $1', [id]);

This alone resolves a surprising share of "too many clients" fires. The trap to avoid is creating the pool inside a request handler, which spins up a brand-new pool on every call and reproduces the exact problem you're trying to kill. Create it once, at startup.

2. Put a pooler in front of the database

App-side pools have a hard limit: they can't coordinate across instances. Each one only knows its own connections, so a fleet that scales horizontally will still blow the ceiling no matter how careful each process is. The fix is a single pooler that sits between your apps and Postgres, multiplexing thousands of client connections down onto a small set of real database ones. PgBouncer is the standard tool for this, and in transaction-pooling mode it hands a real connection to a client only for the duration of a transaction, then returns it to the shared set, so a handful of backend connections can serve a large fleet.

Running PgBouncer yourself means another process to deploy, configure, and keep alive. Runsite's managed PostgreSQL has PgBouncer built in, so there's no separate pooler to operate. You connect to the standard port 5432 for a direct session, or to port 6432 for the pooled endpoint, controlled by a single flag on the connection string:

bash
# Direct connection (port 5432)
DATABASE_URL=postgresql://user:pass@db.runsite.app:5432/mydb

# Pooled through PgBouncer (port 6432)
DATABASE_URL=postgresql://user:pass@db.runsite.app:6432/mydb?pgbouncer=true

One caveat worth knowing up front: transaction pooling doesn't play nicely with session-level features like prepared statements or `SET` that expect to live for a whole session. Most apps and ORMs have a setting for this; the specifics for each driver are in the Runsite docs rather than something to improvise mid-incident.

Hunt down leaked and idle connections

If `pg_stat_activity` showed a wall of `idle` or `idle in transaction` rows, pooling alone won't save you, because something is opening connections and never letting go. The common causes are short: a code path that opens a connection and returns before closing it, a transaction that errors out without a rollback, or a worker that holds a connection open while it waits on something slow.

To buy breathing room in the moment, you can terminate the connections that have been sitting idle the longest:

sql
-- Free connections idle for more than 10 minutes
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE state = 'idle'
  AND state_change < now() - interval '10 minutes'
  AND pid <> pg_backend_pid();

That's a fire extinguisher, not a fix; the leak will refill the slots if you don't find it. The durable version is to let the database reap them automatically by setting `idle_in_transaction_session_timeout`, so a forgotten transaction can't hold a slot indefinitely. Pair that with the pooling above and the idle pile stops coming back.

When raising max_connections is (and isn't) the answer

Sometimes the limit really is too low for legitimate, well-pooled traffic, and the right move is genuinely a bigger instance. The order matters, though. Pool first, find any leaks second, and only then raise the ceiling, because each new connection costs memory and a higher limit on the same small instance buys instability, not headroom.

When you do need more, raise the resources, not just the number. On Runsite, managed PostgreSQL scales vertically with one click, which lifts the memory the extra connections actually need along with the limit. And because the database, its WAL archive, and every backup stay in the EU (Frankfurt, Germany) with a signed GDPR DPA on every plan, scaling up doesn't quietly move your data somewhere it shouldn't be.

The short version

"Too many clients already" is a connection-count problem wearing the costume of an overload. Check `pg_stat_activity` to see whether you're looking at real concurrency, idle leftovers, or stuck transactions. Pool connections inside each app and put PgBouncer in front of the fleet so thousands of clients ride on a handful of real connections. Reach for a bigger `max_connections` last, and when you do, give it the memory to match. Do that and the error stops being a recurring incident and becomes a line you never see again. Spin up a managed PostgreSQL database with pooling already handled, and skip the part where you operate it yourself.

FAQ

Frequently Asked Questions

Common questions about this service.

It means the number of open connections has reached max_connections (100 by default), so PostgreSQL refuses any new connection until an existing one closes. It's a count of open connections, not a measure of database load. An app can hit the limit with trivial queries while CPU stays near idle. The fix is almost always connection pooling, not a bigger database.

Rarely as a first step. Every connection in PostgreSQL forks a backend process that reserves memory whether it's busy or not, so a high max_connections on a small instance can starve the database and cause instability. Pool your connections first, fix any leaks (idle or idle-in-transaction sessions), and only then raise the limit, raising the instance resources alongside it, not just the number.

Port 5432 is a direct connection to PostgreSQL; port 6432 routes through the built-in PgBouncer pooler. Use the pooled 6432 endpoint (with ?pgbouncer=true) for high-concurrency app traffic so many clients share a small set of real database connections. Use the direct 5432 endpoint for migrations or admin tasks that need session-level features like prepared statements.

Your app deserves to be online

Free to start. Deploy in under a minute. No credit card needed.