Errata: the max_connections post told you to size the parameter as what the poolers open, “plus the replication connections,” plus a couple of dozen for humans and monitoring. Drop the replication connections from that sum. Since PostgreSQL 12, walsenders are drawn from their own pool, sized by max_wal_senders, and do not count against max_connections at all. The rest of the arithmetic stands.

superuser_reserved_connections has held a few slots back since PostgreSQL 7.3, and for most of the years since then it has been protecting the wrong account. Nobody who runs PostgreSQL responsibly connects to production as a superuser if they can avoid it, and on a managed service nobody can. The roles that actually need to get in when the application has eaten every connection (the monitoring agent, the backup job, the human with the pager) are ordinary roles, and ordinary roles get locked out with everyone else. reserved_connections is the fix: a second reserve, for roles you choose, standing in front of the superuser reserve.

The default is 0, which means the feature is off. The context is postmaster, so turning it on requires a restart, and the moment you discover you need a reserve is not a moment when you get to restart. The nominal maximum is the same absurd 262,143 as max_connections, but the real constraint is that superuser_reserved_connections plus reserved_connections must be strictly less than max_connections; if it isn’t, the postmaster refuses to start and says why. The parameter arrived in PostgreSQL 16. On 14 and 15 the superuser reserve is the only reserve there is.

The mechanism is a gate at connection time, not a quota. After authentication, the new backend has already claimed a slot from the regular pool. It then counts the slots still free and compares that count to the two reserves. If fewer than superuser_reserved_connections remain, only a superuser may keep the slot; everyone else gets FATAL: remaining connection slots are reserved for roles with the SUPERUSER attribute. If at least that many remain but fewer than superuser_reserved_connections plus reserved_connections, the slot goes to superusers and to roles with the privileges of the predefined role pg_use_reserved_connections, and everyone else gets FATAL: remaining connection slots are reserved for roles with privileges of the "pg_use_reserved_connections" role. Superusers skip the whole check, which is why they can also consume every slot in the pool and then hit sorry, too many clients already like anyone else. All three messages carry SQLSTATE 53300, so alerting that already keys on that code for connection exhaustion will catch this too.

On a default install the arithmetic is 100 minus 3: 97 slots for ordinary roles. Set reserved_connections = 10 and ordinary roles get 87, members of pg_use_reserved_connections get 97, and superusers get 100. The count the gate sees is the number of free slots in the regular backend pool: max_connections minus every client backend currently holding one, including connections still in authentication (which pg_stat_activity does not show before 18) and ones on their way out. Nothing else is counted. Existing connections from privileged roles are not charged against the reserve, so a monitoring role with a connection leak can happily eat its own reserve, and an ordinary role that arrives just after a superuser disconnects still finds the freed slot inside a band it is not allowed to use. The parameter guarantees that unprivileged roles cannot take the last N slots. It guarantees nothing else.

The phrase “privileges of” in that message is exact. The check uses the same inherited-membership test as the rest of the privilege system, so a NOINHERIT role that has been granted pg_use_reserved_connections is refused exactly like a role that was never granted it, and SET ROLE cannot rescue it, because the refusal happens before there is a session in which to run SET ROLE. Check rolinherit on the role you are protecting (and, since 16, the INHERIT option on the grant itself) before you trust the grant.

What the reserve does not cover matters too. Walsenders, autovacuum workers, and background workers (parallel workers included) each draw from their own pool of process slots and are neither counted nor checked. A streaming replica or a pg_basebackup will connect when an ordinary role cannot; a pg_dump running as an ordinary role will not, because pg_dump is just a client. Until the February 2025 minor releases (16.7, 17.3, 15.11, 14.16), the exclusion was haphazard enough that a parallel worker running on behalf of an unprivileged user, launched while the pool was already inside a reserved band, could be refused a slot and take the query down with it. If you are behind on minor releases, that is one more reason not to be.

The parameter exists because of a January 2023 email from Robert Haas listing the things that still kept a CREATEROLE administrator from being a usable stand-in for a superuser. One of them was this: CONNECTION LIMIT caps each role individually, and no sum of individual caps can reserve slots in aggregate for the role that has to get in last. Nathan Bossart’s patch was committed four days later, which tells you how overdue it was. RDS had already solved the same problem for its own almost-superuser with rds.rds_superuser_reserved_connections (default 2, for rds_superuser), which is why the non-superuser connection count on RDS is max_connections minus 3 minus 2 rather than minus 3.

So turn it on. The max_connections post told you to budget a couple of dozen slots for humans and monitoring; reserved_connections is how you make that budget enforceable instead of aspirational. Set it to roughly that number, grant pg_use_reserved_connections to the roles that are supposed to use those slots (the monitoring role, the DBA role, the role that runs pg_dump), and do not grant it to the application role, because an application that can reach the reserve has no reserve. Keep superuser_reserved_connections at 3 as the last ditch behind it, and keep the per-role CONNECTION LIMIT on the application side; the two work from opposite ends toward the same guarantee. Zero protects nobody but the account you never log in with.