SaaS architecture
remaining connection slots are reserved in PostgreSQL
'remaining connection slots are reserved for roles with the SUPERUSER attribute': why PostgreSQL refuses you, measured on 15–17, and how to free slots.
FATAL: remaining connection slots are reserved for … means the server is almost full: every connection slot that an ordinary role may use is taken, and the few that are left are kept for superusers (and, since PostgreSQL 16, for roles you choose). It is SQLSTATE 53300. The fix is not the reserve but whatever is holding the ordinary slots: too many application connections, connections nobody closes, or sessions left idle in a transaction.
We filled servers running PostgreSQL 15.19, 16.15 and 17.11, each with max_connections = 10 and the default superuser_reserved_connections = 3, one connection at a time, and wrote down which connection was refused with which message.
Why does PostgreSQL say the remaining connection slots are reserved?
Because max_connections counts every client connection, and superuser_reserved_connections (3 by default) of those are kept back. An ordinary role can use the rest: with 10 slots, our application role opened 7 connections, and the 8th was refused. A superuser then opened 3 more, and the 4th superuser connection got a different message: sorry, too many clients already. Both are SQLSTATE 53300.
The wording depends on the version. The same 8th connection got:
| Version | Message for an ordinary role when only reserved slots are left |
|---|---|
| 15.19 | remaining connection slots are reserved for non-replication superuser connections |
| 16.15, 17.11 | remaining connection slots are reserved for roles with the SUPERUSER attribute |
16.15, 17.11 with reserved_connections set | remaining connection slots are reserved for roles with privileges of the "pg_use_reserved_connections" role |
| any, when every slot is taken | sorry, too many clients already |
The background processes we saw did not count against max_connections: at the full moment, pg_stat_activity showed 15 rows — the 10 client connections plus the autovacuum launcher, background writer, checkpointer, logical replication launcher and WAL writer.
Who gets the last slots: reserved_connections in PostgreSQL 16 and 17
PostgreSQL 16 added a second reserve: reserved_connections slots for roles with privileges of the predefined role pg_use_reserved_connections. It is useful for roles you need when the server is full — monitoring, an operator, a migration — without making them superusers. It does not exist on 15: SHOW reserved_connections there returned unrecognized configuration parameter "reserved_connections".
- Application role, connections 1–5All accepted.5 free slots left: the two reserves
- Application role, connection 6Refused: reserved for roles with privileges of the "pg_use_reserved_connections" role.Still 5 free
- Operator role (member of
pg_use_reserved_connections), connections 1–2Accepted.3 free: the superuser reserve - Operator role, connection 3Refused: reserved for roles with the SUPERUSER attribute.Still 3 free
- Superuser, connections 1–3Accepted.0 free
- Anyone, the next connectionRefused for the application role, the operator and the superuser alike: sorry, too many clients already.Full
Measured the same on PostgreSQL 16.15 and 17.11. On 15.19 there is no second reserve: the application role got 7 connections and the 8th the message for non-replication superusers.
The documentation describes the same order: the superuser_reserved_connections slots “are intended as final reserve for emergency use after the slots reserved by reserved_connections have been exhausted”. Both settings only change at a server restart.
How do you free connection slots?
Look at who holds them first. As a superuser, or a role with pg_use_reserved_connections, you can still get in:
select usename, application_name, state, count(*)
from pg_stat_activity
where backend_type = 'client backend'
group by 1, 2, 3
order by 4 desc;
What we saw in the measurements, and what follows from it:
- Killing the client does not free the slot straight away. We killed seven
psqlclients whose server sessions were inside apg_sleep(600). Two seconds laterpg_stat_activitystill showed all seven asactive; after killing a superuser client on a full server, a new connection one second later was still refused. The server session keeps running until its query ends; only then, when it tries to answer, does it notice the client is gone, so a client process that dies mid-query keeps its slot until then. Withclient_connection_check_interval = '1s'the session was gone within 3 seconds, withFATAL: connection to client lost(SQLSTATE08006); the default is 0, off.pg_terminate_backend(pid)ends a session from the server side: after terminating one, a new application connection was accepted immediately. - Sessions idle in a transaction keep their slot for as long as nobody ends them. After
ALTER ROLE app SET idle_in_transaction_session_timeout = '5s', new sessions of that role that opened a transaction and then did nothing were ended after 5 seconds, and the client’s next statement gotFATAL: terminating connection due to idle-in-transaction timeout(SQLSTATE25P03). Sessions that were already open before theALTER ROLEwere not affected: one that stayed idle in its transaction for 30 seconds still ran its next query (checked on 15 and 17). What else such a session holds — row locks,ALTER TABLE, vacuum — is in idle in transaction in PostgreSQL. - Raising
max_connectionsmoves the limit, not the cause. It needs a restart, and every connection is a separate server process. For a SaaS application with many app servers or workers, the usual answer is a pooler in front of the database, so that hundreds of client connections share a few dozen server sessions.
A pooler has its own trap for multi-tenant applications: a tenant set with a plain SET stays on the server session and reaches the next client. What leaked and what did not, measured with PgBouncer in transaction mode, is in PgBouncer and row-level security.
Can a migration or a deploy still connect when the ordinary slots are full?
Only if its role can use a reserve. On 15 that means a superuser. On 16 and 17, GRANT pg_use_reserved_connections TO migrator; plus reserved_connections above zero let our operator role in while the application role was refused. Keep the number small: those slots are taken from what ordinary roles can use — with reserved_connections = 2 our application role got 5 connections instead of 7.
What we did not measure
Managed services, which may reserve slots of their own for their administration roles. max_connections at production sizes and the memory per connection. Connection limits per role and per database (ALTER ROLE … CONNECTION LIMIT) give their own message — too many connections for role "app", also SQLSTATE 53300 (checked once on 17) — and we did not test them further.
Sources: PostgreSQL 17 documentation on connection settings (max_connections, reserved_connections, superuser_reserved_connections) and predefined roles; PostgreSQL 16 release notes. All read on 16 October 2026. All measurements are our own, on PostgreSQL 15.19, 16.15 and 17.11 in containers, on 16 October 2026.