PostgreSQL Connection Pool Size Calculator

Give this Postgres connection pool size calculator a server and the shape of the application in front of it, and four numbers come back. It works out max_connections, the pool each worker process should keep, default_pool_size for PgBouncer and the ceiling on clients, with presets for what Supabase, Neon and AWS publish.

Storage

The formula doubles this. Between 1 and 512.

Read by the memory estimate only. Between 1 and 4,096.

The formula's effective_spindle_count, which is zero once the working set is cached. Between 0 and 64.

Containers, pods or machines running your application. Between 1 and 512.

Processes or threads inside one instance, each keeping a pool of its own. Between 1 and 128.

superuser_reserved_connections, which PostgreSQL ships at 3. Between 0 and 100.

(4 × 2) + 0 = 8 connections busy at once. Your 2 instances of a single worker split that into 2 pools of 4.

max_connections 16, pool size 4 per worker, default_pool_size 8, 20 pooler clients.

max_connections

16

your 8 plus 3 reserved and 5 spare

Pool size

4

per worker process, 4 per instance

default_pool_size

8

the whole of what the formula wants busy

max_client_conn

20

clients PgBouncer will admit, 10 of them spare

An estimate, not a measurement: 16 connections each running 2 sorts at the 4MB work_mem default would ask for 128 MB, 1% of the 16GB on this server. PostgreSQL ships with 100, which is a ceiling rather than a target.

postgresql.conf
# 4 cores, 0 spindles: the formula wants (4 * 2) + 0 = 8 connections busy at once.# 2 instances x 1 worker x 4 connections = 8, plus 3 reserved and 5 for psql and migrations.max_connections = 16superuser_reserved_connections = 3## Both take effect at server start, so this is a restart rather than a reload.# A rolling deploy runs old and new instances at once, so double the app total or drain the old ones first.
pgbouncer.ini
[databases]* = host=127.0.0.1 port=5432 [pgbouncer]pool_mode = transactiondefault_pool_size = 8max_client_conn = 20 # default_pool_size is the whole of what the formula wants busy.# PgBouncer ships with 20 and 100 in those two lines.# session mode instead of transaction keeps SET, LISTEN and temporary tables, and most of the reason you wanted a pooler.
Your driver
# node-postgres, one pool per worker processnew Pool({ max: 4 }) # PrismaDATABASE_URL="postgresql://USER:PASSWORD@HOST:5432/DB?connection_limit=4" # SQLAlchemycreate_engine(URL, pool_size=4, max_overflow=0) # max_overflow=0 holds SQLAlchemy at 4. Its default opens ten more under load, unasked.

Nothing you type is sent anywhere: the arithmetic and the preset table both ship with this page. Ctrl+Enter, Cmd+Enter on a Mac, copies the postgresql.conf block.

What it does and how to use it

Start with the server. The self-hosted tab asks for the core count, the memory and the number of spinning disks under the data directory, while a provider tab replaces the first two with a compute size and adds the connection ceiling that provider publishes for it.

The other half of the form is what connects. An app instance is a container or a machine, a worker is a process or a thread inside one, and since every worker keeps a pool of its own, a box running four Gunicorn workers arrives at the database looking like four clients. Used as a postgres max_connections calculator, the self-hosted tab works the ceiling out from that layout upwards, the connections the workers will open plus the superuser reserve plus five slots for psql and migrations. On a provider tab the ceiling is fixed, so the same arithmetic runs the other way and the panel says when the layout wants more slots than are left after the reserve. The three blocks underneath follow what you type, so the postgresql.conf lines, the pooler settings and the driver call never drift apart from each other, and none of it leaves the tab.

How many connections a PostgreSQL server should really take

For years the project wiki has carried the same answer, ((core_count * 2) + effective_spindle_count) active connections, with the spindle term falling to zero once the working set is cached. Four cores on an SSD come out at eight, which looks absurd beside the 100 that max_connections ships with, until you notice the two figures answer different questions. Eight is how many backends should be doing work at any instant. Attached and idle is a different state, and a backend sitting in it still holds a process and whatever work_mem its last sort claimed, which is what the ceiling is really rationing. Past the point where the cores are saturated, one more connection buys a context switch and a share of the lock contention rather than throughput, and that is the argument for a pooler sizing two numbers instead of one: pgbouncer default_pool_size is what the formula produced, max_client_conn is what your fleet can open.

Transaction pooling makes that ratio possible and also catches people out. The server connection goes back to the pool at COMMIT, so everything living on the session leaves with it, a SET, a prepared statement, a temporary table, a LISTEN. Supabase draws the line by port, 5432 reaching Postgres directly or Supavisor in session mode and 6543 reaching the transaction pooler, and Neon draws it by hostname with -pooler in the pooled one. Both splits are worked through where they bite hardest, in seeding a Supabase project and in seeding a Neon database, since a seed run is the long transaction with prepared statements in it that transaction pooling suits least.

FATAL: sorry, too many clients already

This is the line a client gets when every slot under max_connections is taken, and for most teams it is how they first learn the fleet has outgrown the server. PostgreSQL raises it as SQLSTATE 53300, too_many_connections, and the same code sits behind the message an ordinary role meets a little earlier, remaining connection slots are reserved for roles with the SUPERUSER attribute. On a default install that earlier refusal lands on the 98th ordinary connection, since max_connections ships at 100 and superuser_reserved_connections at three, with reserved_connections at zero; the blunter message arrives once the reserved three are gone as well. Both are refused at connect time, so no query ran and nothing has to be rolled back. The process that asked simply has nowhere to sit.

The calculator works in the direction that prevents it. Pool per worker is chosen so that instances times workers times pool lands under the slots left once the reserve is taken out, and the panel says in so many words when a layout wants more than that. On the provider tabs the ceiling is the provider's own figure, which is why a Supabase Small at 90 direct connections cannot carry ten containers of four workers holding three connections each without a pooler in between, and why max_client_conn rather than max_connections is the number to hold against your fleet.

Provider ceilings the presets carry

Every figure below is what the provider tab loads, read from the provider's own documentation on 11 September 2026 and linked under the panel. None of it is inferred, apart from one Neon number named in its paragraph.

Supabase pool size by compute size

Supabase publishes a direct connection ceiling and a pooler client ceiling per compute size, and the presets carry both.

  • Micro (2 shared cores, 1 GB): 60 direct connections, 200 pooler clients
  • Small (2 GB): 90 and 400
  • Medium (4 GB): 120 and 600
  • Large (2 dedicated cores, 8 GB): 160 and 800
  • XL (4 cores, 16 GB): 240 and 1,000
  • 2XL (8 cores, 32 GB): 380 and 1,500
  • 4XL (16 cores, 64 GB): 480 and 3,000
  • 8XL (32 cores, 128 GB): 490 and 6,000
  • 12XL (48 cores, 192 GB): 500 and 9,000
  • 16XL (64 cores, 256 GB): 500 and 12,000

Nano is missing because Supabase lists its CPU as shared without a number, so a Nano owner types what they have into the self-hosted tab. The Supabase pool size the calculator writes is a share of the direct ceiling rather than all of it, 80% while the Data API is quiet and 40% once PostgREST is busy holding connections of its own, and it lives in a dashboard field, Database settings then Connection pooling, rather than in a file. Supabase also notes that the direct column is a recommendation you can change, which is why the page treats it as a ceiling to plan against.

Neon connection limits by compute unit

Neon sets max_connections per compute size and runs PgBouncer in front of it, with default_pool_size at nine tenths of that figure and max_client_conn at 10,000 on every size. Seven connections on each size are held for the Neon superuser, so the number you can use is the published one minus seven.

  • 0.25 CU (1 GB): 104
  • 0.5 CU (2 GB): 209
  • 1 CU (4 GB): 419
  • 2 CU (8 GB): 839
  • 3 CU (12 GB): 1,258
  • 4 CU (16 GB): 1,678
  • 6 CU (24 GB): 2,517
  • 8 CU (32 GB): 3,357

RAM is the only hardware figure Neon documents per compute unit; it says CPU scales with RAM without naming a vCPU count, so the calculator starts from one core per compute unit, never fewer than one, and leaves the field editable. That core count is the one number on this page nobody can cite. The pooled hostname is the one with -pooler in it, and a query that finds no free server connection behind it waits two minutes before failing.

RDS and Aurora max_connections formula

AWS publishes a formula rather than a table, and it is the same one for RDS for PostgreSQL and for Aurora PostgreSQL, LEAST({DBInstanceClassMemory/9531392}, 5000). The presets apply it to the memory each instance class advertises, which makes every figure an upper bound. DBInstanceClassMemory is what remains after the operating system and the RDS management processes take theirs, AWS says as much on its quotas page without publishing the remainder per class, so the real default sits somewhat under each number here. Aurora adds that whatever you set should leave at least three connections spare for its own automation.

  • db.t3.micro (2 vCPU, 1 GiB): at most 112
  • db.t3.small (2 vCPU, 2 GiB): 225
  • db.t3.medium (2 vCPU, 4 GiB): 450
  • db.t3.large and db.m6g.large (2 vCPU, 8 GiB): 901
  • db.m6g.xlarge (4 vCPU, 16 GiB): 1,802
  • db.r6g.large (2 vCPU, 16 GiB): 1,802
  • db.r6g.xlarge (4 vCPU, 32 GiB): 3,604

The memory-optimised classes show what the formula rewards. A db.r6g.large has the same two vCPUs as a db.t3.large and twice its connection ceiling, because the divisor only ever sees memory while the wiki formula behind the pool size only ever sees cores. On these tabs the calculator takes the AWS figure as the ceiling and the core count for the pool, so the two numbers pull in different directions on purpose.

FAQ

Frequently asked questions

A connection is a backend process with memory of its own, and once the cores are busy the extra processes queue rather than work. Raising the ceiling moves that queue out of your pooler and into the database, where holding it costs more.

On a Supabase project the choice is already made. Supavisor sits in front of the database with nothing to install, and its pool size is a dashboard field. PgBouncer is the usual answer everywhere else, and the two settings this page writes mean the same thing in both.

It is per process, because a pool belongs to whichever process created it. Ten containers of four workers hold forty pools rather than one, so the figure labelled Pool size is what a single one of those forty gets.

The instance count stops being something you choose, since a function can run in hundreds of copies during a spike and none a second later. Neon lists exactly that as a common way to exhaust a compute size, so enter the concurrency ceiling your platform allows and point the functions at the pooled hostname.

Every one of them stays in the tab. The arithmetic is a few dozen lines of JavaScript and the preset table is a static file, both already loaded with the page, so there is nothing to send and no connection string to ask you for.

The queries themselves are safe. Trouble starts with anything that assumes the session is yours, since a prepared statement, a SET, a temporary table and a LISTEN all live on the server connection, which goes back to the pool at COMMIT and may belong to someone else a moment later. The driver block on this page carries the two switches that matter most, pgbouncer=true on a Prisma URL and prepare_threshold=None for psycopg, and an ORM that relies on named prepared statements without a switch like that should talk to port 5432 or the unpooled host instead.

One worker needs as many as it can keep busy at once, which for a synchronous worker is one and for an async worker or a thread pool is the number of queries it runs concurrently. The calculator gives every worker an equal share of the formula's answer, so four instances of four workers on an eight-core server get one each. When even one per worker adds up to more than the formula wants busy, the panel says that the rest will queue, which is the case a pooler exists for.