# Edge Function takes too long or times out

> Why a SupaHost Edge Function times out, what the 150-second response limit means, and patterns for long work: timeouts on calls, batching, queues and scheduled functions.

Source: https://supahost.app/kb/function-timeout · Updated 2026-11-11

## What you see

A function call hangs and then fails. Depending on where you look:

```text
HTTP/2 504
sb-error-code: IDLE_TIMEOUT
```

or, in the dashboard's **Test a function** panel, the test gives up after about a minute with no response.

## Why it happens

On SupaHost, each request must **start responding within 150 seconds**. If your handler hasn't returned a response by then, the runtime ends it with `504 IDLE_TIMEOUT`. Separately, a worker has an overall lifetime cap, so a function is not a place for work that runs for many minutes.

The Test panel in the dashboard has its own, shorter **60-second** timeout. A function that takes 90 seconds works when your app calls it, but the Test panel reports a timeout. That's the panel, not the function.

Slow functions are usually waiting, not computing: on an external API, a large database query, or a loop of sequential calls.

## How to fix it

1. **Find the slow step.** Add timing logs and read them in *Dashboard → your project → **Functions** tab → Logs card*:

   ```ts
   const t0 = performance.now()
   const rates = await fetch('https://api.example.com/rates', {
     signal: AbortSignal.timeout(15_000), // fail fast instead of hanging
   })
   console.log('rates fetched in', Math.round(performance.now() - t0), 'ms')
   ```

   Each request also gets a log line with its total duration.
2. **Run independent calls in parallel.** Ten sequential 5-second calls take 50 seconds; in parallel they take about 5:

   ```ts
   const results = await Promise.all(
     invoiceIds.map((id) => fetch(`https://api.example.com/invoices/${id}`))
   )
   ```

   Keep the batch size reasonable so you don't hit the other API's rate limit.
3. **Move the heavy query into the database.** An index or an RPC that aggregates in SQL is usually far faster than pulling rows into the function. See [Query performance and EXPLAIN](/docs/database/query-performance).
4. **Turn one long job into many short ones.** Have the request enqueue work and return right away, then let a scheduled function process the queue in small batches:

   ```sql
   -- every minute, ask the worker function to process the next batch
   select cron.schedule(
     'process-report-queue',
     '* * * * *',
     $$ select net.http_post(
          url := 'https://app-one.acme.supahost.space/functions/v1/report-worker',
          headers := jsonb_build_object(
            'Authorization', 'Bearer ' || (select decrypted_secret from vault.decrypted_secrets where name = 'service_role_key'),
            'Content-Type', 'application/json'),
          body := '{}'::jsonb
        ) $$
   );
   ```

   This assumes you saved the key in Vault as `service_role_key`. See [Queues with pgmq](/docs/cron-queues/queues) and [Running functions on a schedule](/docs/functions/scheduling).
5. **Tell the user it's in progress.** For a long report, return `202 Accepted` with a job id, and let the client poll a table or listen on a Realtime channel for the result.

## On SupaHost

The 150-second response limit and 150 MB memory per worker are fixed. SupaHost doesn't charge per invocation, so splitting one big job into many small calls doesn't cost more.

## Related

- [Edge Function limits](/docs/functions/limits)
- [Edge Function returns 503 or 504](/kb/function-503-504)
- [Processing queue messages](/docs/cron-queues/queue-consumers)
- [How do I run background jobs?](/kb/background-jobs)
