Fix this error · Servers and hosting
Fix: FATAL: sorry, too many clients already (Postgres and Prisma P2024)
Fix Postgres connection exhaustion in serverless: pooled strings for Supabase and Neon, Prisma 7 and 6 pool settings, one client per process.
Postgres has a fixed number of connection slots, and every one is taken. In a serverless app this usually happens because each function instance opens its own pool of connections. The fix that works most often is to connect through your provider's pooled connection string (Supabase transaction mode on port 6543, or Neon's -pooler host), keep each instance's pool small, and create one database client per process.
| Fact | Detail |
|---|---|
| Error | FATAL: sorry, too many clients already |
| Also seen as | FATAL: remaining connection slots are reserved for roles with the SUPERUSER attribute · Prisma P2024: Timed out fetching a new connection from the connection pool |
| SQLSTATE | 53300 (too_many_connections) |
| Where you see it | Server logs, Vercel or other serverless function logs, your Postgres client |
| Usual cause | Many serverless instances, each with its own pool, connecting directly to the database |
| Usual fix | Use the pooled connection string and a small pool per instance |
| BugTape catches it | Yes, through the Node SDK, when the error reaches your framework or is left unhandled |
What the error means
Postgres allows max_connections connections at once, and keeps a few of them for superusers and roles with reserved-connection privileges. When the ordinary slots run out, a new connection gets one of two errors:
sorry, too many clients alreadywhen every slot is in use.remaining connection slots are reserved for roles with the SUPERUSER attributewhen only reserved slots are left. Postgres 15 and earlier printremaining connection slots are reserved for non-replication superuser connections.
Prisma has its own pool in front of the database. In Prisma ORM 6 and earlier, a query that waits too long for a free connection in that pool fails with:
P2024: Timed out fetching a new connection from the connection pool. More info: http://pris.ly/d/connection-pool (Current connection pool timeout: 10, connection limit: 5)P2024 means Prisma's own pool was busy. The database may still have free slots. In Prisma ORM 7, the pool belongs to the driver adapter (for example pg), and its timeout and size are set on the adapter.
Common causes
- Serverless instances connecting directly. Each function instance creates its own client and its own pool. During a traffic spike, many instances times the pool size is more than the database allows. Paused instances can keep their connections open.
- A new client per request.
new PrismaClient()ornew Pool()inside the handler opens a new pool for every request. - Hot reload in development. Next.js reloads the module that creates the client, and each reload creates another client.
- Too many parallel queries.
Promise.allover a large list sends more queries than the pool has connections, so the rest wait and time out (P2024). - Connections that are never released. A
pool.connect()withoutclient.release()in afinallyblock, or a long transaction left open. - A small database plan. Small instances have a low
max_connections.
How to fix it
See who holds the connections
Connect with a direct (non-pooled) string, using a role that can see other sessions, and run:
SHOW max_connections;
SELECT usename, application_name, state, count(*)
FROM pg_stat_activity
GROUP BY usename, application_name, state
ORDER BY count(*) DESC;Many idle connections from your app user point to pools in serverless instances. Many idle in transaction connections point to transactions that are never committed.
Use your provider's pooled connection string
A pooler such as PgBouncer or Supavisor accepts many client connections and shares a small number of database connections between them.
Supabase. Use the shared pooler in transaction mode on port 6543 for serverless and edge functions. Supabase says transaction mode does not support prepared statements, so turn them off in your client.
postgresql://postgres.[PROJECT-REF]:[YOUR-PASSWORD]@[POOLER-HOST]:6543/postgresNeon. Add -pooler to the endpoint id in the host name. Neon runs PgBouncer in transaction mode.
postgresql://user:password@ep-cool-darkness-123456-pooler.us-east-2.aws.neon.tech/dbname?sslmode=requireRun migrations over the direct connection string. Transaction pooling breaks tools that need one session for the whole run.
Configure Prisma for a pooler
Prisma ORM 7. The runtime client uses the pooled string through a driver adapter, and the CLI uses the direct string from prisma.config.ts. Keep max small in serverless functions.
# .env
DATABASE_URL="postgresql://USER:PASSWORD@POOLER-HOST:6543/postgres?pgbouncer=true"
DIRECT_URL="postgresql://USER:PASSWORD@DIRECT-HOST:5432/postgres"// prisma.config.ts
import 'dotenv/config';
import { defineConfig, env } from 'prisma/config';
export default defineConfig({
schema: 'prisma/schema.prisma',
datasource: { url: env('DIRECT_URL') },
});// lib/db.ts
import { PrismaClient } from '../prisma/generated/client';
import { PrismaPg } from '@prisma/adapter-pg';
const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL, max: 3 });
export const prisma = new PrismaClient({ adapter });Prisma recommends ?pgbouncer=true for Supabase Supavisor and for PgBouncer older than 1.21.0. For PgBouncer 1.21.0 or later, Prisma recommends leaving it off.
Prisma ORM 6. Set the pool size in the connection URL. Without an external pooler, Prisma's serverless advice is to start with connection_limit=1.
postgresql://USER:PASSWORD@HOST:5432/DATABASE?connection_limit=1Create one client per process
Create the client once at module level, outside the handler. In development, store it on globalThis so hot reload reuses it:
// lib/db.ts
import { PrismaClient } from '../prisma/generated/client';
import { PrismaPg } from '@prisma/adapter-pg';
const globalForPrisma = globalThis as unknown as { prisma?: PrismaClient };
export const prisma =
globalForPrisma.prisma ??
new PrismaClient({ adapter: new PrismaPg({ connectionString: process.env.DATABASE_URL, max: 3 }) });
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;Release connections and let Vercel close idle ones
With node-postgres, always release a checked-out client. On Vercel with Fluid compute, attachDatabasePool from @vercel/functions releases idle pool clients before the function suspends.
import { Pool } from 'pg';
import { attachDatabasePool } from '@vercel/functions';
export const pool = new Pool({ connectionString: process.env.DATABASE_URL, max: 3 });
attachDatabasePool(pool);
export async function getUser(id: string) {
const client = await pool.connect();
try {
const { rows } = await client.query('SELECT * FROM users WHERE id = $1', [id]);
return rows[0];
} finally {
client.release();
}
}Limit parallel queries
Run a large batch in small groups so it never needs more connections than the pool has:
for (let i = 0; i < ids.length; i += 5) {
await Promise.all(ids.slice(i, i + 5).map((id) => prisma.order.update({ where: { id }, data: { synced: true } })));
}How to catch it in production with BugTape
These errors happen on the server, so the Node SDK reports them. In Next.js 15 and later on the Node runtime, a Prisma or pg error thrown in a page, route handler, Server Action or middleware reaches onRequestError. In Express, add bugtape.expressErrorHandler() after your routes; it reports errors that end in a 5xx. A rejected query that nothing awaits is reported as an unhandled rejection. If you catch the error yourself and return a fallback, call bugtape.captureException(err) in the catch, or BugTape never sees it.
// instrumentation.ts
export async function register() {
if (process.env.NEXT_RUNTIME === 'nodejs') (await import('@bugtape/node')).init({ service: 'web' });
}
export async function onRequestError(...args: Parameters<typeof import('@bugtape/node').captureRequestError>) {
if (process.env.NEXT_RUNTIME === 'nodejs') await (await import('@bugtape/node')).captureRequestError(...args);
}The SDK replaces numbers in messages before sending, so repeats of the same error from the same code location group into one issue with a count, even when the pool size or timeout in the P2024 message differs. That count shows when the problem starts during a traffic spike. Each report carries the route pattern, such as POST /api/orders. Server reports have no replay or console output.
In the browser, the SDK reports a failed request only when the error response arrives within 8 seconds of the click that started it. A P2024 that waits out a 10-second pool timeout arrives after that window. Requests still running after 15 seconds are reported as hung.
Set release (the SDK reads VERCEL_GIT_COMMIT_SHA on Vercel) so a fix that does not hold reopens the issue as a regression. Your coding agent can read the issue over MCP (npx -y bugtape connect claude, or cursor or codex) and propose the change. You review the pull request.
FAQ
Should I just raise max_connections?
Raising it lets more processes connect, but each connection uses database memory, and Prisma warns that this can hurt performance or exhaust the server's resources. On managed Postgres the value is often tied to the instance size. A pooler fixes the cause, which is many clients each holding connections.
What is the difference between P2024 and "too many clients already"?
too many clients already comes from Postgres when the database has no free slots. P2024 comes from Prisma's own pool when a query waited too long for one of that pool's connections. You can get P2024 while the database has free slots, for example when one function runs many queries in parallel.
Do I need ?pgbouncer=true?
With Prisma and Supabase Supavisor, Prisma recommends it. With PgBouncer 1.21.0 or later, Prisma recommends leaving it off. Either way, run Prisma Migrate over a direct connection, because the schema engine needs a single session.
Why does this happen in development?
Next.js hot reload re-runs the module that creates your client, and each run opens a new pool. Store the client on globalThis outside production so every reload reuses it.
Sources
- PostgreSQL source: postinit.c and proc.c (error text), errcodes.txt (53300), checked 2 Nov 2026.
- Prisma engines: P2024 message, checked 2 Nov 2026.
- Prisma ORM 7: Database connections, Connection pool and PgBouncer, checked 2 Nov 2026.
- Prisma ORM 6: Database connections (
connection_limit=1), checked 2 Nov 2026. - Supabase: Connecting to Postgres, checked 2 Nov 2026.
- Neon: Connection pooling, checked 2 Nov 2026.
- Vercel: attachDatabasePool, checked 2 Nov 2026.
Related errors
- Vercel FUNCTION_INVOCATION_TIMEOUT:
504: GATEWAY_TIMEOUT Code: FUNCTION_INVOCATION_TIMEOUT - Vercel FUNCTION_INVOCATION_FAILED:
500: INTERNAL_SERVER_ERROR Code: FUNCTION_INVOCATION_FAILED - Supabase row-level security violation:
new row violates row-level security policy for table - Express: Cannot set headers after they are sent:
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client - Node.js unhandled promise rejection:
This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch()
All guides: Fix an error.
Start free. Free plan · No credit card · Set up in minutes. Or try the live demo first.