A 504 means your server didn't crash - it just didn't answer fast enough. Somewhere between the visitor's browser and your app, one layer got tired of waiting for another and gave up. The fix isn't "restart everything," it's finding which layer quit first.
What 504 actually means
Every 500-series error is your server telling on itself, and 504 has a very specific story: a proxy or gateway (Nginx, Apache acting as a proxy, Cloudflare, a load balancer) sent a request upstream to the real backend - PHP-FPM, a Node process, MySQL through PHP - and got no response within its timeout window. It's not that the backend refused; it's that it never replied in time. That distinction matters, because it means the fix is almost always about timeouts and slow processes, not broken code.
Contrast this with its cousins: a 502 means the backend replied with garbage or closed the connection outright, a 503 means the server is overloaded and refusing new work, and a 504 means the backend just went quiet. Same family, different diagnosis.
Find out which layer is actually timing out
A request to a typical Getwebup or cPanel-hosted site can pass through several hops, and each one has its own timeout clock:
| Layer | Typical default timeout | Where to check |
|---|---|---|
| Cloudflare (proxied/orange-cloud) | 100 seconds (fixed on Free/Pro) | Cloudflare dashboard → shows a 524, not 504, when it's the one that gave up |
| Nginx (reverse proxy or in front of Apache) | 60 seconds | /var/log/nginx/error.log - look for "upstream timed out" |
| Apache (as the app-facing server) | 300 seconds | /var/log/apache2/error.log or cPanel's error log viewer |
| LiteSpeed / OpenLiteSpeed | 300 seconds (Connection Timeout) | WHM → LiteSpeed Web Admin, or the site's error log |
| PHP-FPM | Governed by max_execution_time | php.ini or MultiPHP INI Editor in cPanel |
Start at the outside and work in. If you're behind Cloudflare, the error page itself tells you something: a genuine 524 (not 504) means Cloudflare's own clock ran out waiting on your origin - the problem is downstream, on your server. If Cloudflare is just passing along a 504 it received from your origin, the fault is entirely inside your hosting.
Common causes, and the fix for each
1. A single request is doing too much work
The most frequent cause on WordPress and PHP sites: a plugin import, a large database migration, a report generator, or a poorly written API call is taking longer than any of the timeouts above allow. Check your slow query log or add simple timing to the suspect script. If a specific admin action (bulk import, backup trigger, theme install) reliably 504s, that's your culprit - not the whole site.
Fix: move it off the request/response cycle. Run it as a cron job or WP-Cron background task instead of making the visitor's browser wait on it. If it must be synchronous, raise the specific timeout for that one endpoint rather than globally.
2. Nginx's proxy timeout is too short for your backend
If you're running Nginx in front of Apache, LiteSpeed, or a custom app (Node, Python, Docker), Nginx's own proxy timeouts default to 60 seconds - shorter than Apache's or PHP-FPM's. That mismatch means Nginx gives up before the backend would have finished.
Fix it in the relevant server or location block:
location / {
proxy_pass http://127.0.0.1:8080;
proxy_connect_timeout 90s;
proxy_send_timeout 90s;
proxy_read_timeout 90s;
}Reload after editing: sudo nginx -t && sudo systemctl reload nginx. Set this a little above your backend's own timeout, not wildly beyond it - a huge value just means visitors stare at a spinner for minutes before eventually getting an error anyway.
3. PHP is hitting max_execution_time
If PHP-FPM kills the script before Nginx or Apache even times out, you'll see it in the PHP error log as "Maximum execution time of 30 seconds exceeded." In cPanel, raise it per-domain via MultiPHP INI Editor:
max_execution_time = 120
max_input_time = 120On a VPS without cPanel, edit the pool's php.ini or the FPM pool config directly, then restart:
sudo systemctl restart php8.2-fpmAgain - this buys time, it doesn't fix a query that shouldn't take 90 seconds in the first place.
4. The database or an external API is the actual bottleneck
A lot of 504s trace back to a slow MySQL query (missing index, a huge JOIN, table locking) or an external service your site calls out to - a payment gateway, a shipping API, a third-party font or ad script your server fetches server-side. If the slow part is external, add a short timeout in your own code so it fails fast instead of hanging until the gateway kills it for you.
5. Server resources are maxed out
On shared cPanel hosting under CloudLinux, if you're near your LVE limits, PHP processes queue up waiting for CPU or entry processes, and requests time out before they're even properly served. This usually shows as intermittent 504s under load rather than every single time. Check WHM → Resource Usage or run top/htop on a VPS during the slow window to confirm.
Prevention checklist
- Line up timeouts so they increase as you go inward: Cloudflare/CDN ≤ Nginx ≤ Apache/LiteSpeed ≤ PHP-FPM. A shorter outer timeout than inner one guarantees premature errors.
- Move anything that takes more than a few seconds - imports, exports, bulk emails, report generation - to a background queue or cron job.
- Keep an eye on the MySQL slow query log and add indexes before a table grows large enough to make every query borderline.
- Set explicit timeouts on any outbound API calls your server makes, so a flaky third party can't hang your whole request.
- Enable page and object caching so most requests never reach PHP or the database at all.
- If you're on shared hosting and hitting this under load, that's usually a sign it's time to move to a VPS where you control the resource ceiling directly.
When it's not really a 504
Two look-alikes worth ruling out first. A 508 Resource Limit Reached on cPanel/CloudLinux is a hard resource cap, not a timeout - different fix, different log. And a request that just spins forever with no error at all is often a redirect loop or a stuck WP-Cron job, not a gateway timeout - check for that before you start tuning timeout values.