Whyfire

Blog · 13 October 2026 · 6 minute read

PostgreSQL "sorry, too many clients already": find the culprit

Tested with PostgreSQL 16 and Prometheus 3.5.

Quick answer

Every connection slot is taken. Connect as a superuser, group pg_stat_activity by user, application and address to see who holds them, and end the sessions stuck idle in transaction. Raising max_connections only buys time if something is leaking connections.

Your app servers start throwing timeouts, the load balancer returns 502s, and the alarms go off on the web tier. The real problem is one server further back: the database has run out of connections.

What the error means

PostgreSQL has a fixed number of connection slots, set by max_connections (100 by default). When every slot is taken, each new connection is refused with:

FATAL:  sorry, too many clients already

A few slots are held back for superusers (superuser_reserved_connections, 3 by default), so ordinary users often hit a slightly different message first:

FATAL:  remaining connection slots are reserved for non-replication superuser connections

On PostgreSQL 16 and later that second message ends "…reserved for roles with the SUPERUSER attribute". Either way, the database itself is usually healthy. Something is holding connections and not giving them back.

Find who is holding the connections

Connect as a superuser (the reserved slots are for exactly this) and group the open connections:

SELECT usename, application_name, client_addr, state, count(*)
FROM pg_stat_activity
WHERE backend_type = 'client backend'
GROUP BY 1, 2, 3, 4
ORDER BY 5 DESC;

One line usually stands out: a single user, application or client address with far more connections than the rest. Then look at what those connections are doing and for how long:

SELECT pid, usename, client_addr, state,
       now() - state_change AS in_state_for,
       left(query, 80) AS last_query
FROM pg_stat_activity
WHERE backend_type = 'client backend'
ORDER BY state_change
LIMIT 20;

What you are looking for:

Get service back

End the stuck sessions, starting with the oldest idle in transaction ones. pg_terminate_backend closes the connection and rolls back its open transaction:

SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE state = 'idle in transaction'
  AND now() - state_change > interval '10 minutes';

Check the list before you run it. Raising max_connections needs a restart and only buys time if something is leaking: each connection is a separate process using memory, so a much higher limit can trade this outage for a memory one.

Stop it happening again

Alert before the slots run out

With postgres_exporter, warn at 80% of the limit (metric names vary a little between exporter versions, so check them on its /metrics page):

- alert: PostgresConnectionsHigh
  expr: |
    sum by (instance) (pg_stat_activity_count)
      / max by (instance) (pg_settings_max_connections) > 0.8
  for: 5m
  labels:
    severity: warning
  annotations:
    summary: "{{ $labels.instance }} is using over 80% of its connection slots"

If you collect PostgreSQL's logs in Loki, you can also alert on the error itself. Adjust the label selector to match how your logs are labelled:

sum by (host) (count_over_time({app="postgres"} |= "too many clients already" [5m])) > 0

More rules built the same way are in Alert rules that won't burn you out.

Why the alarm went off somewhere else

The awkward part of this incident is that the database rarely alarms first. The connection count climbs quietly, and the first thing anyone notices is the app servers timing out, because they are the ones being health-checked. On-call starts on the web tier, restarts it, and the restarted app opens a fresh pool of connections into a database that is already full.

That pattern, a cause on one server and the alarms on another, is what Whyfire is built for. It compares every server's signals with the hour before and names the first one that changed. Our live demo is exactly this outage: the database's connections went from 2 to 302 six minutes before the app servers alarmed.