What a 504 Gateway Timeout means
A 504 Gateway Timeout means a server that sits in front of your application, acting as a gateway or proxy, waited for the upstream server behind it and gave up before a response arrived. RFC 9110, the current HTTP specification, defines it in exactly those terms: the gateway did not get a timely response from the upstream server it needed to finish the request.
The key point is that the server showing you the error is usually not the one with the problem. Nginx, a load balancer or a CDN returned the 504; the slow part is PHP, Node, Python, the database or an external API further back. Fixing a 504 means finding which hop is slow and why.
504 vs 502 vs 503
| Code | What the proxy saw | Typical cause |
|---|---|---|
| 502 Bad Gateway | An invalid response from upstream | App crashed, socket refused, PHP-FPM worker killed mid-request |
| 503 Service Unavailable | The server says it cannot handle the request right now | Overload or planned maintenance; may send a Retry-After header |
| 504 Gateway Timeout | No response from upstream within the time limit | Slow query, slow external API, long-running script |
Quick-fix checklist
- Reload once. If the page loads, a single slow request or a brief load spike was the cause.
- Check whether only one URL fails (a report, an export, checkout) or the whole site. One URL points at slow code; every URL points at an overloaded server or a stuck database.
- Time the request with
curl(below) to see how long the backend actually takes. - Read the proxy error log for
upstream timed outlines and note the URL. - Check the database slow query log and the PHP-FPM slow log for that URL.
- Fix or move the slow work first. Raise timeouts only when the work is genuinely long and cannot be queued.
Visitor side vs site owner side
If you are a visitor, there is little you can fix. Reload after a minute, try another network, and if you use a VPN or corporate proxy, try without it, because a proxy on your side can time out too. If the site works for others but not for you, check your network, firewall and DNS settings. Otherwise, tell the site owner the URL and the time it failed.
If you own the site, a 504 is a performance or architecture problem on your servers. The rest of this guide is for you.
Find the slow part
Time the request with curl
Run this from your own machine and again from the server itself:
curl -s -o /dev/null -w "dns %{time_namelookup} connect %{time_connect} tls %{time_appconnect} first byte %{time_starttransfer} total %{time_total} code %{http_code}\n" https://example.com/slow-page
time_starttransfer is the time until the first byte arrives, so it includes the server's processing time. If DNS, connect and TLS take milliseconds but first byte takes 60 seconds, the delay is in your application, not the network.
Read the proxy error log
On Nginx, a 504 usually leaves a line like this in /var/log/nginx/error.log:
upstream timed out (110: Connection timed out) while reading response header from upstream, ... request: "GET /report HTTP/1.1", upstream: "fastcgi://unix:/run/php/php8.3-fpm.sock"
That tells you the URL and which upstream was slow. List recent ones with grep "upstream timed out" /var/log/nginx/error.log | tail.
Turn on the PHP-FPM slow log
PHP-FPM can dump a backtrace of any request that runs longer than a threshold. This is off by default, because request_slowlog_timeout defaults to 0. In the pool file (for example /etc/php/8.3/fpm/pool.d/www.conf), set a threshold and a log path in a directory that already exists:
request_slowlog_timeout = 5s
slowlog = /var/log/php8.3-fpm-slow.log
Reload PHP-FPM with sudo systemctl reload php8.3-fpm, reproduce the 504, and the backtrace shows the function the script was stuck in: a database call, a curl_exec to an API, or a loop.
Turn on the MySQL slow query log
The slow query log is off by default, and long_query_time defaults to 10 seconds, which is far too generous for a web page. Enable it at runtime from a MySQL account with admin privileges (the log directory must be writable by the mysql user, and the new long_query_time applies to connections opened after the change):
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;
SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';
These settings are lost on restart, so add them under [mysqld] in the config file if you want to keep them. Summarise the log with mysqldumpslow -s t -t 10 /var/log/mysql/slow.log to see the queries that cost the most total time.
Common causes and fixes
Slow database queries
A query that scans a large table without an index can take longer than the proxy is willing to wait. Run EXPLAIN on the slow query, add the missing index, and avoid running one query per row inside a loop. Reports and exports that genuinely need to read millions of rows belong in a background job, not in a page request.
Waiting on an external API
A payment gateway, SMS provider or shipping API that hangs will hold your request open for as long as your HTTP client allows. Always set your own short timeout on outbound calls, for example CURLOPT_TIMEOUT in PHP or timeout= in Python's requests, so a stuck third party produces a clean error you can handle instead of a 504.
Long-running work in the request
Imports, image processing, bulk emails and PDF generation should not run while a visitor waits. Accept the request, put the job on a queue or schedule it with a cron job, return immediately, and let the browser poll for the result. This removes the timeout problem instead of moving it.
PHP limits vs proxy limits
Several timers run at once, and the shortest one wins:
- PHP
max_execution_timedefaults to 30 seconds. On Linux it normally does not count time spent in system calls and stream operations, so a script waiting on a slow API or socket can run well past 30 seconds of wall-clock time. - PHP-FPM
request_terminate_timeoutkills the worker after a fixed time per request, as a backstop whenmax_execution_timedoes not stop the script. It defaults to 0 (off). When it kills a worker, Nginx usually reports a 502 rather than a 504. - Nginx
fastcgi_read_timeout(for PHP-FPM) andproxy_read_timeout(for an app server such as Node or Gunicorn) both default to 60 seconds. They measure the gap between two successive reads from upstream, not the whole response. - A CDN in front adds its own limit (see Cloudflare below).
If PHP is allowed 300 seconds but Nginx waits only 60, the visitor gets a 504 at 60 seconds while the script carries on in the background.
Raising timeouts carefully
When a request is legitimately long, such as an admin export that cannot yet be queued, raise the limit only for that path rather than for the whole site:
location /admin/export {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root/index.php;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_read_timeout 300s;
}
Adjust the fastcgi_pass socket and script path to match your existing PHP location block. Keep the timers in a sensible order: PHP's own limit lowest, request_terminate_timeout slightly above it, and the Nginx read timeout above both, so PHP stops first and returns a real error. For a proxied app, the same idea applies with proxy_read_timeout. Test with sudo nginx -t before sudo systemctl reload nginx. Raising timeouts site-wide hides slow code and lets stuck requests tie up workers, which can turn one slow page into a full outage.
Overloaded server
If every page returns 504 at once, look at the machine: top or htop for CPU and memory, and the PHP-FPM log for warnings that pm.max_children has been reached. When all workers are busy, new requests queue until the proxy gives up. Fix the slow requests first; add workers only if you have the memory for them.
Cloudflare and other CDNs
When a site is proxied through Cloudflare (orange cloud), Cloudflare waits up to 125 seconds by default for your origin to respond. If the origin does not answer in time, Cloudflare shows its own error 524, not a 504, and only Enterprise zones can raise that limit, up to 6,000 seconds. If you see a 504 while using Cloudflare, it usually came from your own origin proxy, which timed out first.
Cloudflare's suggested fixes match the ones above: log origin response times, run long work as a background job the browser polls, or serve long-running endpoints such as large exports from a DNS-only (grey cloud) subdomain that bypasses the proxy.
How this applies on Bigdomainhost
On Web & cPanel hosting, sites run on LiteSpeed in Mumbai, with the PHP version selectable per domain, phpMyAdmin and cron jobs; WordPress hosting also runs on LiteSpeed in Mumbai. The fixes that matter most are in your own code: index slow queries, set timeouts on outbound API calls, and move heavy work into a cron job. SSH is available on request on Web & cPanel plans. If a 504 persists, contact support with the URL and the time it failed.
On a KVM VPS you have root access, so you control the Nginx, PHP-FPM and MySQL settings above yourself. The server firewall denies inbound traffic by default except SSH on port 22, so open ports 80 and 443 in the panel's per-instance firewall rules before testing. If a configuration change locks you out, the out-of-band console in the panel still gets you in. The documentation covers the firewall and first-login steps.
Support is available 24x7 on phone and WhatsApp at +91 75994 50220, or by email at support@bigdomainhost.com.
Sources (checked )
- IETF: RFC 9110: HTTP Semantics
- MDN Web Docs: 504 Gateway Timeout
- nginx.org: Module ngx_http_proxy_module
- nginx.org: Module ngx_http_fastcgi_module
- PHP Manual: FPM configuration
- Cloudflare Docs: Error 524