Nginx

Nginx 504 Gateway Timeout on Long-Running API Request

A 504 Gateway Timeout on a request that's legitimately slow (a large report generation, a heavy computation, a batch operation) means Nginx gave up waiting for the upstream server's response before the operation actually finished — the fix is adjusting Nginx's proxy timeout settings to match how long the operation genuinely, legitimately takes.

The Problem

A specific API endpoint that does meaningful, time-consuming work fails consistently with:

504 Gateway Time-out
nginx

Checking the application's own logs shows the request was still being processed when Nginx gave up — the backend wasn't actually broken or hanging indefinitely, it just needed more time than Nginx was configured to allow.

Why It Happens

Nginx's default proxy timeout values (typically 60 seconds for several relevant settings) are reasonable defaults for most typical web traffic, but they're too short for endpoints that do genuinely long-running work by design. Common scenarios:

  • A report generation, data export, or bulk processing endpoint that legitimately takes several minutes to complete, exceeding Nginx's default timeout well before the actual work finishes
  • A file upload or download involving very large files, where the transfer itself takes longer than default timeout values allow, even though there's no actual error occurring
  • A synchronous call to a slow third-party API or service that the backend is waiting on, where the overall request duration is dominated by that external dependency's own response time
  • Only proxy_read_timeout being adjusted while other related timeout directives (proxy_connect_timeout, proxy_send_timeout) are left at defaults, when the actual bottleneck is in a different phase of the request than the one setting that got changed

The Fix

Increase the relevant proxy timeout directives for the specific location handling long-running requests, rather than changing them globally for the entire server if only certain endpoints genuinely need more time:

location /api/reports/ {
    proxy_pass http://backend;
    proxy_connect_timeout 300s;
    proxy_send_timeout 300s;
    proxy_read_timeout 300s;
}

Each of these three settings governs a different phase of the proxied connection: proxy_connect_timeout is how long Nginx waits to establish the initial connection to the upstream, proxy_send_timeout is how long between successful writes of the request to the upstream, and proxy_read_timeout is how long Nginx waits between successful reads of the response — for a slow backend operation specifically, proxy_read_timeout is usually the most directly relevant one, since the backend has likely already received the full request and connected successfully, but is just taking a long time to actually produce a response.

Scope longer timeouts to only the specific endpoints that genuinely need them, rather than raising timeouts globally across the entire server — a very long global timeout on every route can mask genuine hangs or bugs elsewhere in your application that should legitimately fail fast rather than hang for an extended period:

# Keep default (shorter) timeouts for most routes
location / {
    proxy_pass http://backend;
    # uses whatever the http/server-level defaults are
}

# Only this specific route gets extended timeouts
location /api/reports/ {
    proxy_pass http://backend;
    proxy_read_timeout 300s;
}

If the client itself also has its own timeout (a frontend fetch call, a mobile app's HTTP client), make sure that's also adjusted to match, since fixing only the Nginx side doesn't help if the client gives up waiting before Nginx's newly extended timeout is even reached.

For genuinely very long operations (multiple minutes or more), consider whether a synchronous request/response pattern is really the right architecture at all — converting the endpoint to kick off a background job and return immediately with a status you can poll, rather than holding a connection open for the entire duration, is often a more robust design than continuing to raise timeout values indefinitely as operations get longer over time.

Still Not Working?

If timeouts are correctly raised on the Nginx side but the 504 persists, check whether another layer in front of Nginx — a CDN, a cloud load balancer — has its own separate timeout setting that's shorter than what you've configured in Nginx itself. Fixing Nginx alone doesn't help if an earlier layer in the request path gives up first, and each of these layers typically requires its own separate configuration change through that provider's own settings or dashboard, independent of anything configured directly in your nginx.conf.