Skip to content
Servers

How to Fix 504 Gateway Timeout on Nginx

In this article9 sections

A 504 Gateway Time-out means Nginx sent the request on to the application, and the application did not answer in time. The page is not broken, and the server is not down. Something behind Nginx is slow: a database query, a slow remote API call, a large import, or a PHP process stuck on a lock. Nginx gives up after its timeout and returns the 504. This is different from the 502 error, where the application is unreachable or crashed. The 502 guide covers that case.

The right fix is almost always to find out why the request is slow, and only then to raise the timeout. A higher timeout hides the problem and ties up workers for longer, so this guide puts the diagnosis first.

Step 1: Confirm the error and find the upstream

Look at the Nginx error log at the time of the failure. A timeout to the application appears with a message like this:

sudo grep -i "timed out" /var/log/nginx/error.log | tail -20

The line tells you which part timed out. The two common forms are:

  • A reverse proxy (for example Node.js, n8n, or any app on a port): upstream timed out (110: Connection timed out) while reading response header from upstream, with proxy in the request line.
  • PHP-FPM: the same message, with fastcgi in the request line, or a FastCGI socket path in the message.

The upstream that timed out decides which timeout to look at in Step 4. Note the exact URL in the log line too, because it tells you which page is slow.

Step 2: Reproduce the slow request and time it

Time the same request from the server, so the network is out of the picture:

curl -o /dev/null -s -w "status %{http_code}  total %{time_total}s  ttfb %{time_starttransfer}s\n" https://example.com/slow-page/

Run it while the error log is open in another window. A slow request that returns 200 after 45 seconds is a performance problem. A request that never returns is more likely a stuck process or a deadlock. Note the time to first byte as well: a long wait before the first byte points at the application, and a long download after it points at a large response.

Step 3: Find out what the application is doing

For a PHP application, enable PHP-FPM’s slow log for the pool, so it records a stack trace for any request that runs longer than a set time. In the pool file, usually /etc/php/8.4/fpm/pool.d/www.conf, set:

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

Reload PHP-FPM, reproduce the slow request, and read the trace:

sudo systemctl reload php8.4-fpm
sudo tail -50 /var/log/php8.4-fpm-slow.log

The trace names the function that was running. A database call, a remote HTTP request, or a file operation at the top of the trace tells you where to look. Turn the slow log off once you have the answer, because it adds a small overhead.

For a Node.js, Python or other app server, check its own log for the request, and check whether the application is waiting on a database, an external API, or a queue.

Step 4: Fix the cause first

Most 504 errors have one of these causes. Fix the one that matches your trace:

  • A slow database query. Check the slow query log for the same time window, and add an index or fix the query. The database steps are in how to optimise MariaDB on a low-RAM WordPress server.
  • A slow remote API. Set a timeout on the outgoing call inside the application, so one slow service cannot hold a worker. Most HTTP client libraries accept a timeout setting.
  • A large job run in a web request. Imports, exports, image batches and reports belong in a background job or the cron queue, not in a page load. Move them there, and return a message to the user straight away.
  • Too few PHP workers. If every worker is busy, new requests wait in the queue until they time out. The sizing method is in how to size PHP-FPM workers for limited RAM.
  • A plugin or theme doing heavy work on every page. Disable plugins one at a time to find it, then replace or remove it.

Step 5: Raise the timeout only where it is needed

When the request is legitimately long, for example a report that takes a minute, raise the timeout for that path only. Nginx’s defaults are 60 seconds for the connection, sending and reading, and they apply to every location unless you change them:

location /reports/ {
    proxy_read_timeout 180s;
    proxy_send_timeout 180s;
    proxy_pass http://127.0.0.1:3000;
}

location ~ \.php$ {
    include snippets/fastcgi-php.conf;
    fastcgi_pass unix:/run/php/php8.4-fpm.sock;
    fastcgi_read_timeout 180s;
}

Keep the global value at its default, and raise it only for the locations that need it. A site-wide value of 300 seconds means one stuck request can hold a connection open for five minutes, and that uses up the connection limit under load.

For PHP itself, the limit on script run time is max_execution_time (30 seconds by default for web requests). PHP-FPM can also stop a request with request_terminate_timeout, which is off by default. If you raise one timeout, check the others, because the shortest one wins.

Test the configuration, then reload:

sudo nginx -t && sudo systemctl reload nginx

Step 6: Check whether a proxy adds its own limit

If the site is behind a CDN or a load balancer, that layer has its own timeout. A request that takes longer than the proxy allows gets an error from the proxy, not from Nginx. Cloudflare, for example, has a fixed connection limit on standard plans and returns its own 524 error when the origin is too slow. Raising the timeout on your server does not change that limit, so the slow request must also become faster, or be moved to a background job.

Step 7: Test the change

Reproduce the request that failed, and time it. Check three things:

  • The request returns 200 within the new limit, not just within the old one.
  • The error log has no new timed out lines for normal pages.
  • Other pages still respond quickly, because the raised timeout applies only to the path you changed.
sudo tail -f /var/log/nginx/error.log

Common mistakes

  • Raising every timeout to the maximum. It hides the slow request and lets stuck requests pile up.
  • Raising the Nginx timeout and forgetting PHP’s. The shorter limit still ends the request.
  • Treating a 504 as a 502. A 502 points at the connection to the application. Check the log message first.
  • Testing only from the browser. Use curl on the server, to separate the network from the application.
  • Leaving the slow log on. It is a diagnostic tool, not a permanent setting.

Frequently asked questions

Is a 504 caused by the server being too small?

Sometimes, but usually a single slow piece of code or query is the cause. Check the trace in Step 3 before you resize the server.

Why does the page work for me but fail for visitors?

Often the page is cached for you, or your request is the only one running. Under load, the same request waits in the worker queue, and the timeout is reached. The load and worker checks in Step 4 show this.

Does the error appear in the PHP log?

Not usually. Nginx generates the 504, so check the Nginx error log first, and use the PHP-FPM slow log for the cause.

Can I set the timeout in the .htaccess file?

Not on Nginx. Nginx does not read .htaccess. Set the value in the server block or the location block.

A 504 means something behind Nginx took too long to answer. Find the request in the log, time it from the server, read the slow trace, and fix the slow part. Raise the timeout only for the paths that legitimately take longer, and check the other timeouts in the chain.

Share this guide