Configuring PgBouncer pool_size and max_client_conn
This guide is part of PgBouncer Transaction vs Statement Pooling. Choosing a pooling mode is the first decision; sizing the pools is the second, and it is the one that determines whether PgBouncer absorbs your connection load or becomes the bottleneck itself.
PgBouncer has two independent sides, and almost every sizing mistake comes from conflating them. On the client side it accepts connections from your applications, and that number can be very large. On the server side it opens connections to PostgreSQL, and that number must stay inside the database’s ceiling. The whole point of the proxy is that these two numbers differ by an order of magnitude.
The settings that matter
| Setting | Side | What it bounds | Default |
|---|---|---|---|
max_client_conn |
client | total client connections PgBouncer will accept across all pools | 100 |
default_pool_size |
server | server connections per (database, user) pool | 20 |
min_pool_size |
server | server connections kept warm per pool | 0 |
reserve_pool_size |
server | extra server connections lent to a pool under pressure | 0 |
max_db_connections |
server | hard cap on server connections to one database, across all pools | 0 (unlimited) |
max_user_connections |
server | hard cap on server connections for one user, across all pools | 0 (unlimited) |
The default max_client_conn of 100 is the setting that catches everyone. It is a global cap across every pool, not per database, and PgBouncer refuses new clients with no more connections allowed (max_client_conn) once it is reached. For a proxy whose purpose is to accept thousands of clients, 100 is far too low; it exists as a conservative default, not a recommendation.
Sizing the server side
Server-side sizing starts at the database, not at PgBouncer. Work down from max_connections:
- Start with the database ceiling. Suppose
max_connections = 200andsuperuser_reserved_connections = 3. - Subtract operational headroom. Reserve 20–30 connections for migrations, monitoring, replication slots, and a human with
psqlduring an incident. That leaves roughly 170. - Set
max_db_connectionsbelow that. With one PgBouncer instance,max_db_connections = 160guarantees the proxy can never consume the whole ceiling, no matter how many pools exist. - Divide across pools. Each distinct
(database, user)combination is its own pool with its owndefault_pool_size. Five such pools atdefault_pool_size = 25is 125 server connections — comfortably inside 160. - Divide again by PgBouncer instance count. If you run three PgBouncer replicas behind a load balancer, each one gets a third of the budget. This is the step most often skipped, and it is why a working single-instance configuration blows past
max_connectionsthe moment it is made highly available.
[databases]
orders = host=db.internal port=5432 dbname=orders pool_size=25
reports = host=db.internal port=5432 dbname=orders user=reporter pool_size=8
[pgbouncer]
pool_mode = transaction
max_client_conn = 5000
default_pool_size = 25
min_pool_size = 5
reserve_pool_size = 5
reserve_pool_timeout = 3
max_db_connections = 160
max_user_connections = 60
server_idle_timeout = 600
server_lifetime = 3600
The per-database pool_size= override in the [databases] section is how you give a reporting workload a smaller share than the transactional one. Without it, every pool inherits default_pool_size and a batch job can occupy as many server connections as the request path.
The right value for default_pool_size in transaction mode is much smaller than intuition suggests. In transaction pooling a server connection is only held for the duration of a transaction, so a pool of 25 can serve thousands of clients whose transactions are a few milliseconds each. Sizing it toward the database’s CPU and disk parallelism — rather than toward client count — is the correct instinct, and the arithmetic is the same as in Connection Pool Sizing Formulas.
Sizing the client side
max_client_conn should be comfortably above the sum of every application pool’s maximum, plus headroom for reconnect churn during a deploy. During a rolling restart both the old and new pods hold connections briefly, so the peak is higher than steady state.
max_client_conn >= (pods x pool max) x 1.5 + admin sessions
= (120 x 20) x 1.5 + 20
= 3620 -> round up to 5000
Client connections are cheap in PgBouncer — a socket, a small buffer, and an entry in an internal list. The practical constraint is file descriptors: each client connection uses one, and PgBouncer needs max_client_conn + max_db_connections + a small constant descriptors available. Raise the process limit accordingly, or PgBouncer will fail to accept connections with accept() failed: Too many open files long before max_client_conn is reached:
# systemd unit
[Service]
LimitNOFILE=16384
PgBouncer also allocates pkt_buf bytes per connection (2048 by default), so 5000 clients is roughly 10 MB of buffers — negligible. Raising pkt_buf to 8192 for large result sets multiplies that by four; still small, but worth knowing before setting it to something exotic.
reserve_pool_size and min_pool_size
These two are frequently left at their defaults of zero, and both are useful.
min_pool_size keeps a floor of warm server connections per pool. Without it, a pool that goes fully idle closes all its server connections, and the next request pays a full PostgreSQL connection setup — process fork, authentication, catalog load — inside its own latency. Setting min_pool_size = 5 on a pool with pool_size = 25 costs five idle backends and removes that cliff. Note that min_pool_size only applies while the pool has been used at least once; PgBouncer does not pre-warm pools at startup.
reserve_pool_size is an overflow allowance. When clients have been waiting longer than reserve_pool_timeout seconds, PgBouncer allows the pool to grow by up to reserve_pool_size extra server connections. It converts a hard queue into a soft one for brief spikes. The trap is that reserve connections count against max_db_connections too, so the true worst case per pool is pool_size + reserve_pool_size, and your arithmetic in the previous section must use that number.
Common failure patterns and remediation
| Symptom | Underlying cause | Remediation |
|---|---|---|
no more connections allowed (max_client_conn) |
Global client cap left at the default 100 | Raise max_client_conn above the fleet’s total pool maximum with margin |
accept() failed: Too many open files |
File descriptor limit below max_client_conn |
Raise LimitNOFILE above clients plus server connections |
FATAL: sorry, too many clients already from PostgreSQL |
Server side unbounded, or replicas each using the full budget | Set max_db_connections; divide it by the PgBouncer instance count |
High cl_waiting with low database CPU |
default_pool_size too small for the transaction rate |
Raise pool_size for that pool, or shorten transactions |
High cl_waiting with saturated database CPU |
Pool is correctly sized; the database is the bottleneck | Do not raise pool_size — it will make latency worse |
| Latency spike on the first request after a quiet period | min_pool_size at zero, pool fully collapsed |
Set min_pool_size to a small non-zero value |
| Brief spikes cause timeouts despite headroom elsewhere | No reserve configured | Set reserve_pool_size and a short reserve_pool_timeout |
| One tenant starves the others | All pools share default_pool_size and no per-user cap |
Set per-database pool_size= and max_user_connections |
Verifying the configuration
SHOW POOLS on the admin console is the authoritative view. The columns that matter for sizing are cl_active, cl_waiting, sv_active, sv_idle, and maxwait:
psql -h 127.0.0.1 -p 6432 -U pgbstats pgbouncer -c "SHOW POOLS;"
Read it this way. sv_active at pool_size with cl_waiting at zero is a correctly sized pool at full utilisation — not a problem. sv_active at pool_size with cl_waiting above zero and maxwait growing means clients are queuing, and the next question is whether the database is saturated. If PostgreSQL CPU is low, raise pool_size; if it is high, the pool is already the correct size and raising it converts queue time into contention. Reading these columns in depth is covered in PgBouncer Metrics Monitoring.
SHOW DATABASES shows the effective pool_size, reserve_pool and max_connections per database after all overrides are applied, which is the fastest way to confirm a config reload actually took effect:
psql -h 127.0.0.1 -p 6432 -U pgbstats pgbouncer -c "SHOW DATABASES;"
Operational boundary
These settings decide how many connections exist and how many clients may queue. They do not make queries faster, and pool_size is not a throughput dial you can turn upward indefinitely. Past the point where the database’s CPUs and disks are saturated, a larger pool increases concurrency without increasing work done, and total latency rises. Confirm the database has spare capacity before raising pool_size, using the server-side view in PostgreSQL Server-Side Connection Diagnostics.
Note also that these limits are per PgBouncer process. PgBouncer is single-threaded, so scaling past roughly 10,000–20,000 client connections or saturating one CPU core means running multiple instances — at which point every server-side number must be divided among them.
Frequently Asked Questions
Is max_client_conn per database or global?
max_db_client_connections setting or by giving each database its own PgBouncer instance.What is a pool, exactly?
(database, user) pair. Ten application users connecting to the same database create ten pools, each with its own pool_size. This is why max_db_connections exists — it caps the sum.Should pool_size be larger in session mode?
pool_size must be at least the number of concurrent clients. That is why session mode gives up most of the multiplexing benefit; see PgBouncer Transaction vs Statement Pooling.Do reserve connections count against max_db_connections?
pool_size + reserve_pool_size when checking the total against the database ceiling.Why does my application pool also need a limit?
max: 200 per pod is what drives max_client_conn upward. Keeping application pools small is still worthwhile even behind a proxy — it bounds queue depth at the layer closest to the request.How do I change these without dropping connections?
pgbouncer.ini and issue RELOAD; on the admin console. Most settings, including pool_size and max_client_conn, apply to new connections without disrupting existing ones. Changing pool_mode is the notable exception and warrants a maintenance window.Does min_pool_size pre-open connections at startup?
Related
- PgBouncer Transaction vs Statement Pooling — the parent guide on choosing a pooling mode.
- Connection Pool Sizing Formulas — the arithmetic behind
pool_size. - PgBouncer Metrics Monitoring — reading
SHOW POOLSto validate these numbers. - PgBouncer vs RDS Proxy vs pgpool-II — choosing the proxy in the first place.
- PostgreSQL Server-Side Connection Diagnostics — confirming the database has headroom before raising
pool_size.