Home › Web & HTTP

504 Gateway Timeout — how to fix

504 Gateway Time-out
nginx
upstream timed out (110: Connection timed out) while reading response header from upstream

Quick fix: Check the proxy error log (sudo tail -f /var/log/nginx/error.log) for upstream timed out. Make the slow request faster or move it to a background job; if it legitimately takes long, raise proxy_read_timeout 300s; (nginx) and the app server's own timeout (e.g. gunicorn --timeout 300).

What it means

A 504 is generated by whatever sits in front of your application. The proxy connected (or tried to connect) to the upstream server and gave up waiting. Compare:

Find out which hop timed out first. With nginx the error log says it explicitly:

sudo tail -n 50 /var/log/nginx/error.log
# upstream timed out (110: Connection timed out) while reading response header from upstream,
#   upstream: "http://127.0.0.1:8000/report"

while connecting to upstream means the app is unreachable; while reading response header means it's reachable but slow.

Cause 1: the request really is slow

Large reports, exports, unindexed database queries, or calls to a slow third-party API. Time it directly against the app, bypassing the proxy:

curl -o /dev/null -s -w '%{http_code} %{time_total}s\n' http://127.0.0.1:8000/report

Fix 1: make it faster or asynchronous

Cause 2: proxy timeout lower than the job needs

nginx defaults to 60 seconds for proxy_read_timeout. If the endpoint legitimately takes longer, raise it for that location only:

location /api/export {
    proxy_pass http://127.0.0.1:8000;
    proxy_connect_timeout 10s;
    proxy_send_timeout    300s;
    proxy_read_timeout    300s;
}

For PHP-FPM use fastcgi_read_timeout 300s;; for uWSGI uwsgi_read_timeout 300s;. Then:

sudo nginx -t && sudo systemctl reload nginx

Cause 3: the app server kills or queues the request

Raising nginx's timeout doesn't help if the app server has its own, shorter limit or all workers are busy:

# gunicorn: default worker timeout is 30s
gunicorn app:app --workers 4 --timeout 300

# PHP: max_execution_time in php.ini and request_terminate_timeout in the FPM pool

If every worker is stuck on slow requests, new requests queue until the proxy gives up. Add workers, or use async workers (-k uvicorn.workers.UvicornWorker, gevent).

Cause 4: upstream unreachable (firewall, wrong address)

while connecting to upstream with a 504 usually means packets are dropped rather than rejected: a security group, firewall, or a container IP that changed. Test from the proxy host:

curl -v --max-time 5 http://10.0.1.15:8000/health

In Docker, proxy to the service name on a shared network instead of a hard-coded container IP.

Cause 5: cloud load balancer / CDN limits

Every hop in the chain needs a timeout at least as long as the one behind it.

Last updated 2026-11-04.