FATAL: 53300

PostgreSQL FATAL: sorry, too many clients already — Fix Connection Exhaustion

PostgreSQL FATAL 53300 means max_connections is maxed out. Fix it by raising limits, fixing leaks, and using PgBouncer — not just bumping the number.

You deploy at 2am, everything goes green, then ten minutes later the app logs fill with FATAL: sorry, too many clients already and every request 500s. Classic trigger: a Java or Node service with a default pool of 10, scaled to 20 pods, all hitting one Postgres instance with max_connections = 100. That's 200 potential connections against a limit of 100. Postgres hits the ceiling, refuses new ones, and your health checks start failing. I've seen this exact pattern at three different companies. The math never gets done until production breaks.

What the error actually means

Postgres has a hard cap called max_connections. Every client — app server, admin, psql session, monitoring agent — takes one slot. When max_connections is reached, new connections get rejected with SQLSTATE 53300 and that FATAL message. Existing connections keep working, which is why the app feels half-broken instead of fully dead.

Two things make this worse than it looks. First, Postgres forks a backend process per connection — around 5-10MB of RAM each. So cranking max_connections to 1000 isn't free; you'll swap or OOM. Second, if your app leaks connections (opens them, never closes on error paths), the count creeps up over hours until you're cooked.

Check the current state first

Don't guess. Run these before touching anything.

-- How close are you to the ceiling?
SELECT count(*) AS used,
       current_setting('max_connections')::int AS max
FROM pg_stat_activity;

-- Who's holding connections?
SELECT usename, application_name, state, count(*)
FROM pg_stat_activity
GROUP BY 1,2,3
ORDER BY 4 DESC;

-- The real killer: idle in transaction
SELECT pid, usename, state, query_start, now() - state_change AS idle_time
FROM pg_stat_activity
WHERE state = 'idle in transaction'
ORDER BY idle_time DESC;

If idle in transaction shows up with long times, that's your bug. Someone opened a transaction and walked away — usually a Java app missing a commit() or a Python service with autocommit off.

The fix, in order

1. Kill the offenders (temporarily)

Get breathing room so the app can recover. Only kill idle ones — don't nuke an active query mid-write.

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

2. Raise max_connections — carefully

Bump it, but understand the cost. Rule of thumb: each connection uses ~10MB. On a 16GB box you can safely go to 200-300. Beyond that, add a pooler.

-- Check current value
SHOW max_connections;

-- postgresql.conf
max_connections = 200

-- Reload without restart
SELECT pg_reload_conf();

Also raise shared_buffers if you can — a bigger connection count without more buffers just means more contention on the same cache.

3. Fix the connection leak in your app

This is the real fix. The pool bump is a band-aid. Check your code for:

  • Connections opened outside a try/finally or context manager
  • Missing conn.close() on exception paths
  • HikariCP leakDetectionThreshold not set — turn it on, set to 60 seconds, and read the logs
  • Django: CONN_MAX_AGE set to something huge with no CONN_HEALTH_CHECKS

For HikariCP, this config exposes leaks fast:

leakDetectionThreshold=60000
maximumPoolSize=10
minimumIdle=2
connectionTimeout=5000

4. Put PgBouncer in front

If you're running microservices, you need a pooler. Period. PgBouncer in transaction mode lets 500 app connections share 50 real Postgres backends. That's the whole trick.

; pgbouncer.ini
[databases]
mydb = host=127.0.0.1 port=5432 dbname=mydb

[pgbouncer]
pool_mode = transaction
max_client_conn = 1000
default_pool_size = 20
reserve_pool_size = 5
server_reset_query = DISCARD ALL

Use transaction mode unless your app uses prepared statements or session-level state — then use session mode and add more PgBouncer instances instead.

5. Reserve slots for the superuser

Always keep a few connections for yourself. Otherwise you can't even log in to fix the problem.

superuser_reserved_connections = 5

If it's still failing

  • Check pg_stat_activity again — did the count actually drop, or did something reconnect instantly?
  • Look at the OS limits. ulimit -n on the postgres user, and kernel somaxconn. Sometimes you hit the OS before Postgres.
  • Test with psql from the DB host itself. If local works but remote doesn't, it's pg_hba.conf or listen_addresses, not connection count.
  • Check max_worker_processes and max_parallel_workers. Parallel query workers eat into the same process budget.
  • Look at cron jobs and monitoring. That Prometheus exporter polling every 5 seconds holds a connection. Ten of them add up.
If you're bumping max_connections more than once a quarter, you've got a leak or you need PgBouncer. Postgres scales up, not out — respect that and your database will stop biting you.

Grab the current docs on connection settings before you tune anything blind. And test changes on staging with real load — a quiet dev box tells you nothing about connection pressure.

Related Errors in Database Errors
0X80040150 Fix REGDB_E_READREGDB 0X80040150: Registry Read Failure 18456 MS SQL Error 18456: Fix Login Failed in 3 Steps TNS-12535, TNS-00505 Oracle Listener not responding? Here's the fix that works WAL retention due to replication slot overflow Database Replication Slot Overflow Fix

Was this solution helpful?

EP
Erropedia Team
Tech Support Editors
The Erropedia editorial team researches and documents real-world tech errors from across Windows, Linux, macOS, networking, databases, cloud platforms, and more. Every solution is reviewed for accuracy and updated as software and systems evolve.