Skip to content
ohsudo, Home
Networking

Nginx "504 Gateway Time-out": find out what's slow before raising the timeout

Fix Nginx "504 Gateway Time-out": read the error log, measure the backend, set proxy_read_timeout or fastcgi_read_timeout, and check Cloudflare and load balancers.

Intermediate5 min readApplies to Nginx 1.18+ · PHP-FPM · Linux · Cloudflare · AWS ALB

by OhSudo ··

Checked on real software: Nginx · November 2026How we check

Not sure this is your cause?Find it step by step: Website down →

Part of a learning path:Web servers: nginx and HTTPS in production

504 Gateway Time-out
nginx/1.24.0

Nginx passed the request on to your application, waited, and gave up. By default that means 60 seconds passed without an answer. Nginx itself is healthy, and it’s telling you that whatever sits behind it is slow, stuck or unreachable.

Raising the timeout does stop the error, but it may also only hide the problem: visitors then wait longer for the same failure. Find out what’s slow first.

  1. Browser
  2. nginxwaits for the answer(works)
  3. Your appno answer within the timeout(breaks here)
nginx passed the request on, waited 60 seconds by default, and gave up. nginx is healthy; what's behind it is slow, stuck or unreachable.

TL;DR

Terminal window
sudo tail -n 20 /var/log/nginx/error.log | grep -i 'timed out'
curl -o /dev/null -s -w 'status %{http_code}, %{time_total}s\n' http://127.0.0.1:3000/slow-page

If the backend is really slow and it’s meant to be, raise the limit for that one path:

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

1. Read the error log

Terminal window
sudo grep 'timed out' /var/log/nginx/error.log | tail -n 5
upstream timed out (110: Connection timed out) while reading response header from upstream, client: 203.0.113.7, server: example.com, request: "GET /report HTTP/1.1", upstream: "http://127.0.0.1:3000/report"

The words after while say which limit expired:

The log says Waiting for Setting
while connecting to upstream A connection to the backend proxy_connect_timeout
while reading response header from upstream The first byte of the answer. The usual one proxy_read_timeout
while sending request to upstream The backend to accept the request proxy_send_timeout

No such line in Nginx’s log at all? Then this Nginx didn’t produce the 504. Look at what’s in front of it (section 5).

2. Connecting: the backend can’t be reached

A timeout while connecting means the packets get no answer. The application might be on another machine, with a firewall dropping them, or the address might be wrong. Test from the Nginx server:

Terminal window
nc -vz -w 5 10.0.0.20 3000

A closed port refuses at once and gives a 502 Bad Gateway instead, so a timeout points to a firewall or a dead address. “Connection timed out” vs “Connection refused” explains the difference. Don’t wait 60 seconds for this one. A short limit, such as proxy_connect_timeout 5s;, is usually better.

3. Reading: measure the backend

Go around Nginx, on the Nginx server, and time the same request:

Terminal window
curl -o /dev/null -s -w 'status %{http_code}, first byte %{time_starttransfer}s, total %{time_total}s\n' \
http://127.0.0.1:3000/report

A slow answer here means the problem is in the application, and that’s where to look:

  • The database. A slow query is the top cause. Look at the slow query log or pg_stat_activity. A lock, or a pool with no free connections, makes requests wait too, as in too many clients.
  • An outside API with no timeout. One slow service makes every request hang.
  • All the workers are busy. Fast pages time out too, because they’re queuing behind slow ones. Under load, this looks like the whole site being down.
  • Not enough resources. Check top, free -m and df -h. Swapping makes everything crawl, and a full disk can make writes hang.

If the same curl is fast, the problem is between the two: a wrong proxy_pass address, or DNS. Nginx resolves a name in proxy_pass only at startup, so a backend that has changed address needs a reload.

4. PHP: fastcgi_read_timeout

PHP through FastCGI uses its own setting, not the proxy_ ones:

location ~ \.php$ {
include fastcgi_params;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_read_timeout 300s;
}

PHP also has its own limits, so raise them together, or PHP stops first:

; php.ini
max_execution_time = 300
; the pool, for example /etc/php/8.3/fpm/pool.d/www.conf
request_terminate_timeout = 300

Then reload both:

Terminal window
sudo nginx -t && sudo systemctl reload nginx
sudo systemctl reload php8.3-fpm

If it fails under load only, see whether the pool is exhausted. A log line such as server reached pm.max_children setting in the PHP-FPM log says so.

5. The 504 comes from further out

There’s often more than one proxy, and each has its own limit. The shortest one wins, so raising Nginx’s does nothing if another is smaller:

In front of Nginx Limit Looks like
Cloudflare About 100 seconds, on most plans 524 with Cloudflare’s own page
AWS Application Load Balancer Idle timeout, 60 seconds by default 504 from awselb/2.0
Kubernetes Ingress NGINX 60 seconds by default Set with the proxy-read-timeout annotation

To see who answered, check the Server header:

Terminal window
curl -sI https://example.com/report | grep -i '^server'

6. Raising it the right way

Raise it where it’s needed, not everywhere. A long global timeout lets slow requests tie up your workers across the site:

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

The timeout is the wait between two reads, not for the whole request, so an answer that keeps sending data doesn’t hit it. WebSockets and long-lived streams need a long one too. The Nginx Config Generator sets this for WebSocket proxies.

Then, for work that really takes minutes, consider not doing it during the request at all. Accept the job, answer 202 straight away, run it in the background, and let the client check for the result. No proxy can time that out, and a retry doesn’t start the work twice.

Warning

Raising a timeout doesn’t stop anything. When the proxy gives up, the backend usually keeps working, and a visitor who reloads starts the same heavy work again. That’s how a slow page turns into an outage, so limit what a slow request can cost as well.

Shell, we have a problem!

Similar, but different: a 502 Bad Gateway means the backend refused or broke the connection, instead of staying silent. A 503 means there’s no backend available, or a rate limit. A 413 is about upload size. And a request that fails with ERR_TOO_MANY_REDIRECTS is a redirect loop.

Found this useful? Share it.

Found a mistake? Let us know at contact@ohsudo.com.

Tools

Try it yourself

Articles

Related articles