Prisma Timeout Errors on Supabase Pooler (Transaction vs Session Mode)

Prisma timeout errors on Supabase pooler almost always show up the same way: everything works fine while you're the only one testing, and the moment real concurrent traffic hits, Prisma starts hanging and eventually timing out waiting for a database connection that never arrives. The query is fine. The schema is fine. The problem is nearly always which of Supabase's two pooler modes your app is actually connected through, and how few concurrent clients that mode was ever designed to support.
Short answer: if you're seeing timeouts under real concurrency, you're very likely connected through Supavisor's session mode, which grants each client an exclusive backend connection and runs out of room fast — switch your application's runtime DATABASE_URL to transaction mode instead, and reserve session mode for a separate direct connection used only by Prisma Migrate. Supabase's own port assignments for these two modes changed as recently as February 2025, so if you're following an older guide, it may already be describing a setup that doesn't exist anymore.

Why Prisma Times Out on Supabase Under Real Load
Supabase's connection pooler, Supavisor, offers two fundamentally different modes, and they scale to very different amounts of concurrency:
- Transaction mode (the pooler hostname on port 6543) shares a small number of real Postgres backend connections across many client connections, handing one out only for the duration of a single transaction before returning it to the pool. This is what lets a handful of real connections serve a much larger number of concurrent application instances.
- Session mode (the pooler hostname on port 5432) gives each connecting client an exclusive backend connection for as long as that client stays connected — no sharing at all. It supports things transaction mode doesn't, like prepared statements and session-level state, but at a dramatically lower total concurrency ceiling.
If your application's regular runtime traffic is wired to session mode — often because an older tutorial or a first working setup used it — everything looks fine at low concurrency, because you haven't hit the ceiling yet. Real traffic does, and every connection request beyond that ceiling queues behind clients that are still holding theirs, until Prisma's own connection timeout fires.
(If you've been raising Prisma's timeout value hoping the connection eventually comes through — it won't arrive faster by waiting longer. There's a hard ceiling on session-mode slots, and a bigger number in a config file doesn't create more of them.)
A Port Assignment That Changed Recently
Here's the detail that trips up a lot of existing guides: as of February 28, 2025, Supabase deprecated session mode on port 6543 entirely. Before that date, port 6543 could serve either mode depending on configuration. Supabase's own troubleshooting docs on Supavisor terminology confirm the current, post-deprecation state plainly: port 6543 on the pooler hostname is transaction mode only, and session mode now lives on port 5432 on that same pooler hostname — a different address than the separate direct-connection hostname, which also happens to use port 5432. If you're debugging this by pattern-matching against a Stack Overflow answer or blog post from before that date, the port-to-mode mapping it describes may simply no longer be accurate.

Which Prisma Operations Actually Need Session Mode
Not every operation is fine under transaction mode's shared-connection model. Named prepared statements — which Prisma creates by default — don't survive across the connection boundary transaction mode introduces between each client's transactions, which is the mechanism behind the "prepared statement already exists" error covered separately. Beyond that specific error, anything genuinely session-scoped — session-level SET statements, advisory locks held across multiple round trips, and Prisma Migrate's own schema-management operations — needs the exclusive, stable connection session mode provides. Regular application queries, including Prisma's interactive $transaction() calls, work fine under transaction mode as long as prepared statements are disabled via the connection string.
The Correct Connection String Setup for Each Use Case
Supabase's own current Prisma integration guide recommends splitting the two concerns explicitly:
1# .env — runtime traffic through transaction mode, migrations through session mode
2DATABASE_URL="postgres://[DB-USER].[PROJECT-REF]:[PASSWORD]@aws-0-[region].pooler.supabase.com:6543/postgres?pgbouncer=true"
3DIRECT_URL="postgres://[DB-USER].[PROJECT-REF]:[PASSWORD]@aws-0-[region].pooler.supabase.com:5432/postgres"1// prisma.config.ts — Prisma Migrate uses the session-mode connection specifically
2import 'dotenv/config';
3import { defineConfig, env } from 'prisma/config';
4
5export default defineConfig({
6 schema: 'prisma/schema.prisma',
7 datasource: {
8 url: env('DIRECT_URL'),
9 },
10});Your application's PrismaClient at runtime uses DATABASE_URL — transaction mode, scaled for real concurrency, with pgbouncer=true disabling the prepared statements that mode can't support. Migrate uses DIRECT_URL — session mode, which gives it the stable, exclusive connection schema changes actually need.

The Opinion Part
Here's the pattern worth naming, because it's the same one underneath most of the Supabase-and-Prisma bugs in this genre: a managed platform's defaults and documentation are a moving target, not a fixed spec you learn once. Supabase changed a port assignment that a meaningful chunk of existing tutorials still describe incorrectly, and the fix for that isn't memorizing today's port numbers — it's checking the provider's current docs against your actual connection string every time something in this stack starts behaving strangely, rather than trusting a two-year-old blog post that was completely accurate when it was written. That's not a knock on Supabase; it's the honest cost of building on any managed platform that improves over time, and it's exactly the kind of unglamorous verification step teams skip when a setup "already works" — until traffic reveals it was working by coincidence.
Conclusion
If Prisma is timing out against Supabase under real traffic, check which pooler mode your DATABASE_URL actually points at before touching timeout values. Session mode's exclusive-connection model runs out of room fast under concurrency; transaction mode scales much further but needs pgbouncer=true to avoid the prepared-statement conflict, and Prisma Migrate needs its own session-mode DIRECT_URL reserved for schema changes. Confirm your setup against Supabase's current docs directly, since the port assignments here have already changed once.
If you've fixed the timeout and hit the prepared-statement error next, that's the expected next step, not a new bug — both fixes are meant to be applied together. And if the same project is also fighting connection exhaustion on Vercel specifically, our serverless connection pool guide covers the client-side half of the same underlying concurrency problem.
Split the two connection strings the way Supabase actually recommends today, and enjoy timeouts that stop happening instead of timeout values that just get longer.
Frequently Asked Questions
The most common cause is running the application's regular runtime traffic through Supavisor's session mode, which grants each client an exclusive, dedicated backend connection for as long as it's connected. Session mode supports far fewer total concurrent clients than transaction mode, which shares a small number of real connections across many logical ones — under light testing you rarely exceed session mode's ceiling, but real concurrent traffic does, and every request past that ceiling queues and eventually times out.
Transaction mode (port 6543 on the pooler hostname) shares a small pool of real Postgres connections across many client connections, handing a backend connection to a client only for the duration of a single transaction — it scales to far more concurrent clients but doesn't support named prepared statements. Session mode (port 5432 on the pooler hostname) gives each client an exclusive backend connection for its entire session, supporting prepared statements and session-level features, but at a much lower total concurrency ceiling.
For serverless or high-concurrency runtime traffic, Supabase's own current guidance recommends the pooler's transaction mode on port 6543 with pgbouncer=true appended to the connection string. Reserve the pooler's session mode on port 5432 for a separate DIRECT_URL used specifically by Prisma Migrate, which needs a stable, exclusive connection rather than one shared across many clients.
Yes — as of February 28, 2025, Supabase deprecated session mode on port 6543 entirely. Port 6543 on the pooler hostname now serves transaction mode exclusively, and session mode moved to port 5432 on the same pooler hostname, distinct from the separate direct-connection hostname that also uses port 5432. Any setup guide written before that date describing 6543 as session mode is describing a configuration that no longer exists.
It depends on which connection method you're using. If you're connecting via the standard TCP connection string through Supavisor's transaction mode, pgbouncer=true is still what disables Prisma's named prepared statements to avoid the transaction-pooling prepared-statement conflict. If you've moved to a driver-adapter-based setup, the equivalent behavior is configured through the adapter itself rather than a connection-string flag.
