ArmorDB Logo
ArmorDB
All articles
Quick FixesAugust 28, 20265 min read

Fix PostgreSQL: sorry, too many clients already

Learn how to diagnose PostgreSQL connection-limit errors, distinguish leaks from traffic spikes, and fix them safely with pool sizing and PgBouncer.

ArmorDB Engineering

ArmorDB engineering

PostgreSQLQuick FixConnections
On this page 7 sections

FATAL: sorry, too many clients already means PostgreSQL refused a new session because the server's connection capacity was full. The error often appears during a deploy, import, cron burst, serverless traffic spike, or application leak. It can also show up as remaining connection slots are reserved for roles with privileges of the pg_use_reserved_connections role or as a provider-specific connection-limit message.

The important point is that this is a capacity and lifecycle problem, not a bad password or SQL syntax problem. Retrying every request immediately usually makes the incident worse because each retry tries to open another connection. The safe fix is to identify who is holding sessions, stop new connection storms, and then right-size application pools or add a pooler such as PgBouncer.

Fast diagnosis

If you can still connect with an admin user, start with pg_stat_activity. It shows current backend sessions, the database and user attached to each one, the client address, session state, and query timing. Run a compact count first so you do not scroll through hundreds of rows during an incident:

SELECT datname, usename, application_name, state, count(*)
FROM pg_stat_activity
GROUP BY 1, 2, 3, 4
ORDER BY count(*) DESC;

Then look for old idle sessions and transactions that have been open too long:

SELECT pid, usename, application_name, client_addr, state,
       now() - state_change AS state_age,
       now() - xact_start AS transaction_age
FROM pg_stat_activity
WHERE state <> 'active' OR xact_start IS NOT NULL
ORDER BY greatest(now() - state_change, now() - coalesce(xact_start, state_change)) DESC
LIMIT 25;

This separates three common cases. A real traffic spike usually has many active sessions from the same application tier. A pool-size problem often has too many idle sessions spread across many app instances. A leak often has connections whose state_change age keeps growing long after the request that opened them should have finished.

Symptom in pg_stat_activityLikely causeSafest immediate actionDurable fix
Many active sessions from one jobImport, migration, or worker burstPause or reduce the job concurrencyAdd explicit worker limits and backpressure
Many idle sessions across app hostsPool size multiplied by replicasRestart only the offending app tier if neededLower per-instance pool size or use PgBouncer
Long idle in transaction sessionsCode opened a transaction and stoppedCancel or terminate after checking impactFix transaction scope and timeouts
Failures only during deploysOld and new pods overlap poolsSlow rollout or temporarily reduce replicasAccount for max surge in pool math
Admins cannot connectAll ordinary slots are consumedUse provider console or reserved/admin pathKeep application roles away from reserved slots

Do not fix it by only raising max_connections

PostgreSQL exposes max_connections, and the official documentation describes reserved connection settings such as reserved_connections and superuser_reserved_connections. Raising the limit can be appropriate when the instance has enough memory and the workload truly needs more concurrency, but it is rarely the first fix. Every backend session consumes server resources, and hundreds of mostly idle application sessions can still make failover, memory planning, and incident response harder.

For managed PostgreSQL, the practical answer is usually to reduce direct database sessions rather than to let every process talk to the server independently. A web app with 20 replicas and a default pool of 20 can try to hold 400 database sessions before background workers, migrations, dashboards, and admin tools are counted. If the database plan allows fewer connections than that, the outage is predictable even when average query volume is modest.

Calculate the pool budget

Set a connection budget before tuning any individual library. Leave room for migrations, admin access, monitoring, and a failover or deploy overlap. Then divide the application share by the maximum number of processes that can be alive at the same time, not just the number you normally run.

A simple starting formula is: direct app pool size equals (database connection limit - reserved operational headroom) / maximum concurrent app processes. If the answer is tiny, that is not a sign to ignore the math. It is a sign to use a transaction pooler, reduce app process count, or move bursty workers behind a queue with explicit concurrency.

PgBouncer helps because it accepts many client connections while keeping a smaller number of server connections open to PostgreSQL. Its own documentation distinguishes settings such as max_client_conn, default_pool_size, and pool_mode. For most request/response web applications, transaction pooling is often the useful shape: a server connection returns to the pool when a transaction finishes. Session features, prepared statement behavior, temporary tables, and advisory locks need review before switching modes, so do not flip pooling in production without testing the application path that uses those features.

ArmorDB includes PgBouncer for this reason. If connection pressure is the incident pattern, compare the direct connection string with the pooled one in /docs/pgbouncer, and keep migrations or admin scripts on the connection type that matches their behavior.

Immediate recovery checklist

First, stop the source of new connections. Pause a runaway job, scale down a noisy worker, or halt an aggressive rollout. If retries are hammering the database, increase backoff in the application or at the queue layer before restarting everything.

Second, identify safe sessions to remove. Terminating a session rolls back its active transaction, so prefer to cancel obvious long-running statements first and terminate only sessions that are clearly abandoned, idle too long, or owned by the broken deploy. Use application names in connection strings so this decision is possible under pressure.

Third, reconnect cleanly. Restarting a web tier can clear leaked sessions, but it can also create a thundering herd if every instance starts at once. Bring instances back gradually with a lower pool size, verify the count in pg_stat_activity, and only then restore worker concurrency.

Common mistakes

One mistake is counting only one application process. Autoscaling, rolling deploy max surge, preview environments, background queues, BI tools, and migration containers all consume the same finite server slots when they connect directly. Another mistake is leaving the pool default untouched. Many client libraries choose defaults that are reasonable for a single process but unsafe when multiplied across containers.

A third mistake is treating idle sessions as free. Idle is better than active, but idle sessions still occupy connection slots. In a managed database, the goal is not to eliminate every idle session; it is to keep the number bounded and explainable.

Sources and further reading

  • PostgreSQL documentation: max_connections, reserved_connections, and superuser_reserved_connections in runtime connection settings.
  • PostgreSQL documentation: pg_stat_activity in monitoring statistics.
  • PgBouncer documentation: pool_mode, default_pool_size, and max_client_conn configuration.
  • ArmorDB docs: /docs/pgbouncer for using the pooled endpoint.

Takeaway

too many clients already is PostgreSQL protecting itself from unbounded sessions. Recover by stopping the connection storm, finding who owns the slots, and clearing only the sessions you understand. Prevent the repeat by budgeting connections across every app process, lowering per-instance pool sizes, and using PgBouncer when many clients need to share a smaller, healthier number of PostgreSQL backends.

Written by ArmorDB Engineering

Practical notes on PostgreSQL operations, security, and infrastructure decisions for teams building production applications.

Updated Aug 28, 2026