Get started Dashboard
Troubleshooting ·
Part 6 of Platform playbooks

Vercel 504 Gateway Timeout on Serverless Functions: Causes and Fixes

Explore with AI

A 504 with the code FUNCTION_INVOCATION_TIMEOUT means your Vercel Function ran past its maximum duration. With fluid compute that limit is 300 seconds by default on every plan, and Pro and Enterprise can raise it to 800 seconds. Find the cause with vercel logs and vercel httpstat (usually a slow upstream call, a code path that never responds, a loop that never exits or an unhandled exception), put a timeout on every outbound call, and raise maxDuration only when the work really needs longer.

On this page

A 504 Gateway Timeout from a Vercel Function comes with the code FUNCTION_INVOCATION_TIMEOUT. It means the invocation ran past its maximum duration and Vercel stopped it. Fluid compute is on by default for new projects, and under it every plan starts at 300 seconds. Pro and Enterprise teams can raise a function to 800 seconds, or to 1,800 seconds in beta on supported runtimes.

Most timeouts come from four places: a slow upstream API or database, a code path that never sends a response, a loop that never exits, or an unhandled exception. This page shows how to work out which one you have using the Vercel CLI and Observability. It then gives the fix for each, and shows how to raise the limit safely when the work really does take longer.

What FUNCTION_INVOCATION_TIMEOUT means

When a function runs out of time, the caller sees an error page like this one:

This Serverless Function has timed out.
504: GATEWAY_TIMEOUT
Code: FUNCTION_INVOCATION_TIMEOUT
ID: dub1::t6bwb-1726118697497-a4671f12a86e

The Code line is the part that matters. FUNCTION_INVOCATION_TIMEOUT means the invocation took longer than its allowed execution time. Maximum duration is the longest an invocation can run before Vercel ends it. For a request handler, that time covers processing the request and sending the response, and it includes streamed responses. Work you hand to waitUntil runs against the same deadline, and Vercel cancels it when the function times out.

So the clock starts when the request arrives. It keeps running until the last byte is sent and the last background promise settles. Anything inside that window counts: a database query, a fetch to a model provider, a retry loop, or a promise that never resolves.

Other function errors look similar but point somewhere else. A request or response body over 4.5 MB returns 413: FUNCTION_PAYLOAD_TOO_LARGE. That is a size problem, and no timeout setting will fix it.

The duration and memory limits your function runs under

Default maximum duration

seconds
Hobby 300
Pro and Enterprise 300

Highest configurable duration

seconds
Hobby 300
Pro and Enterprise 800

Maximum memory

GB
Hobby 2
Pro and Enterprise 4
Figure 1
Vercel Function limits with fluid compute, from the Vercel Functions limits docs

These are the numbers to check before you change any code:

  • Hobby: 300 seconds default and 300 seconds maximum. You cannot raise it on Hobby.
  • Pro and Enterprise: 300 seconds default, 800 seconds maximum. The 800 second maximum is generally available.
  • Extended maximum (beta): up to 1,800 seconds on Pro and Enterprise. It only works for nodejs20.x, nodejs22.x, nodejs24.x, Bun 1.x and 1.4.x, and python3.12 to python3.14. You set it per function, because project defaults above 800 seconds are not supported yet. Secure Compute and Static IPs cap at 800 seconds during the beta.
  • Memory: 2 GB and 1 vCPU by default. Pro and Enterprise can go to 4 GB and 2 vCPU. A function with too little memory gets CPU-throttled, so heavy computation runs slower and gets closer to the limit.
  • Region: functions run in iad1 by default. If your database lives elsewhere, every query pays the distance.

Two other clocks catch people out. Functions on the Edge runtime must begin sending a response within 25 seconds, and can then keep streaming for up to 300 seconds. Separately, the proxied request timeout is 120 seconds on every plan.

Older blog posts and forum answers still quote the much lower limits from before fluid compute. Check the Vercel limits page before you plan around a number.

Finding which cause is behind your 504

Link the project first with vercel link. Then work through these steps in order. Each one rules a cause in or out.

  1. Confirm the function is hitting its ceiling. Run vercel inspect against the deployment URL and note the memory, region and max duration it is running with. Then open the Vercel Functions tab in Observability and sort by duration. If the failing requests all end at about the configured maximum, the function is hitting the limit.
vercel inspect <deployment-url>
  1. List recent function requests. The --source serverless filter drops static assets and CDN requests. The JSON output gives you the path and status code for each request, but not its duration.
vercel logs --environment production --source serverless --since 1h --json \
  | jq '{path: .requestPath, statusCode: .responseStatusCode}'
  1. Search for upstream failures. Timeout and connection-refused messages point at a dependency, and away from your own code.
vercel logs --environment production --query "timeout" --since 1h --expand
vercel logs --environment production --query "ECONNREFUSED" --since 1h --expand
  1. Measure where the time goes. vercel httpstat breaks one request into DNS, TCP, TLS, server processing and content transfer. Server processing is the time spent inside your function. If that is the big number, the problem is your code or what it calls.
vercel httpstat /api/slow-endpoint
  1. Rule cold starts in or out. Call the endpoint three times. If the first call is much slower than the next two, cold starts are adding latency. They push a function over the limit only when it was already close.

  2. Find the slow dependency. The External APIs tab in Observability shows latency for every outbound call your functions make. Sort by P75 to find the slowest upstream service.

  3. Look for an unhandled exception. Vercel calls this a common cause of timeouts. Application logs are also available at the /_logs path on the deployment URL, for example https://my-deployment-my-username.vercel.app/_logs.

Act quickly on step 2. Runtime logs are kept for 1 hour on Hobby, 1 day on Pro and 3 days on Enterprise. A timeout that happened overnight on Hobby may have no logs left by morning.

If the logs show a request running right up to the limit with no output at all, there is nothing to tell you where it stalled. I built and led the Workers observability team at Cloudflare, and the habit I’d push hardest on any serverless platform is one structured log line before and after every outbound call:

const started = Date.now();
const res = await fetch('https://api.example.com/orders', {
  signal: AbortSignal.timeout(10_000),
});
console.log(JSON.stringify({ step: 'orders-api', ms: Date.now() - started, status: res.status }));

The fix for each cause

A slow upstream API or database

Make sure every call can fail before the function does. Work out how much time is left with getDeadline() from @vercel/functions, keep a reserve back, and return your own error when the upstream is too slow:

import { getDeadline } from '@vercel/functions';

function msLeft(reserveMs = 2_000) {
  const deadline = getDeadline();
  if (!deadline) return 30_000; // running outside Vercel, e.g. local dev
  return Math.max(0, deadline.getTime() - Date.now() - reserveMs);
}

export async function GET() {
  try {
    const res = await fetch('https://api.example.com/report', {
      signal: AbortSignal.timeout(msLeft()),
    });
    return Response.json(await res.json());
  } catch (err) {
    console.error(JSON.stringify({ step: 'report-api', error: String(err) }));
    return Response.json({ error: 'upstream timed out' }, { status: 503 });
  }
}

Then reduce the time each call takes:

  • Move the function region next to your database. Every round trip adds the distance between them.
  • Cache database and API results that don’t change on every request.
  • Run independent calls together with Promise.all, so their waits overlap.
  • Pool database connections. Functions share 1,024 file descriptors across concurrent executions, and the runtime uses some of them. Connections you don’t close build up until you see “too many open files” errors.

A code path that never returns a response

A function has to return an HTTP response, even if that response is an error. If it returns nothing, it waits until the maximum duration runs out. The usual culprit is a branch that forgets to respond:

// pages/api/order.ts
export default async function handler(req, res) {
  const order = await getOrder(req.query.id);
  if (order) {
    return res.status(200).json(order);
  }
  // Missing: nothing is sent when the order does not exist,
  // so the request hangs until the function times out.
  return res.status(404).json({ error: 'order not found' });
}

Check every if, every catch and every early exit. Each one has to end in a response.

A loop that never exits

A loop or recursive call that never ends runs the function to its limit and leaves no output. Give long loops a time budget, and return a cursor so the caller can pick up where the loop stopped:

import { getDeadline } from '@vercel/functions';

const hasTime = () => {
  const d = getDeadline();
  return !d || d.getTime() - Date.now() > 2_000;
};

export async function GET(request: Request) {
  let page = Number(new URL(request.url).searchParams.get('page') ?? '1');
  const synced = [];
  while (hasTime()) {
    const { records, hasMore } = await getRecords(page);
    synced.push(...records);
    if (!hasMore) return Response.json({ synced });
    page += 1;
  }
  return Response.json({ synced, resumePage: page });
}

An unhandled exception

A rejected promise nobody awaits can leave a request with no response. Wrap the body of the handler in try and catch, log the error, and return a 500. Under fluid compute, an uncaught exception in Node.js is logged. Vercel lets the requests already running finish before it stops the process, so one bad request doesn’t crash its neighbours. The failing request still needs your handler to answer it.

Work too long for one request

For AI responses and other slow output, stream. The client gets the first bytes before the full computation finishes. Over HTTP/2, Vercel sends PING frames while a long response sits idle. HTTP/1.1 has no equivalent, so clients and proxies can close a quiet connection. Send heartbeat data while the work runs:

export const maxDuration = 300;

export async function GET() {
  const enc = new TextEncoder();
  const stream = new ReadableStream({
    async start(controller) {
      const beat = setInterval(() => controller.enqueue(enc.encode(': keep-alive\n\n')), 15_000);
      try {
        const result = await buildReport();
        controller.enqueue(enc.encode(`data: ${JSON.stringify(result)}\n\n`));
      } finally {
        clearInterval(beat);
        controller.close();
      }
    },
  });
  return new Response(stream, { headers: { 'Content-Type': 'text/event-stream' } });
}

For work that has to run for minutes or longer, use Vercel Workflows. They can pause, resume and keep state from minutes up to months, with no duration limit.

Raising maxDuration when the work is legitimately long

In Next.js 13.5 and later, SvelteKit, Astro, Nuxt, Remix and Node.js, set the duration in the function itself. For the Next.js App Router, export it from the route file:

// app/api/report/route.ts
export const maxDuration = 120;

For older Next.js, Rust, Go, Python and Ruby, use the functions object in vercel.json. The order of the patterns matters. If your Next.js project uses a src directory, start the paths with src/.

{
  "$schema": "https://openapi.vercel.sh/vercel.json",
  "functions": {
    "api/report.js": { "maxDuration": 120 },
    "api/*.js": { "maxDuration": 15 }
  }
}

FastAPI, Flask and Django apps build into one function, so use the entrypoint file as the key, for example app/main.py. To change the default for the whole project, open Settings, then Functions, then Function Max Duration. You can only go above 800 seconds per function, on a supported runtime:

// app/api/long-task/route.ts
export const maxDuration = 1800;

If the project is old enough to have fluid compute turned off, turn it on under Settings, then Functions, or add "fluid": true to vercel.json. Then redeploy.

What goes wrong when you fix Vercel timeouts

  • Raising the limit hides a hung call. A request that stalls for 300 seconds will stall for 800 once you raise the limit. You are billed for provisioned memory for as long as the instance runs, even while the code waits on I/O. Put timeouts on the upstream calls first.
  • Moving work into waitUntil to beat the clock. Promises passed to waitUntil get the same timeout as the function and are cancelled when it expires. In Next.js 15.1 and later, Vercel recommends after() for this work, and its tasks are bound by the route’s maxDuration too.
  • A duration above 800 seconds that never applies. It fails when set as a project default, on an unsupported runtime version, or on a project with Secure Compute or Static IPs. Set it per function on a runtime from the beta list.
  • The vercel.json pattern doesn’t match. A missing src/ prefix or patterns in the wrong order leave the function on the default. Run vercel inspect after deploying and check the value you meant to set.
  • Hobby projects past 300 seconds. Hobby can’t go higher. Stream, split the work, move it to Workflows or upgrade to Pro.
  • An Edge function that thinks first. Edge functions must start their response within 25 seconds. Send headers or a first chunk early.
  • A slow proxied origin. A rewrite to a slow backend hits the 120 second proxied request timeout, whatever maxDuration you set on your functions.
  • The evidence is gone. Runtime logs on Hobby last 1 hour. Reproduce against a preview straight away, or ship logs somewhere that keeps them longer.

Keeping 504s from coming back

  1. Time every dependency. Record upstream latency as a custom metric with metric() from @vercel/functions, for example metric('upstream.duration_ms', ms, { api: 'orders' }). Then chart and filter it in Observability. You can call metric() up to 100 times per invocation.
  2. Keep a reserve. Base upstream timeouts on getDeadline(), so the function always has a couple of seconds left to send a proper error.
  3. Check the slowest routes before users do. Sort the Vercel Functions tab by duration, and the External APIs tab by P75, after each release.
  4. Measure the fix on a preview first. Deploy with vercel deploy, then compare the before and after numbers.
vercel deploy
vercel httpstat /api/slow-endpoint --deployment <preview-url>
vercel curl /api/slow-endpoint --deployment <preview-url>
vercel deploy --prod
vercel logs --environment production --source serverless --since 5m

Tracing Vercel timeouts to their cause with Polylane

Polylane connects to Vercel over OAuth, counts Vercel logs and metrics as a telemetry source, and triages Vercel alerts in the same lifecycle as its own checks. Each confirmed timeout becomes an issue worked by one fix run. The fix run reads the logs, recent deploys and your code, writes out the causal chain with evidence for each link, and opens a pull request for you to review when a code change fixes the cause.

Running on Vercel? See how Polylane monitors Vercel in production.

Common questions.

What is the maximum duration of a Vercel Function?

With fluid compute, every plan defaults to 300 seconds. Hobby can't go higher. Pro and Enterprise can set up to 800 seconds, and up to 1,800 seconds in beta for supported Node.js, Bun and Python versions, set per function.

Can I stop 504 timeouts on the Hobby plan by raising maxDuration?

No. On Hobby, 300 seconds is both the default and the maximum. Put timeouts on slow upstream calls, stream the response, split the work into pages with a resume cursor, move long jobs to Vercel Workflows, or upgrade to Pro for up to 800 seconds.

Does waitUntil or after() let background work run past the timeout?

No. Promises passed to waitUntil have the same timeout as the function, and Vercel cancels them if the function times out. getDeadline() returns one deadline that covers both request processing and waitUntil tasks. In Next.js 15.1 and later, after() tasks are bound by the route's maxDuration too.

How do I set maxDuration in a Next.js App Router route?

Export it from the route file, for example export const maxDuration = 120; in app/api/report/route.ts. Next.js versions older than 13.5 use the functions object in vercel.json. If you use a src directory, those paths need the src/ prefix.

Why is my maxDuration above 800 seconds not taking effect?

During the beta, values above 800 seconds only work when set on each function, never as a project default. They also need nodejs20.x, 22.x or 24.x, Bun 1.x or 1.4.x, or python3.12 to 3.14. Secure Compute and Static IPs don't support more than 800 seconds yet.

Where do I find logs for a function that timed out?

Run vercel logs --environment production --source serverless --since 1h --expand, or open the /_logs path on the deployment URL. Runtime logs are kept for 1 hour on Hobby, 1 day on Pro and 3 days on Enterprise, so pull them soon after the incident.

Are cold starts causing my 504?

Call the endpoint three times with vercel httpstat. If only the first call is slow, cold starts are adding latency. Smaller bundles, moving setup code out of the handler, and more memory all help. Fluid compute also uses bytecode caching on Node.js 20 and later in production.

Why does my Edge function time out much sooner than 300 seconds?

Functions on the Edge runtime must begin sending a response within 25 seconds. After that they can keep streaming for up to 300 seconds. Send headers or a first chunk early, or move the route to the Node.js runtime.

Sources

  1. Vercel Functions Limits
  2. FUNCTION_INVOCATION_TIMEOUT (Vercel error reference)
  3. Configuring Maximum Duration for Vercel Functions
  4. How to stop Vercel Functions from timing out
  5. Debugging slow Vercel Functions
  6. Fluid compute
  7. @vercel/functions API Reference (Node.js)
  8. Vercel Limits
  9. Vercel Community: max time before the serverless function times out
  10. Polylane documentation
  11. Polylane full content

About the author

Boris Tane

Founder of Polylane

Boris Tane is the founder of Polylane. He previously founded Baselime, observability for the future of the cloud, which Cloudflare acquired. At Cloudflare he built and led the Workers observability team.

More in this series

Platform playbooks

  1. 1 How to Monitor a Django App on Render
  2. 2 How to Monitor a FastAPI App on Railway: Logs, Traces and Alerts
  3. 3 How to Monitor a Supabase App in Production
  4. 4 How to Debug Cloudflare Workers Errors: Logs, Traces and Error Codes
  5. 5 How to debug Vercel function timeouts
  6. 6 Vercel 504 Gateway Timeout on Serverless Functions: Causes and Fixes
  7. 7 Cloudflare Workers error 1101: causes and how to fix it
  8. 8 Cloudflare Hyperdrive connection errors: causes and fixes
  9. 9 How to Monitor a Convex App in Production

Related

Nobody should be on-call. Polylane watches your infra, finds what broke, and writes the fix.

Get started for free