The Error

Your app suddenly fails with one of these:

FATAL: sorry, too many clients already
FATAL: remaining connection slots are reserved for non-replication superuser connections
FATAL: too many connections for role "app_user"

Every PostgreSQL connection is a separate server process using memory. Postgres caps them with max_connections (100 by default). When your app opens more than that, new connections are refused.

The fix is rarely "raise the limit." It's usually connection pooling and fixing code that opens too many connections.


🎯 Quick Fix

  1. See who's connected (query below) and kill idle connections if you're in an emergency.
  2. Use a connection pooler: PgBouncer, Supabase's pooler, Neon's pooled connection string, or RDS Proxy.
  3. Create the database client once per process, not per request.
  4. In serverless, always use the pooled connection string and keep each function's pool size small.

Why It Happens

flowchart LR subgraph Serverless["Serverless functions (scale out)"] F1["fn 1
pool: 10"] F2["fn 2
pool: 10"] F3["fn ...50
pool: 10"] end F1 --> PG[("Postgres
max_connections = 100")] F2 --> PG F3 --> PG PG --> X["❌ 500 connections wanted,
100 allowed"]

Common causes:

  • Serverless scaling: each function instance opens its own connections. 50 instances × 10 connections = 500.
  • Creating a new client per request instead of reusing one.
  • Connection leaks: connections opened and never released (missing release() / finally).
  • Dev hot reload: frameworks like Next.js re-run modules on reload, creating new clients each time.
  • Idle transactions: sessions stuck "idle in transaction" hold connections forever.

Step 1: See Who's Connected

SELECT usename, application_name, client_addr, state, count(*)
FROM pg_stat_activity
GROUP BY 1, 2, 3, 4
ORDER BY count(*) DESC;

SHOW max_connections;

Look for a single app or IP holding many connections, or lots of idle and idle in transaction sessions.

Emergency cleanup (terminates idle sessions older than 10 minutes; be careful in production):

SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE state = 'idle'
  AND state_change < now() - interval '10 minutes'
  AND pid <> pg_backend_pid();

Step 2: Add a Connection Pooler

flowchart LR A1["App / functions
hundreds of client connections"] --> PB["Pooler
(PgBouncer, Supavisor,
RDS Proxy)"] PB -->|"~20 real connections"| PG[("Postgres")]

A pooler accepts many lightweight client connections and shares a small number of real Postgres connections between them.

Platform What to use
Self-hosted PgBouncer in front of Postgres
Supabase The pooler connection string (transaction mode for serverless) from your project's Connect settings
Neon The pooled connection string (hostname contains -pooler)
AWS RDS / Aurora RDS Proxy
Watch out: in transaction pooling mode, session features like session-level prepared statements, SET and advisory locks may not behave as expected. Many ORMs need a setting for this, such as Prisma's pgbouncer=true connection parameter.

Step 3: Fix Your Code

Reuse one pool per process (Node.js):

// db.js — created once, imported everywhere
import pg from "pg";
export const pool = new pg.Pool({
  connectionString: process.env.DATABASE_URL,
  max: 5,                    // small per instance, especially in serverless
  idleTimeoutMillis: 10_000,
});

// always release
const client = await pool.connect();
try {
  await client.query("SELECT 1");
} finally {
  client.release();
}

Avoid duplicate clients during Next.js hot reload:

const globalForDb = globalThis as unknown as { pool?: pg.Pool };
export const pool = globalForDb.pool ?? new pg.Pool({ connectionString: process.env.DATABASE_URL, max: 5 });
if (process.env.NODE_ENV !== "production") globalForDb.pool = pool;

Kill stuck transactions automatically:

ALTER DATABASE mydb SET idle_in_transaction_session_timeout = '60s';

Step 4: Only Then, Consider max_connections

Raising max_connections (in postgresql.conf, followed by a restart) can help, but each connection uses memory, and hundreds of active connections can make Postgres slower, not faster. Managed providers often tie the limit to instance size. Treat a higher limit as a last step, after pooling.


Checklist

  • Checked pg_stat_activity for the biggest consumers
  • Using a pooler (PgBouncer, Supabase/Neon pooled URL, RDS Proxy)
  • One client/pool per process, reused across requests
  • Every connection released in a finally
  • Small pool size per serverless instance
  • idle_in_transaction_session_timeout set

Frequently Asked Questions

What is the default max_connections in PostgreSQL?

The default is 100, though managed providers often set a different value based on instance size. A few slots are reserved for superusers, which is why you may see "remaining connection slots are reserved" before reaching the full number.

Why do serverless apps run out of Postgres connections?

Each serverless function instance runs separately and opens its own connections. Under traffic, the platform starts many instances at once, multiplying connections far beyond the database's limit. A connection pooler and small per-instance pools solve this.

Should I just increase max_connections?

Usually not as a first step. Every Postgres connection is a process using memory, and too many active connections reduce performance. Pooling lets many clients share a small number of real connections, which scales much better.


Conclusion

"Too many connections" means your app opens more Postgres connections than the server allows. Find who's holding them, put a connection pooler in front of the database, reuse one pool per process, release every connection, and keep serverless pools small. Raise max_connections only after that.


Sources

Last verified: 2 November 2026.


Related reads: PostgreSQL vs MySQL · Prisma vs Drizzle · Serverless vs Containers · Fix Supabase RLS Policy Error

I write practical guides for developers. Follow on LinkedIn or X, or invite me to speak.