The short version
Supabase uses IPv6 by default. Most local dev environments and many hosting providers don't support IPv6, so DNS resolution fails with ENOTFOUND or hangs with ETIMEDOUT. The fix: use the transaction pooler connection string (port 6543) instead of the direct connection (port 5432). It routes through Supabase's IPv4-compatible proxy.
The error
You're running your app locally and you see:
Error: getaddrinfo ENOTFOUND db.xxxxxxxxx.supabase.co
at GetAddrInfoReq.onlookup [as oncomplete] (node:dns:1107:26)
Or:
Error: connect ETIMEDOUT 2600:1ff:f000:e000::1:5432
The first one means DNS can't resolve the hostname. The second means DNS resolved it to an IPv6 address, but the connection timed out because your network doesn't route IPv6.
Why it happens
Supabase assigns each project a hostname like db.xxxxxxxxx.supabase.co. This hostname resolves to an IPv6 address by default. If your machine or hosting provider doesn't support IPv6, the connection fails.
| Environment | IPv6 support |
|---|---|
| macOS (most setups) | Yes |
| Linux (most setups) | Yes |
| Windows (most setups) | No |
| Vercel | Yes |
| Railway | Yes |
| Render | Yes |
| Cloudflare Workers | No |
| Local Docker | Depends on config |
The three connection strings
Supabase gives you three connection strings in the dashboard (Settings → Database):
1. Direct connection (port 5432)
postgresql://postgres:[YOUR-PASSWORD]@db.xxxxxxxxx.supabase.co:5432/postgres
- Full Postgres protocol support
- Supports prepared statements,
SETcommands,LISTEN/NOTIFY - Uses IPv6 -- may fail locally or on some hosts
2. Transaction pooler (port 6543)
postgresql://postgres.[YOUR-PROJECT-REF]:[YOUR-PASSWORD]@aws-0-[region].pooler.supabase.com:6543/postgres
- Routes through Supabase's connection pooler (PgBouncer)
- IPv4-compatible -- works everywhere
-
Does NOT support prepared statements or
SETcommands - Best for most web apps
3. Session pooler (port 6543, mode=session)
postgresql://postgres.[YOUR-PROJECT-REF]:[YOUR-PASSWORD]@aws-0-[region].pooler.supabase.com:6543/postgres?pgbouncer=true&session_mode=true
- Same as transaction pooler but preserves session state
- Supports
SETcommands but not prepared statements - Use this if you need
SET search_pathor similar
Which one to use
| Use case | Connection string |
|---|---|
| Local dev (Node.js, Python, etc.) | Transaction pooler (6543) |
| Vercel / Railway / Render | Transaction pooler (6543) |
| Cloudflare Workers | Transaction pooler (6543) |
| Prisma ORM | Transaction pooler (6543) |
| Drizzle ORM | Transaction pooler (6543) |
| Django | Session pooler (6543 + session_mode) |
| Laravel | Transaction pooler (6543) |
| pg_dump / pg_restore | Direct connection (5432) |
| Migrations (Prisma, Knex) | Direct connection (5432) |
The Prisma gotcha
Prisma uses prepared statements by default. The transaction pooler doesn't support prepared statements, so you'll get:
Error: prepared statement "stmt_1" does not exist
Fix: Add ?pgbouncer=true to your connection string, or use the direct connection for migrations and the pooler for runtime queries.
# .env
DATABASE_URL="postgresql://postgres.xxx:password@aws-0-us-east-1.pooler.supabase.com:6543/postgres?pgbouncer=true"
Quick diagnosis
Test if IPv6 works
# If this returns an address, IPv6 works
dig AAAA db.xxxxxxxxx.supabase.co
# If this returns an address, IPv4 works
dig A db.xxxxxxxxx.supabase.co
Test the connection
# Direct connection (may fail with IPv6)
psql "postgresql://postgres:password@db.xxxxxxxxx.supabase.co:5432/postgres"
# Transaction pooler (should always work)
psql "postgresql://postgres.xxx:password@aws-0-us-east-1.pooler.supabase.com:6543/postgres"
FAQ
Why does my app work on Vercel but not locally?
Vercel supports IPv6. Your local machine might not. Use the transaction pooler (6543) locally and you'll get the same behavior as production.
Can I force IPv4 on the direct connection?
Yes. Add ?sslmode=require&ip=4 to the direct connection string, but this isn't officially supported and may break. Use the pooler instead.
What's the difference between transaction and session pooler?
Transaction pooler resets the connection after each transaction (faster, more scalable). Session pooler preserves session state (needed for SET commands, LISTEN/NOTIFY). Most apps should use transaction pooler.
Does the pooler affect performance?
Minimal. The pooler adds ~1-2ms of latency per query. For most web apps, this is negligible compared to network latency and query execution time.
Can I use both pooler and direct connections?
Yes. Use the pooler for runtime queries (better scalability) and the direct connection for migrations and admin tasks (full protocol support).
Top comments (0)