Fixing 504 Gateway Timeout Errors
A 504 Gateway Timeout means a server in the middle, such as nginx, a load balancer or a CDN, passed the request on to your application and then gave up waiting for the answer. The application may still be working away on it; nobody is left to receive the result.
Visitors see an error after a long pause, and crawlers record a failed fetch. This guide shows how to tell which timeout fired, how to find the slow step behind it, and how to fix the slowness rather than simply making everyone wait longer.
A 504 is a chain of stopwatches
Between a visitor and your code there are usually several programs, and each one starts its own stopwatch when it hands the request inward. The first stopwatch to run out produces the error. That is why the same slow page can show a 504 on one day, a CDN error on another, and simply load very slowly on a third.
| Layer | Setting that limits the wait | Common default | What the visitor sees |
|---|---|---|---|
| Cloudflare (proxied) | Origin response timeout | 100 seconds on most plans | Cloudflare error 524, not 504 |
| Cloud load balancer | Idle timeout | Often 60 seconds, but check your provider | 504 from the load balancer |
| nginx proxying to an app | proxy_read_timeout | 60 seconds | 504 from nginx |
| nginx passing to PHP-FPM | fastcgi_read_timeout | 60 seconds | 504 from nginx |
| Apache with mod_proxy | ProxyTimeout, falling back to Timeout | 60 seconds | 504 from Apache |
| PHP-FPM pool | request_terminate_timeout | 0, meaning no limit | Usually a 502, because the worker is killed mid-request |
| PHP itself | max_execution_time | 30 seconds for web requests | A 500 or a PHP fatal error |
Defaults change between versions and hosts often override them, so treat the table as a guide and check the real values on your server. The pattern matters more than the numbers: if the outer layer's limit is shorter than the inner layer's, the outer one fires first and you get a 504 with nothing useful in your application log.
max_execution_time on Linux counts time spent running PHP code,
not time spent waiting on the database, an external API or a file download. A script waiting 90 seconds
for a slow query will not hit a 30-second PHP limit, but nginx will give up at 60 seconds and return
a 504.Working out which timeout fired
Time the request first. The number of seconds before the error usually identifies the layer straight away.
curl -s -o /dev/null -w "status %{http_code} total %{time_total}s first byte %{time_starttransfer}s\n" \
"https://www.example.co.uk/reports/annual-summary/?year=2025"
- Almost exactly 60 seconds and a plain nginx or Apache error page: a proxy or FastCGI read timeout on your own server.
- Around 100 seconds and a Cloudflare page with code 524: the origin did not answer Cloudflare in time. Your own server may still be working on the request.
- A round number with a cloud provider's error page: the load balancer's idle timeout.
- An error after a few seconds: probably not a real timeout. A 504 that arrives quickly can mean the proxy cannot reach the upstream at all, which the 502 guide covers in more detail.
Then confirm it in the web server error log. nginx writes a distinctive line for this case:
upstream timed out (110: Connection timed out) while reading response header from upstream,
client: 198.51.100.24, server: www.example.co.uk, request: "GET /reports/annual-summary/?year=2025 HTTP/2.0",
upstream: "fastcgi://unix:/run/php/php8.3-fpm-example.sock"
"While reading response header" means the connection worked and the application was simply too slow. "While connecting to upstream" means nginx could not even open the connection in time, which points at an overloaded or unreachable back end instead.
One URL or all of them?
If one report page, search or export fails, the slowness is in that page's own work. If every page times out at once, something shared is stuck: the database, a full pool of workers, a lock on a table, or a service every page calls, such as a session store. When all PHP-FPM workers are busy on slow requests, even fast pages wait in the queue behind them and time out too, so one slow page can take down the whole site.
Catching the slow step in the act
Once you know the request is too slow, you need to know where the time goes. Three tools answer that without guesswork.
The PHP-FPM slow log
PHP-FPM can write a stack trace of any request that runs longer than a threshold. The trace shows the function it was in at that moment, which usually points straight at a database call, an HTTP request or a loop.
; in the site's PHP-FPM pool file
request_slowlog_timeout = 10s
slowlog = /var/log/php-fpm/example-slow.log
Reload PHP-FPM, trigger the slow page, and read the file. A trace ending in curl_exec()
means an outgoing web request; one ending in a database driver function such as mysqli_query()
or PDOStatement->execute() means a query.
The database slow query log
-- MySQL or MariaDB, until the next restart
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 2;
SHOW VARIABLES LIKE 'slow_query_log_file';
Run EXPLAIN on any query that appears. A type of ALL on a large table means
every row is scanned, and the usual fix is an index on the columns in the WHERE and ORDER BY
clauses. For PostgreSQL, the equivalent setting is log_min_duration_statement.
Application monitoring
An application performance monitoring tool, or a framework debug bar on a staging copy, breaks a request into database, cache, external call and code time. If you have one, it is the fastest way to see the slow step for a specific URL.
The fix, step by step
Step 1
Measure how long the failing request takes
Take a URL the HTTP Error Audit flagged with a 504 and time it with curl -w,
printing the status code, total time and time to first byte. Try it several times, and at a quieter hour.
Note the exact number of seconds before the error, because a result close to 60 or 100 seconds points
to a particular layer, and note whether other pages are slow at the same moment.
Step 2
Identify which timeout fired
Look at who sent the error page and search the web server error log for the request. upstream timed out while reading response header means the application was too slow; while connecting to upstream means it could not be reached. A Cloudflare 524 means the origin missed Cloudflare's limit. List every timeout between the visitor and your code, with its current value, before changing any of them.
Step 3
Record what the slow request is doing
Switch on the PHP-FPM slow log with request_slowlog_timeout set to around ten
seconds, and the database slow query log with long_query_time set to a couple of seconds.
Trigger the failing page, then read both logs. The stack trace shows whether the time goes on a query,
an outgoing HTTP call, file work or a loop, and the query log names the statement involved.
Step 4
Fix slow queries and external calls
Run
EXPLAIN on slow queries and add indexes for the columns used to filter and sort, or rewrite
queries that load whole tables. Cache results that do not change on every request. For calls to other
services, set a short connect and response timeout of a few seconds on the outgoing request, and show
a fallback if the service does not answer, so one slow partner cannot hang your page.
Step 5
Move long jobs out of the request
Imports, exports, large reports, bulk emails and image processing should not run while a visitor waits. Put the job on a queue or run it from a scheduled task, return a page straight away saying the work has started, and let the visitor download the result or receive an email when it finishes. The web request then takes a second or two, whatever the size of the job.
Step 6
Line up the timeouts from inside out
Once the work itself is fast, set the timeouts so each inner layer gives up before the layer
outside it. For example, the outgoing API call gives up first, then PHP-FPM with request_terminate_timeout,
then nginx with fastcgi_read_timeout, then the load balancer or CDN. Raise an outer limit
only for the specific admin or export path that genuinely needs it, not site-wide.
Step 7
Retest under realistic load
Request
the fixed page several times with curl and confirm a 200 well inside the limits, then re-run
the HTTP Error Audit so pages are fetched in quick succession, much as a crawler does. Check the slow
logs again afterwards to make sure nothing new appears. Turn the database slow query log threshold back
up, or off, if it is producing more output than you need.
When raising the timeout is the right answer
Increasing fastcgi_read_timeout or proxy_read_timeout is the most common
advice for a 504, and it is sometimes correct. It is right when a small number of known, legitimate operations
need more time and only trusted users trigger them, such as an administrator running a report. It is
wrong as a general fix, because:
- Visitors still wait. A page that takes 90 seconds instead of failing at 60 is not a working page for anyone browsing.
- Slow requests hold workers. Every request that runs for a minute occupies a PHP-FPM worker or Node connection for that minute, so fewer are left for everyone else.
- The CDN will not wait. Raising your own limit above the CDN's timeout just moves the error to the CDN.
If you do raise it, scope it to the path that needs it:
# nginx: longer limit only for the admin export area
location ^~ /admin/exports/ {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root/index.php;
fastcgi_pass unix:/run/php/php8.3-fpm-example.sock;
fastcgi_read_timeout 180s;
}
Remember to raise the matching PHP-FPM request_terminate_timeout for that pool if it
is lower, or the worker will be stopped anyway.
Patterns that point to the cause
| What you see | Likely cause |
|---|---|
| Only search results or filtered listings time out | Queries with LIKE '%term%'
or many filters on unindexed columns |
| Checkout or contact form times out | A payment, email or CRM call with no timeout set on the outgoing request |
| Everything times out at the same time each night | A backup, import or scheduled task locking tables or using all the disk bandwidth |
| Timeouts start after the database grew | A query that was fine on a small table and now scans millions of rows |
| Timeouts only during a crawl or traffic spike | Workers all busy on slow pages, so fast pages queue behind them |
| Admin pages time out, public pages are fine | Heavy dashboard widgets, update checks or uncached reports |
Pages that can be cached for anonymous visitors, such as product listings and articles, should be. A full-page cache in front of the application means most requests never reach the slow code at all, which protects the site even while you work on the underlying query.
What 504s mean for search engines
Search engines treat a 504 like other server errors: the page is not fetched, and the crawler tries again later. Repeated timeouts cause Google to slow down its crawling of the site, and slow responses in general reduce how many pages it will request in a session. The Crawl Stats report in Search Console shows both the average response time and the share of requests that ended in server errors, which is a useful before-and-after check once you have made fixes. Slow pages also hurt visitors long before they reach a timeout, so fixing the slow step pays off even for pages that never actually fail.
If your pages respond but only after a long delay, or the connection never gets an answer at all, the request timeout guide covers slow first bytes and network-level causes.
Keeping timeouts away
- Leave the slow logs on. A PHP-FPM slow log at a generous threshold and a database slow query log cost little and warn you before slow becomes a timeout.
- Set timeouts on every outgoing call. Many HTTP client libraries wait a very long time by default. Give each external call an explicit short limit.
- Design big tasks as background work. Anything that grows with the size of your data belongs in a queue, not in a page request.
- Check query plans when data grows. Review slow queries after large imports or when a table grows significantly.
- Schedule heavy jobs for quiet hours. Keep backups and imports away from busy periods and from times when crawlers are most active on your site.
- Audit regularly. The HTTP Error Audit records response times as well as status codes, so pages creeping towards a limit are visible before they fail.
1. What does 504 Gateway Timeout mean?
It means a server acting as a go-between, such as nginx, a load balancer or a CDN, forwarded the request to the application behind it and gave up waiting for an answer. The application may still be running the request. The problem is almost always that the page does too much work, waits on a slow database query, or waits on another service, so the fix is to find and speed up that slow step rather than restarting the web server.
2. What is the difference between a 502 and a 504 error?
Both come from a proxy in front of your application. A 502 means the proxy got a broken answer or could not talk to the application at all, often because it crashed or is not running. A 504 means the connection worked, but the application took longer than the proxy was willing to wait. A 502 usually points to a process problem, while a 504 usually points to a slow query, a slow external call or a long-running task.
3. Should I just increase fastcgi_read_timeout or proxy_read_timeout?
Only for specific paths that genuinely need longer, such as an administrator export, and only after checking that the work cannot be made faster or moved to a background job. Raising the limit site-wide means visitors wait longer for pages that are still slow, ties up workers so other pages suffer, and often just moves the error to the CDN or load balancer, whose limit you may not be able to change on your plan.
4. Why do I get a Cloudflare 524 error instead of a 504?
Cloudflare uses 524 when it connected to your server successfully but your server did not send a response within its time limit, which is 100 seconds on most plans. It is the Cloudflare equivalent of a gateway timeout. Your server may still finish the work after Cloudflare gives up. Treat it like a 504: find the slow step on the origin, and move long operations into background jobs rather than expecting the connection to stay open.
5. Why does one slow page cause 504 errors across my whole site?
Your server only has a limited number of workers to handle requests at the same time. If one page takes a minute and several visitors or a crawler request it, those workers stay busy for that whole minute. New requests, even for fast pages, then wait in a queue behind them and run out of time too. Fixing or caching the single slow page, or limiting how often it can run, often clears the site-wide timeouts.
6. How do I find which database query is causing a 504?
Switch on the slow query log in MySQL or MariaDB with a threshold of a couple of seconds, or log_min_duration_statement in PostgreSQL, then trigger the failing page and read the log. The PHP-FPM slow log is also useful, because its stack trace shows which function was running when the request passed its threshold. Once you have the query, run EXPLAIN on it to see whether it scans the whole table and needs an index.
7. Do 504 errors affect my search rankings?
A page that times out cannot be fetched, so it cannot be indexed while it fails. Occasional timeouts are retried by crawlers and rarely matter on their own. Frequent timeouts cause Google to reduce its crawl rate for the site, which slows the discovery of new and updated pages, and pages that fail for a long time can drop out of the index. Slow responses that stop short of a timeout also make crawling less efficient and frustrate visitors.
The HTTP Error Audit requests every URL in your crawl scope, records the status code and response time, follows redirects and flags anything outside the 200 range, so gateway timeouts and slow pages show up together.
Open HTTP Error Fixes โ