504 Gateway Timeout
PHP was still working when the web server gave up waiting. Nothing crashed - it simply took longer than the timeout allows.
504 Gateway Timeout
Something in the request is slow, and raising the timeout only means waiting longer for the same page. Identify the slow step first: a query, an outbound API call, or an import running inside a web request.
What causes it, most likely first.
Work down the list. Each check is written to rule its row in or out before you change anything, because the fixes below it are different and a guess costs more than a command.
| Cause | How to check | Fix |
|---|---|---|
| An outbound HTTP request with no timeout | The slow URL calls a third party - a licence check, a feed, a payment gateway. It is fast when that service is fast. | Set an explicit timeout on the outbound call and cache the response. WordPress waits far longer by default than any visitor will. |
| A query with no index behind it | The slow page is a filtered archive or a report, and the database shows the same query at the top under load. | Add the index the query needs. A scan that was fine at ten thousand rows is a timeout at a million. |
| An import or export running in a web request | The timeout only happens on an admin action you started, and it fails at roughly the same elapsed time each attempt. | Move the job to WP-CLI or a chunked background run. A browser tab is the wrong place for work measured in minutes. |
| Cron piling up on page loads | The delay lands on ordinary front-end URLs, sporadically, on a site with a long overdue-events list. | Disable WP-Cron and drive it from the system scheduler, so scheduled work stops being charged to whichever visitor arrives first. |
| The timeout is genuinely too short for the work | Everything above is ruled out and the operation is legitimately long - a large backup, a big media conversion. | Raise the timeout for that path only. Raising it globally removes the ceiling that stops one bad request occupying a worker. |
Fixing it, step by step.
Commands assume WP-CLI and shell access. Where you have neither, each step says what it is doing so it can be done from the dashboard or over SFTP instead.
Find out which URL is slow
The access log records how long each request took. Sort by that and the answer is usually one line.
awk '{print $NF, $7}' /var/log/nginx/access.log | sort -rn | head -n 10Ask the database what it is spending time on
A long-running query shows up here while the page is still loading.
mysql -e 'SHOW FULL PROCESSLIST;' | head -n 20
Check whether cron is the passenger
A long overdue list means scheduled work is being run by visitors.
wp cron event list --status=due --fields=hook,next_run_relative
Move the long job out of the request
The same import that times out in a browser usually completes from the command line, because nothing is waiting on it.
wp import large-file.xml --authors=create
Only now, consider the timeout
And raise it for the path that needs it rather than everywhere.
wp config set WP_MAX_MEMORY_LIMIT '512M'
What the platform takes off the list.
Scheduled work runs on a real system scheduler rather than on whichever visitor happens to arrive, so the most common accidental cause of a slow page is absent from the start. Long jobs have their own limits in the cron context rather than borrowing the ones meant for web requests, and per-site containers mean a request that does run long occupies your workers only.
Questions, answered.
Should I just increase the timeout?
Why does the same page load fine sometimes?
My import times out at exactly the same point every time.
Is a 504 caused by my visitors' connection?
If that was not it.
These fail in ways that look similar from the browser.