Troubleshooting guide · http-status · Published 2026-08-16 · 4 min read
HTTP 504 gateway timeout
HTTP 504 gateway timeout: what the proxy is waiting for, how to read the logs for the upstream, and how to fix the slow request.
- ·What 504 means
- ·Find the slow layer
- ·Fix the timeout
504 Gateway Timeout says the proxy in the middle got nothing from the next hop within its own wait window. The browser reached the gateway, the gateway reached the upstream (PHP, a second application, or a cache host), and the work simply stopped coming back in time. It is the actual timeout you hit when the 502 story ends in the proxy.
What 504 means
The difference from a 502 is timing: a 502 is "could not connect or got garbage", a 504 is "connected but never finished in time". Both live in the gateway layer, so the fix is usually at the nginx/Apache/CDN config or the upstream (PHP-FPM), never in the page.
| Layer | Timeout that 504s | Who owns the knob |
|---|---|---|
| nginx → PHP-FPM | fastcgi_read_timeout | Server config |
| nginx → app | proxy_read_timeout | Server config |
| CDN → origin | Cloudflare proxy timeout (~100s) | Edge config |
| Browser → CDN | Gateway/user-agent | Client side |
Common causes
- The PHP worker takes longer than the proxy allows: a slow loop, a remote API, an import, or a render that exceeds
max_execution_time. - PHP-FPM is alive but every worker is busy, and the request queues at the socket.
- The upstream is a second app (an API, a search index, a solver) that itself is slow or hung.
- A firewall or load balancer silently drops a keep-alive, so the connection "times out" before the origin ever answers.
Fix in order
- Confirm 504 is live. A CDN can serve a stale 504 from cache for a while after the origin recovers. Fetch past the proxy:
curl -sI -H "Cache-Control: no-cache" https://example.com/slow-page
Read the status, then compare against the cloudflare 524 article for the edge case where the edge itself gave up.
- Read the log that knows the upstream. The nginx error log names the exact layer:
upstream timed out(fastcgi_read_timeout) vsupstream prematurely closed(app crashed). See error log reading. - Find the slow request.
top, the PHP-FPM status, and a slow-query log will show which page or job hogs the worker. If it is a specific page, keep it out of the synchronous path (push the work to a queue). - Raise the timeout only to the real ceiling. If the job must finish, raise
fastcgi_read_timeoutorproxy_read_timeout(notproxy_connect_timeout, which governs dialing, not waiting):
location ~ \.php$ {
fastcgi_read_timeout 300;
}
- If the worker pool is the limit, the symptom is "504 only when busy". Add workers or vertical power as in the 502 fix order, and cache the page that was slow.
- After the fix, tail the error log and load the page; a 200 with a clean log is the pass.
Verification table
| Test | Command | What it proves |
|---|---|---|
| Live status | curl -sI https://example.com/slow | 200, not 504 |
| Proxy layer | grep "upstream timed out" /var/log/nginx/error.log | nginx saw the issue |
| PHP health | systemctl status php-fpm | Worker pool up |
| Slow page | time curl -s https://host/slow | Runtime matches the timeout |
Prevention
- Split long jobs from the page rendering stream; a report or export that takes 10 minutes has no business living on a request thread.
- Cache the output when safe: the 504 disappears the moment the second client can hit a fast copy.
- Watch P95 latency at the proxy; when average response time approaches the timeout, the 504s are a countdown, not a blip.
When to involve a professional
If a clean re-crawl still 504s every few minutes while the origin is idle, the problem is the health of the reverse proxy or load balancer, which the host owns. Get the host to test from outside the network, not to tune a page.