Guides

504 Gateway Timeout: which request is taking too long, and how to find out

A practical guide to finding which request is causing the 504, on Nginx+PHP-FPM and on cPanel, with exact commands and why raising the timeout almost never fixes it.

UptimeMag editorial team · 8 October 2026 · 6 min read

504 Gateway Timeout: quale richiesta sta impiegando troppo e come scoprirlo

The quickest check: look at the exact time of the error

Before getting into the theory, do this. Take the precise time the 504 appeared (from the browser, from an external monitor, or from the access log) and search for that same minute in the server's error log. On Ubuntu 24.04 with Nginx, the default path is /var/log/nginx/error.log (confirmed by the official nginx documentation). The command:

sudo grep "2026/10/08 1" /var/log/nginx/error.log

On cPanel there's no Nginx log (Apache/LiteSpeed is used instead); the file to check is /usr/local/apache/logs/error_log for EasyApache 3 installations, or /etc/apache2/logs/error_log on EasyApache 4. If you see a line containing upstream timed out, you already have the technical cause: a backend didn't respond in time. If you see nothing, the problem lies further upstream (the provider's load balancer, firewall, network), which we cover at the end.

502 vs 504: the difference in two lines

A 502 means the proxy received a malformed response, or no connection at all, from the upstream (the PHP process has died, the port isn't responding). A 504 means the connection was established, but nobody responded within the maximum allowed time. A 502 is a broken upstream; a 504 is a slow upstream.

Where the timeouts sit, and which one expires first

In a PHP request behind Nginx there are at least three independent timers, in a cascade:

Layer Directive Default value
Nginx (proxy to PHP-FPM) fastcgi_read_timeout / proxy_read_timeout 60 seconds
PHP-FPM (single request) request_terminate_timeout 0 = disabled
MySQL (slow query, logging only, no blocking) long_query_time 10 seconds

The Nginx figure is confirmed by several independent technical sources, which agree on a default of 60 seconds for proxy_read_timeout. The PHP-FPM figure I found directly in the official PHP manual, where request_terminate_timeout has a default value of 0, i.e. "Off". This means something inconvenient: if your PHP script hangs for 10 minutes (an unindexed query, an external call that never responds), PHP-FPM won't kill it on its own. Nginx gives up first, after 60 seconds, and returns the 504. The PHP-FPM process, however, stays there, busy, until it finishes on its own or until you run out of available workers.

With MySQL the situation is different: long_query_time only serves to record slow queries in the slow query log, it doesn't interrupt them. The minimum and default values of long_query_time are 0 and 10 seconds respectively, and the slow query log is normally disabled by default.

How to find the slow request — Nginx + PHP-FPM on Ubuntu 24.04

First step: the Nginx error log. The typical message is the one the technical documentation reports as the standard line for these events: "upstream timed out (110: Connection timed out) while reading response header from upstream". That line also tells you the requested URL and the backend's IP, so you immediately know which page caused the timeout.

If the Nginx log isn't enough (perhaps because the timeout expired on the PHP-FPM side before Nginx had even finished waiting — rare, but possible with badly configured timeouts), check the PHP-FPM slowlog. It isn't enabled by default: it needs to be switched on in the pool file, typically /etc/php/8.3/fpm/pool.d/www.conf on Ubuntu 24.04 with PHP 8.3, by adding:

request_slowlog_timeout = 10s
slowlog = /var/log/php8.3-fpm-slow.log

After editing the file, reload the service:

sudo systemctl reload php8.3-fpm

From that point on, every request exceeding 10 seconds ends up in the slowlog with the full PHP stack trace: function, file, line. It's the most direct way to work out whether the bottleneck is a query, an external HTTP call, or an infinite loop in the code.

How to find the slow request — cPanel

On cPanel with PHP-FPM enabled for the account, the per-user slowlog file is already tracked by the system: it's located at /var/cpanel/php-fpm/USERNAME/logs/slow.log (replace USERNAME with the actual cPanel user). If the site is still using PHP as an Apache module (mod_php) instead of PHP-FPM, this file won't exist: in that case the only useful trace is Apache's generic log, /usr/local/apache/logs/error_log, which, however, doesn't show the stack trace — only the URL and the timestamp.

To find out which PHP engine your account is using, go to MultiPHP Manager in WHM: if "PHP-FPM" appears as active in the domain's column, the per-user slowlog is available and it's worth enabling it (in MultiPHP INI Editor, under request_slowlog_timeout, set it to a value lower than the timeout of the Apache/Nginx layer in front, e.g. 10s if the gateway timeout is 60s).

The most common causes, in order

1. Unindexed query or a long lock. This is the most common cause on sites running a CMS (WooCommerce, large catalogues, reports). You can spot it because the PHP slowlog stack trace ends up inside a call to mysqli_query or an ORM, and MySQL's slow log (if enabled) shows the same query with a Rows_examined figure much higher than Rows_sent. It's fixed by running EXPLAIN on the query and adding the missing index, not by touching the timeouts.

2. Heavy imports, exports or cron jobs triggered from an HTTP request. You can spot it because the URL in the slowlog is always the same endpoint (/admin/import.php, /export/csv) and the request is manual, not scheduled. The correct fix is to move the work to the background (a queue, a command-line cron job) and have the page respond immediately with "processing in progress".

3. A call to an external service that isn't responding. The slowlog stack trace shows a function such as curl_exec or file_get_contents hitting an external URL (payment gateway, third-party API, webhook). The fix is to set a short timeout in the code (CURLOPT_TIMEOUT) so it's the application that fails fast, rather than Nginx blocking everything for 60 seconds.

4. Backups or snapshots saturating I/O and CPU at the same time. You can spot it because the 504s appear at fixed times (e.g. 3am) and hit random requests, not a specific endpoint. The fix is to move the backup window or throttle the backup process's I/O (ionice), not raise the gateway timeout.

Why raising the timeout is the wrong fix (except in two cases)

Raising proxy_read_timeout or fastcgi_read_timeout doesn't make the query any faster: it only keeps the visitor and the PHP worker waiting longer, tying up a connection and a process that, at that moment, aren't serving anyone else. On a server with a finite number of PHP-FPM workers (pm.max_children), just a handful of these "long blocks" running in parallel is enough to saturate the pool and cause 502s and 504s to appear even on perfectly ordinary requests.

The two cases where raising the timeout is legitimate:

  • Planned, one-off administrative operations (a one-time bulk import, a data migration), on a separate endpoint isolated from normal traffic, with the timeout raised only for that specific location in Nginx, never globally.
  • Integrations with external services that are inherently slow by nature (complex PDF generation, video rendering, calls to providers that state response times beyond 60 seconds), provided the code still enforces a lower internal timeout so it doesn't hang indefinitely.

In both cases, the timeout should only be raised on the specific location, never in the global http block.

How to verify it's fixed

After the fix (index added, work moved to a queue, internal timeout set on the external service), repeat the same request that used to fail and check two things: that no more upstream timed out lines appear in the Nginx log for that URL, and that the measured response time (with curl -o /dev/null -s -w "%{time_total}\n" https://yoursite.co.uk/page) is consistently below the configured timeout, not just barely under it. If the PHP slowlog was active, verify that it stops logging that endpoint.

If the 504 might be the provider's fault

If neither the Nginx nor the Apache log shows any timeout line matching the error, the problem is probably further upstream: a load balancer or CDN belonging to the provider with a shorter timeout than yours, or a firewall cutting off long-lived connections. In this case, open a support ticket attaching: the exact URL, the precise time (with timezone) the 504 appeared, the client IP if available, and the outcome of the check on your own local log ("no timeout line in our error log during that window"). This allows the provider to check their own edge logs without having to ask follow-up questions, and shortens the ticket's response time.

Written with the help of artificial intelligence and checked by the editors (EU AI Act, art. 50).