Postgres Said Too Many Clients While Sitting at 40% CPU
The error was FATAL: sorry, too many clients already , thrown by a Postgres instance that, by every dashboard we had, was using 40% of its CPU and half its RAM. Plenty of headroom. It rejected the connection anyway, which is the specific kind of outage that makes people distrust their own monitoring. The number nobody had looked at max_connections was set to 100, the Postgres default nobody…
The error message "sorry, too many clients already" was generated by a Postgres instance that appeared to have only 40% CPU usage and half its RAM allocated. This is a troubling case for monitoring as the server rejected connections despite having ample resources. Postgres' default max_connections is set to 100, but the real issue lay in the aggregate number of connections across all app instances.
Each app instance maintained its own connection pool of 20, totaling 120 connections before Postgres even accounts for its own reserved slots for replication and superuser access. Running the query SELECT count(*) FROM pg_stat_activity; revealed 97 active connections, steadily approaching the max_connections limit. The obvious solution was to increase max_connections to 1000, but this merely addressed the symptom and would likely cause memory issues down the road.
The fix that truly resolved the issue was implementing PgBouncer in transaction mode, acting as a middleman between the application and Postgres, combining hundreds of app-side connections into a smaller pool of real backend connections. With the port changed, Postgres connections dropped from a peak of 97 to a stable 20, leaving ample headroom that did not rely on fluctuating app instance counts.
This issue was undetected for so long because the connection ceiling scales with the product of instance count and pool size, both of which can grow independently due to unrelated factors. No one typically multiplies these numbers, leaving the problem hidden until Postgres raises the alarm during traffic spikes. Additionally, the underlying hardware mattered, as the database was previously hosted on a shared-tenancy VPS where RAM was oversold.
Relocating to a Krova Cube ensured that RAM was reserved 1:1, eliminating overselling and providing an accurate view of available resources. The key takeaway is to first check SELECT count(*) FROM pg_stat_activity; against SHOW max_connections;, and to separately calculate (number of app instances) × (pool size per instance). If these two numbers are close, the problem lies not with Postgres but with an arithmetic issue that Postgres is preventing on your behalf.
Written by urgent.news from Dev.to's reporting — not their text. Machine-written — may contain errors; check the original before relying on it.