PostgreSQL

How to Resolve "FATAL: Remaining Connection Slots Are Reserved for Non-Replication Superuser Connections"

4 min read by DebuggedIt

Quick answer

PostgreSQL has hit its maximum connection limit and is now refusing new connections from regular application users, reserving whatever's left specifically for...

PostgreSQL has hit its maximum connection limit and is now refusing new connections from regular application users, reserving whatever's left specifically for superuser access. This is a deliberate safety mechanism β€” it exists precisely so an administrator can still get in during a connection exhaustion incident to diagnose and fix it.

The Problem

Your application can no longer connect, but the error message itself hints at the fix:

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

Trying to connect as a regular application role fails, but a superuser connection (like postgres) may still succeed, since the reserved slots exist for exactly this case:

$ psql -U app_user -d mydb
psql: error: FATAL:  remaining connection slots are reserved for non-replication superuser connections

$ sudo -u postgres psql
psql (16.2)
Type "help" for help.
postgres=#

Why It Happens

PostgreSQL's max_connections setting caps the total number of simultaneous connections, and a separate superuser_reserved_connections setting carves out a small number of those slots exclusively for superusers, so regular connections get locked out slightly before the absolute maximum is reached. This gives an administrator breathing room to log in and investigate even when the database is fully saturated with application connections. The underlying cause is almost always one of:

  • Connection leaks in the application β€” connections opened and never properly closed, accumulating over time until the limit is reached.
  • A connection pool sized too large relative to max_connections, especially when multiple application instances or services each maintain their own pool against the same database.
  • Genuinely high concurrent load that exceeds what the current max_connections setting was ever tuned for.
  • A stuck or long-running transaction holding a connection open far longer than intended, effectively removing it from the usable pool for everyone else.
max_connections = 100 97 app connections (leaked or pooled too large) 3 reserved Reserved slots let a superuser get in to diagnose and fix it

The Fix

Connect as a superuser using a reserved slot to investigate first:

sudo -u postgres psql

Check what's currently connected and how:

SELECT pid, usename, application_name, state, query_start, now() - query_start AS duration
FROM pg_stat_activity
ORDER BY duration DESC;

Look for connections sitting idle for a long time or stuck in a state like idle in transaction, which is a strong signal of a leak or an application bug that opened a transaction and never committed or rolled it back:

SELECT pid, usename, state, query_start
FROM pg_stat_activity
WHERE state = 'idle in transaction'
ORDER BY query_start;

Terminate specific problematic connections if they're clearly stuck and safe to kill:

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

Check the current connection limit and how close you are to it:

SHOW max_connections;
SELECT count(*) FROM pg_stat_activity;

If genuinely legitimate load requires more headroom, raise the limit β€” but check available RAM first, since PostgreSQL allocates memory per connection:

ALTER SYSTEM SET max_connections = 200;

This requires a full restart to take effect, unlike a simple reload:

sudo systemctl restart postgresql

Still Not Working?

If you're routinely running close to the limit even without an obvious leak, the more scalable long-term fix is putting a connection pooler like PgBouncer in front of PostgreSQL instead of continually raising max_connections. PostgreSQL connections are relatively expensive to hold open (each one is a full OS process), while PgBouncer can multiplex a much larger number of application-side connections onto a small, efficient pool of actual PostgreSQL connections:

# 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

With this in place, your application connects to PgBouncer instead of PostgreSQL directly, and PgBouncer handles the actual multiplexing β€” letting you support far more application-side connections without proportionally raising PostgreSQL's own max_connections and the memory overhead that comes with it.