max_connections
Maximum concurrent connections. Too high × active sorts: OOM kills all sessions. Too low: connection refused. Use PgBouncer for >200.
Tier 2 — Production Guide — Operational guidance is available; evidence scope is stated where relevant.
What We Observed
box
+ cl_mc_safe (--memory=1g), pgbench TPC-B
connection refused
- setting
max_connections=15, superuser_reserved=3
- normal user ceiling
12
- result
connections 13+ refused: FATAL: remaining connection slots are reserved for roles with the SUPERUSER attribute
- source file
normal operation
- setting
max_connections=100
- clients
20
- tps
995.15
- latency ms
20.097
- oomkilled
false
- source file
curve note
cliff behavior: below capacity → connection refused; above memory → OOM. No gradual degradation.
What Was Expected
- framing
max_connections is NOT a safety valve. When every connection is active and running work_mem sorts, the RAM formula multiplies fast: connections × work_mem × sort_nodes × hash_mem_multiplier. OOM kills all sessions simultaneously.
- formula
peak_mem = max_connections × work_mem × avg_sort_nodes × hash_mem_multiplier
- example
300 × 4MB × 2 × 2 = 4.8GB → OOM on 768MB container
- lab evidence
- container
768MB cap (from mc_danger.txt)
- max connections
300
- result
OOMKilled=true
- log
server process (PID N) was terminated by signal 9: Killed
- source file
- mc danger raw
== box load == 0.75 1.35 1.18 5/1122 293183
== PART A DANGER: max_connections=300 (no backpressure), 768MB box, 10-client spike == max_connections=300 waiting for the spike to resolve...
OOMKilled=true Status=running ExitCode=0
postgres crash-cascade evidence: This user must also own the server process. 2026-06-19 22:11:45.020 UTC [1] LOG: server process (PID 161) was terminated by signal 9: Killed 2026-06-19 22:11:45.020 UTC [1] LOG: terminating any other active server processes 2026-06-19 22:11:45.114 UTC [1] LOG: all server processes terminated; reinitializing 2026-06-19 22:11:45.701 UTC [174] LOG: database system was not properly shut down; automatic recovery in progress per-client view (what the application saw): 9 WARNING: terminating connection because of crash of another server process 10 server closed the connection unexpectedly== PART B CONTROL: max_connections=3 (reserved=1 -> 2 usable), SAME 768MB box, SAME 10-client spike == max_connections=3 waiting... instance state: OOMKilled=false Status=running still serving queries? YES alive: 2026-06-19 22:12:38.856831+00 per-client view (recoverable refusals, NOT a crash): 8 psql: error: connection to server on socket "/var/run/postgresql/.s.PGSQL.5432" failed: FATAL: remaining connection slots are reserved for roles with the SUPERUSER attributetorn down cl_mc_oom DONE
- source file
Common Pitfalls
- Raising max_connections to 'fix' connection errors instead of adding a pooler -- this removes admission backpressure and arms an OOM cascade: under load enough work_mem-hungry backends are admitted to exhaust RAM and crash the whole instance.
- Forgetting it needs a RESTART (context=postmaster) -- ALTER SYSTEM + reload does nothing live (pending_restart=t); you cannot bump it during an incident, so capacity must be planned ahead.
- Sizing it to the application's peak connection count rather than to what the hardware can serve -- thousands of backends mostly add context-switch/lock contention and per-backend memory, not throughput.
- Ignoring the per-backend memory math -- max_connections x (work_mem x nodes x parallel multiplier) can dwarf RAM long before you hit the connection limit; the OOM hits under LOAD, not at idle.
- Not reserving admin slots -- with superuser_reserved_connections too low (or reserved_connections unset on PG16+) you can be locked out of your own saturated server exactly when you need to connect.
- Setting an enormous value 'just in case' -- it inflates shared memory and the lock table (sized off MaxBackends) and the proc-array scan cost even when those connections are idle.
- Assuming autovacuum/replication share the client pool -- they get their own slots on top (MaxBackends), so if you under-size max_wal_senders or max_worker_processes you starve those, not client connections.
Tuning Guidance
Unlock Incident Pro
The free sections tell you what to do. Pro is the measured proof — the exact failure signature, safe-ceiling formula, and per-workload defaults, each captured on real PG 14–18 runs.
- Tuning Guidance
max_connections is the hard cap on how many client backends can be connected to the server at the same time. PostgreSQL uses a process-per-connection model: every connection is a separate OS backend process, and max_connections fixes how many of those can exist concurrently. It is set once at server start (context=postmaster) and reserves fixed-size shared-memory structures (the proc array, lock table, predicate-lock table, etc.) sized for that many backends -- which is why it cannot be changed without a restart. A normal (non-superuser) login can occupy at most max_connections - superuser_reserved_connections (- reserved_connections on PG16+) slots; the remaining slots are held back so an administrator can still get in when the server is saturated. It is deceptively important: set too LOW and your application hits 'sorry, too many clients already' connection storms; set too HIGH and you remove the only admission-control backpressure the server has, letting enough memory-hungry backends in to OOM the whole instance under load.
- name
max_connections
- setting default
100
- unit
(none -- a count of concurrent backends)
- vartype
integer
- category
Connections and Authentication / Connection Settings
- context
postmaster
- min val
1
- max val
262143
- enumvals
(n/a -- integer)
- boot val
100
- reset val
100
- pending restart
false (until changed; ALTER SYSTEM SET max_connections flips pending_restart to true until the server is restarted)
- short desc
Sets the maximum number of concurrent connections.
- ground truth note
Captured literally from pg_settings on PG 14-18 (-18) on 2026-06-20. setting=100, boot_val=100, reset_val=100 on EVERY version (default_changed=false, boot_fallback_changed=false). context=postmaster (restart required; ALTER SYSTEM + reload does NOT apply it -- proven in the lab: live value stayed 25 and pg_settings.pending_restart became t). vartype=integer, min_val=1, max_val=262143, unit empty. category='Connections and Authentication / Connection Settings', short_desc='Sets the maximum number of concurrent connections.' Default and bounds are identical across 14, 15, 16, 17, 18.
Rock-stable across versions: default=100, min=1, max=262143, context=postmaster, vartype=integer on PG 14, 15, 16, 17 and 18 -- no version cliff (default_changed=false, boot_fallback_changed=false). The version-adjacent nuance is in the SURROUNDING slot accounting, not in this value: PG16 added a separate reserved_connections GUC (and the pg_use_reserved_connections privilege) that carves out a second pool of reserved slots alongside the older superuser_reserved_connections (default 3), so on PG16+ the normal-user ceiling is max_connections - superuser_reserved_connections - reserved_connections. The total kernel/shared-memory sizing has always been MaxBackends = max_connections + autovacuum_max_workers + max_worker_processes + max_wal_senders + 1, i.e. autovacuum workers, background workers and walsenders get their OWN slots on top of max_connections. Per-version detail in _guc_matrix_machine.
context=postmaster: it can ONLY be changed by editing postgresql.conf (or ALTER SYSTEM SET max_connections=N) and RESTARTING the server. A reload is not enough -- the lab ran ALTER SYSTEM SET max_connections=50 + pg_reload_conf and the live value stayed at 25 while pg_settings.pending_restart turned to t. This is because the server allocates fixed-size shared memory for exactly max_connections backends at startup. Plan capacity AHEAD of time: you cannot bump max_connections live in the middle of an incident. Note that raising it also raises the shared-memory footprint and the size of the lock table (max_locks_per_transaction x MaxBackends), so a very large value costs memory even when the connections are idle. It is not per-database or per-role -- it is a single cluster-wide ceiling. To give specific roles/databases lower limits use ALTER ROLE . CONNECTION LIMIT / ALTER DATABASE . CONNECTION LIMIT (those ARE changeable without restart), but the global ceiling itself needs a restart. BOUNDARY PROOF (captured literal): an out-of-range / invalid value is rejected at set time -- ALTER SYSTEM SET max_connections = 0; -> ERROR: 0 is outside the valid range for parameter "max_connections" (1 .. 262143). It is context=postmaster, so a session-level set is refused -- SET max_connections = ... -> ERROR: parameter "max_connections" cannot be changed without restarting the server -- and ALTER SYSTEM leaves pg_settings.pending_restart=t until the server is restarted.
References
- PostgreSQL docs: Runtime Config -- Connections and Authentication / Connection Settings (max_connections, superuser_reserved_connections, reserved_connections).
- PostgreSQL docs: Connections and Authentication -- per-role/per-database CONNECTION LIMIT (ALTER ROLE / ALTER DATABASE).
- PostgreSQL docs: Resource Consumption -- Memory (work_mem, hash_mem_multiplier) and how per-backend memory scales with concurrency.
- PostgreSQL docs: pg_stat_activity (counting active backends) and pg_settings (pending_restart for postmaster-context parameters).
- PgBouncer / pgcat documentation: transaction-mode pooling to serve many client connections from a small backend pool.
- Lab capture 2026-06-20: cl_mc connection-ceiling sweep (ceiling = max_connections - superuser_reserved_connections; restart-required proof) and cl_mc_oom 768MB OOM-cascade vs recoverable-refusal contrast; pg_settings ground truth PG 14-18 (-18).
Technical Reference
- Category
- Connections and Authentication / Connection Settings
- Type
- integer ((none; a count -- the maximum number of concurrent client backends the server will allow at once))
- Blast radius
- cluster
- Severity
- Critical
- Context
- postmaster
- Versions
- 14, 15, 16, 17, 18
- Reviewed
- 2026-06-20
Severity scale: 1 Minimal · 2 Low · 3 Moderate · 4 High · 5 Critical.