Nginx 504 Gateway Timeout: Causes and How to Fix It

A 504 Gateway Timeout means Nginx gave up after waiting 60 seconds for your app. Find the slow step, often a database query, before raising the timeout.
A 504 is the quieter cousin of the 502. With a 502 the app is down. With a 504 the app is up and working, just too slowly: Nginx forwards the request, waits for proxy_read_timeout (60 seconds by default), and sends the visitor an error. The tempting fix is to set the timeout to 600 seconds. That hides the problem and lets slow requests pile up until the whole server stalls. This is the routine I use on my own stack, Nginx in front of a Next.js app and a NestJS API on PostgreSQL, to find the slow part and fix it properly.
Key takeaways
- 504 means "too slow", not "down". The log line
upstream timed out (110: Connection timed out) while reading response header from upstreamconfirms it. - Nginx's proxy timeouts all default to 60 seconds. The app took longer than that to send its first response bytes.
- The usual culprits: a slow or locked database query, a slow external API with no timeout, an overloaded server, or a long job (exports, imports, image processing) done inside the request.
- Raise the timeout only for routes that really need it, and move long work to a background job.
- Behind Cloudflare you'll see a 524 instead, after 125 seconds, which you can't raise on non-Enterprise plans.
Step 1: Confirm it's a timeout and find the URL
sudo grep 'timed out' /var/log/nginx/error.log | tail -n 20
Each line shows the request (request: "GET /api/v1/reports HTTP/1.1") and the upstream Nginx was waiting on. Note which URLs appear. One or two routes means a slow endpoint. Every route at once means the app or server is overloaded.
Two variants mean different things:
- "while reading response header from upstream": the app accepted the request but didn't answer in time. This is the common case.
- "while connecting to upstream": Nginx couldn't even open a connection in time. The app's accept queue is full, or a firewall is silently dropping packets to the upstream host.
Then time the slow URL directly against the app, skipping Nginx:
curl -s -o /dev/null -w 'status %{http_code} total %{time_total}s\n' \
http://127.0.0.1:5000/api/v1/reports
If it takes over 60 seconds here too, the problem is in the app. If it's fast here but slow through Nginx, check the proxy_pass address and DNS resolution in the Nginx config.
Step 2: Find what the app is waiting on
Slow or blocked database queries
This is the cause I find most often. On PostgreSQL, look at what's running right now and for how long:
sudo -u postgres psql -c " SELECT pid, now() - query_start AS runtime, state, wait_event_type, left(query, 80) FROM pg_stat_activity WHERE state <> 'idle' ORDER BY runtime DESC LIMIT 10;"
A query running for minutes usually needs an index: run it with EXPLAIN ANALYZE and look for a sequential scan on a big table. A wait_event_type of Lock means it's blocked behind another transaction, often one left "idle in transaction" by a bug. If the app can't get a connection at all, see my Postgres "too many clients" guide. On MySQL, SHOW FULL PROCESSLIST; gives the same picture.
External APIs with no timeout
A payment gateway, email provider or AI API that hangs will hold your request open until Nginx gives up. Node's fetch has no short default timeout, so set one on every outbound call:
const res = await fetch(url, { signal: AbortSignal.timeout(10_000) });
Then return a clear error or a cached result instead of hanging.
An overloaded server
If every route is slow, check the machine itself:
uptime # load average vs CPU count (nproc) free -h # swapping heavily makes everything slow top -o %CPU # what is eating the CPU pm2 monit # per-process CPU and memory for Node apps
A load average far above your CPU count, or a server deep in swap, means you need to fix the hungry process or add capacity. My OOM killer guide covers memory pressure.
Long jobs inside the request
CSV exports, bulk imports, video or image processing and report generation shouldn't run while a browser waits. Start the job, return 202 Accepted with a job ID, and let the frontend poll or get notified when it's done.
Step 3: Raise timeouts only where it makes sense
Some routes are legitimately slow, like an admin export. Raise the limit for that location only, not the whole site:
location /api/v1/admin/export {
proxy_pass http://127.0.0.1:5000;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
}
proxy_read_timeout is the time between two reads from the app, not the whole response, so a streaming response that keeps sending data won't time out. Leave proxy_connect_timeout alone: per the Nginx docs it usually can't exceed 75 seconds anyway, and a slow connect means a different problem. Test and reload with sudo nginx -t && sudo systemctl reload nginx.
For PHP sites the equivalent is fastcgi_read_timeout in Nginx, and PHP has its own limits too: max_execution_time in php.ini and request_terminate_timeout in the PHP-FPM pool. All of them have to allow the longer time.
504 behind Cloudflare, a load balancer or Docker
- Cloudflare shows error 524 when your origin takes longer than its 125-second proxy read timeout. Only Enterprise plans can raise it, so long requests must become background jobs.
- Cloud load balancers (AWS ALB, DigitalOcean, Hetzner) have their own idle timeout in front of Nginx. The shortest timeout in the chain wins.
- Docker: if Nginx proxies to a container by IP and the container was recreated with a new IP, connects can hang. Proxy to a published port on
127.0.0.1or a Compose service name.
How to keep 504s from coming back
- Log slow requests: add
$request_timeand$upstream_response_timeto your Nginxlog_formatso you see slow routes before they hit 60 seconds. - Turn on slow query logging, for example
log_min_duration_statement = 1000in PostgreSQL to log queries over one second. - Set a
statement_timeoutfor your app's database user so one bad query can't hold a connection forever. - Put timeouts on every outbound HTTP call.
- Watch it from outside with an uptime check that alerts on slow responses, not just errors.
If your site is fully down rather than slow, start with my website down checklist or the Nginx 502 guide.
Frequently asked questions
What is the difference between 502 and 504?
A 502 Bad Gateway means the app refused the connection, crashed or sent an invalid response. A 504 Gateway Timeout means the app accepted the request but didn't respond within Nginx's timeout, 60 seconds by default.
How do I increase the timeout in Nginx?
Set proxy_read_timeout (and usually proxy_send_timeout) in the location block that proxies to your app, for example proxy_read_timeout 300s;, then run sudo nginx -t and reload. For PHP-FPM use fastcgi_read_timeout instead.
Is increasing proxy_read_timeout a good fix?
Only for the few routes that are slow by design, like exports. For normal pages it hides a slow query or a hanging API call, and slow requests pile up and tie up workers and database connections.
Why do I get 504 only sometimes?
Intermittent 504s usually come from load: a query that's fast on a quiet server becomes slow when many requests run at once, or a background job locks a table. Correlate the times in the Nginx error log with your database and cron activity.
What does Cloudflare error 524 mean?
Cloudflare connected to your server, but your server didn't send a response within 125 seconds. It's the same problem as a 504, fixed the same way: make the request faster or turn it into a background job.
Getting 504s you can't track down?
I find slow queries, missing indexes and overloaded servers, then set up the logging and monitoring that catch them early. Book my emergency server fix, see my DevOps services, or contact me with the error log lines and the slow URL.
Written by
MD Rakibul Islam Rakib
Full-stack developer, DevOps engineer and Linux system administrator with 5+ years of production experience. I deploy, harden and fix servers and web apps for clients worldwide, and everything in this article runs on real servers I manage, including this site.
- Nginx 504 Gateway Timeout
- 504 gateway timeout fix
- proxy_read_timeout
- upstream timed out
- Cloudflare 524
- slow database query
- Node.js


