Blog

504 Gateway Timeout causes: slow upstreams, proxy timeout settings and how to find the layer that stalls

A 504 means a proxy stopped waiting for a slow upstream. Learn how it differs from a 502, which proxy timeouts apply and how to find and monitor the slow layer.

The most common 504 gateway timeout causes are an upstream that answers too slowly, such as a slow database query, exhausted workers or a hanging third-party API call, and a proxy timeout that is shorter than the work the request needs. A 504 is returned by a proxy, load balancer or CDN that forwarded the request and gave up waiting for the response. To fix it, you find the layer that is slow, make inner layers fail before outer ones, and watch response time so you see the slowdown before it turns into errors.

What a 504 actually means

A 504 Gateway Timeout is never generated by your application itself. It comes from something in front of it: nginx, HAProxy, a cloud load balancer, an API gateway or a CDN. That component accepted the client request, passed it upstream and did not receive a response within its configured timeout.

The important detail is that the upstream usually still exists and is often still working on the request. The proxy simply stopped waiting. The query may finish ten seconds later, write to the database and return a response that nobody reads. That is why 504s can leave behind duplicate orders or half-finished jobs when users retry.

504 vs 502: how to tell them apart

Both codes come from a proxy, but they describe different failures. A 502 means the proxy got an invalid response or none at all: the connection was refused, reset or closed early. A 504 means the connection worked, but the answer did not arrive in time. If you need the full list of 502 failure modes, the guide to 502 Bad Gateway causes and how to debug each one covers them.

The two codes often appear together because they depend on which timeout fires first. Gunicorn kills a worker after 30 seconds by default. If nginx waits 60 seconds, the worker dies first, the connection closes and the client sees a 502. If you raise the Gunicorn timeout to 120 seconds, the same slow request now produces a 504 at 60 seconds. Same root cause, different code.

  • 502: the upstream is down, crashed, refused the connection or closed it mid-response.
  • 504: the upstream is reachable but too slow, or the proxy cannot even complete a connection within the connect timeout.

The common 504 gateway timeout causes

Slow database queries and locks

A missing index, a query that scans a growing table or a row lock held by a long transaction keeps the request waiting. These problems tend to grow with data volume: an endpoint that took 200 ms at launch can take 40 seconds a year later without a single code change.

Worker or connection pool exhaustion

When all PHP-FPM children, Gunicorn workers or Node event loop capacity are busy, new requests wait in a queue. Each individual request may be fast, but the time spent waiting for a free worker pushes the total past the proxy timeout. The same happens when the application waits for a free database connection from a pool that is too small or leaking connections.

Upstream calls without timeouts

Your application calls a payment API, a mail service or an internal microservice. If that HTTP client has no timeout, one slow dependency stalls the whole request. Many HTTP libraries default to no timeout or a very long one, so this cause is easy to introduce and hard to spot.

Long-running requests by design

CSV exports, report generation, large uploads, image processing and bulk imports are legitimately slow. They work in development with small data and start timing out in production.

Network problems between proxy and upstream

A firewall rule that silently drops packets, a security group change, or an upstream address that no longer routes produces connect timeouts instead of refusals. The proxy waits for the TCP handshake until proxy_connect_timeout expires and then returns a 504.

Proxy timeout settings in nginx, load balancers and Cloudflare

Every hop in the chain has its own timeout, and the shortest one decides when the client sees an error.

nginx

nginx uses three proxy timeouts, each defaulting to 60 seconds: proxy_connect_timeout, proxy_send_timeout and proxy_read_timeout. For PHP-FPM the equivalents are fastcgi_connect_timeout, fastcgi_send_timeout and fastcgi_read_timeout. Note that proxy_read_timeout applies between two successive reads, not to the whole response. A streaming response that sends data regularly can run longer than 60 seconds.

upstream app {
    server 127.0.0.1:8000;
}

server {
    location / {
        proxy_connect_timeout 5s;
        proxy_send_timeout    30s;
        proxy_read_timeout    30s;
        proxy_pass http://app;
    }

    # Raise only where slow work is expected
    location /exports/ {
        proxy_read_timeout 120s;
        proxy_pass http://app;
    }
}

A short connect timeout is useful: a healthy upstream on the same network connects in milliseconds, so waiting 60 seconds for a handshake only delays the error.

Cloud load balancers

Managed load balancers have an idle or request timeout, often 60 seconds by default, that you configure on the load balancer or its target settings. If your nginx behind it allows 120 seconds, the load balancer still cuts the request at 60. Check this value whenever you raise a timeout further inside.

Cloudflare

When Cloudflare proxies your site, an origin that does not respond in time usually produces a 524, not a 504. A 522 means Cloudflare could not connect to the origin at all. A 504 seen through Cloudflare typically comes from your own origin proxy or load balancer and was passed through. That distinction alone tells you which side to investigate.

Why raising the timeout is rarely the fix

A 504 is a symptom of a slow layer, not a timeout that is too short. Find that layer, make inner timeouts shorter than outer ones, and move slow work out of the request path.

Raising proxy_read_timeout to 300 seconds keeps workers busy longer, which makes pool exhaustion worse under load. Instead, order the timeouts so each inner layer gives up before the one outside it. Then the application can return a clear error, log it and release resources, instead of the proxy cutting the connection blindly.

CDN / edge                 100 s
Load balancer idle         60 s
nginx proxy_read_timeout   30 s
App request timeout        25 s
DB statement timeout       10 s
Outbound HTTP client       5 s

Database-level limits are cheap to add. In PostgreSQL you can set a per-role limit:

ALTER ROLE app SET statement_timeout = '10s';

For genuinely long work, change the pattern: accept the request, enqueue a background job, return 202 Accepted with a job ID, and let the client poll or receive a notification. Exports and imports almost always belong in a queue.

How to find the slow layer

Work from the outside in and remove one layer at a time.

  1. Measure the full request with curl. This splits time into DNS, connect, TLS and time to first byte:
    curl -o /dev/null -s -w 'dns=%{time_namelookup} connect=%{time_connect} tls=%{time_appconnect} ttfb=%{time_starttransfer} total=%{time_total} code=%{http_code}\n' https://example.com/slow-endpoint
    If connect and TLS are fast but TTFB is huge, the server side is slow. If connect hangs, look at the network path.
  2. Bypass the CDN. Send the request straight to the origin while keeping the hostname: curl --resolve example.com:443:203.0.113.10 https://example.com/slow-endpoint. If it is fast here, the problem is at the edge or between edge and origin.
  3. Bypass the proxy. On the server, call the application directly, for example curl http://127.0.0.1:8000/slow-endpoint. Still slow means the application or something behind it is the problem.
  4. Read the upstream timings in nginx logs. Add a log format that records them:
    log_format timing '$remote_addr "$request" $status '
                      'rt=$request_time uct=$upstream_connect_time '
                      'uht=$upstream_header_time urt=$upstream_response_time';
    access_log /var/log/nginx/timing.log timing;
    High uct points to connection or queueing problems, high uht to slow processing, and urt close to your read timeout to requests that hit the limit.
  5. Check worker saturation. The PHP-FPM status page shows the listen queue and active processes. A non-zero queue during incidents means requests are waiting for workers, not running slowly.
  6. Check the database and dependencies. Enable the slow query log, look for lock waits, and add timing around outbound API calls. Application tracing helps here, but logging the duration of each external call is enough to start.

Monitoring response time before it turns into 504s

504s rarely appear out of nowhere. Response time on the affected endpoint climbs for days or weeks first, as tables grow or traffic increases. A monitor that only checks whether the status code is 200 stays green until the moment requests cross the timeout.

  • Check an endpoint that does real work. A static homepage served from cache stays fast while the database struggles. Monitor a page or API route that touches the database and key dependencies.
  • Set the monitor timeout below the proxy timeout. If nginx allows 30 seconds, a check timeout of 10 to 15 seconds flags the slowdown while users still get answers.
  • Alert on a response time threshold. Base it on your normal baseline rather than a generic number, for example several times the usual response time sustained over a few checks.
  • Watch the trend, not just the spikes. Rising time to first byte is an early warning; the article on what makes up TTFB and why rising values warn of outages explains how to read it.
  • Check from several regions. A timeout seen from one location may be a network path issue; one seen everywhere is your server.

m0nitor is one option for this: it checks websites, APIs and servers on a fixed interval from distributed probes in several regions, and alert workflows can notify a channel, create an incident or call a webhook when a monitor fails. Any monitoring setup that records response times and alerts on them covers the same ground.

FAQ

Is a 504 Gateway Timeout a client or server problem?

It is a server-side problem. The proxy, load balancer or CDN in front of the application timed out waiting for the upstream. Reloading the page or clearing the browser cache does not fix the cause, although a retry may succeed if the slowdown was brief.

How do I fix a 504 Gateway Timeout in nginx?

First check $upstream_response_time in the logs to see whether requests hit proxy_read_timeout or fastcgi_read_timeout. Then fix the slow part, such as a query, worker count or external call. Raise the timeout only for specific locations where slow work is expected, and make sure the load balancer in front allows at least as much time.

Why do I get a 524 instead of a 504 with Cloudflare?

Cloudflare uses 524 when it connected to your origin but did not receive a response in time. A 504 shown through Cloudflare usually comes from your origin's own proxy or load balancer. Both mean the origin is too slow, but they point to different timeouts in the chain.

Can a 504 be caused by a slow third-party API?

Yes. If your application waits on an external API without a short client timeout, that wait counts toward the proxy timeout. Set explicit timeouts on every outbound call, typically a few seconds, and handle the failure in your code instead of letting the proxy return a 504.

Product

  • Pricing
  • Features
  • Integrations
  • Security
  • Free network check

Features

  • Uptime monitoring
  • Status pages
  • Alert workflows
  • Incident management
  • Dependencies
  • AI assistant
  • Global infrastructure
  • On-call schedules
  • Enterprise SSO

Use cases

  • Websites for free
  • Cron jobs
  • SSL certificates
  • Databases
  • IP blacklists
  • GDPR compliance

Resources

  • What is a status page?
  • Documentation
  • API
  • MCP server
  • Monitoring glossary
  • Free tools
  • SLA calculator
  • SSL checker
  • DNS lookup
  • Blog
  • Changelog
  • About
  • Contact

Compare

  • Free uptime monitoring
  • UptimeRobot
  • Better Stack
  • Pingdom
  • StatusCake
  • Uptime Kuma
  • Hyperping
  • Atlassian Statuspage
  • Instatus
  • Cachet
  • Freshping