Connections and timeouts
How database connections are shared on a SupaHost instance (max_connections 400, small per-project pools, no pooler) and how to check and set statement timeouts per role, function or session.
Every SupaHost instance runs one Postgres server, and every project on the instance is a separate database on that server. Connections and some settings are therefore shared across projects. This page explains what that means for your app and how to set timeouts so one slow query can't tie things up.
How connections are shared
| Item | Value on SupaHost |
|---|---|
| Postgres servers per instance | 1 |
max_connections |
400, shared by all projects on the instance |
| Data API (PostgREST) pool | 5 connections per project |
| Auth (GoTrue) pool | 5 connections per project |
| Connection pooler (Supavisor) | Not available |
| Direct connections from outside | Business and Enterprise only, through the SSH tunnel |
Your app doesn't open database connections itself. supabase-js talks HTTP to the Data API, Auth and Storage, and those services hold a small pool of connections each. That's why thousands of users can share a handful of connections: each request borrows one for a few milliseconds.
The flip side is that the Data API can run at most five queries at once per project. If a request takes two seconds, it holds one of those five for two seconds and other requests queue behind it. Keeping queries fast (see Query performance and EXPLAIN) matters more here than raw connection counts.
Other connections come from Storage, Realtime, Studio while it's open, cron jobs, pg_net, MCP queries and, on Business, your own tunnel clients. With many projects on one instance these add up, so close idle tunnel sessions and give long-lived tools like ORMs a small pool size.
Check connection use
Count sessions on the whole server and in your project's database:
select
count(*) as total_on_server,
count(*) filter (where datname = current_database()) as this_project,
current_setting('max_connections')::int as max_connections
from pg_stat_activity;
And see who holds them in your project:
select usename, application_name, state, count(*)
from pg_stat_activity
where datname = current_database()
group by usename, application_name, state
order by count(*) desc;
If you hit the limit you'll see errors like remaining connection slots are reserved. Too many connections walks through finding the source.
Serverless and ORM connections
Because there's no pooler, don't point serverless functions (Vercel, Netlify, Cloudflare) at Postgres directly, even on Business: each cold start can open new connections. Use supabase-js and the Data API from serverless code, or keep direct connections to long-running servers with a small pool (2 to 5). See Can I connect from serverless and edge functions?.
Statement timeouts
A statement timeout cancels any query that runs longer than the limit, with the error canceling statement due to statement timeout. It protects the shared pools from runaway queries.
SupaHost doesn't set its own timeout values. Whatever is configured comes from the Supabase Postgres image or from settings you add. Check the current values before changing anything:
-- Settings stored on roles (applies to Data API requests as these roles)
select r.rolname, d.datname as only_in_database, s.setconfig
from pg_db_role_setting s
join pg_roles r on r.oid = s.setrole
left join pg_database d on d.oid = s.setdatabase
where r.rolname in ('anon', 'authenticated', 'service_role', 'authenticator', 'postgres');
-- The value in effect for your current session
show statement_timeout;
A null in only_in_database means the setting applies in every database on the server.
Per role
Timeouts set on a role apply to every request that runs as that role. To give signed-in users eight seconds:
alter role authenticated set statement_timeout = '8s';
notify pgrst, 'reload config';
The notify tells the Data API to reload its configuration so the new value takes effect.
Remember that roles belong to the server, not a project. The statement above changes the timeout for authenticated in every project on the instance. To change it for one project only, name its database. Project databases are called p_ plus the project name with dashes turned into underscores; select current_database(); shows yours.
alter role authenticated in database p_app_one set statement_timeout = '15s';
notify pgrst, 'reload config';
A role-and-database setting takes priority over a role-only setting. Remove one with alter role authenticated in database p_app_one reset statement_timeout;.
Per function
For one known slow operation, such as a monthly report, set the timeout on the function rather than raising it for a whole role:
create or replace function public.monthly_invoice_totals(month date)
returns table (customer_id bigint, total numeric)
language sql
stable
set statement_timeout = '30s'
set search_path = ''
as $$
select customer_id, sum(total)
from public.invoices
where date_trunc('month', issued_on) = date_trunc('month', month)
group by customer_id;
$$;
Called through supabase.rpc('monthly_invoice_totals', { month: '2026-10-01' }), it gets 30 seconds. Remember it still holds one of the five Data API connections while it runs.
Per session or transaction
To raise the limit for one piece of work, such as a large backfill, use set local inside a transaction. This works in a single run of Studio's SQL editor:
begin;
set local statement_timeout = '10min';
update public.invoices set currency = 'USD' where currency is null;
commit;
Over psql through the SSH tunnel (Business and Enterprise), a plain set statement_timeout = '10min'; lasts for the whole session, which suits statements that can't run in a transaction, such as create index concurrently. In Studio, don't rely on a session-level set carrying over from one run to the next.
Other timeouts worth setting
| Setting | What it stops |
|---|---|
idle_in_transaction_session_timeout |
Sessions that opened a transaction and went quiet, holding locks |
lock_timeout |
Statements waiting too long for a lock, useful in migrations |
A migration that fails fast instead of queueing behind a busy table:
set lock_timeout = '5s';
alter table public.invoices add column paid_at timestamptz;
Edge Functions have their own limit: a function must start responding within 150 seconds. See Edge Function limits.