Resolving Nginx 504 Gateway Timeout and Client Timeout Issues
Master Nginx timeouts, including the dreaded 504 Gateway Timeout, by learning to adjust critical proxy directives. This guide details how to increase `proxy_read_timeout`, optimize buffering, and use error logs to diagnose communication failures between Nginx and upstream servers for robust connection handling.
Resolving Nginx 504 Gateway Timeout and Client Timeout Issues
An Nginx 504 Gateway Timeout means Nginx was acting as a proxy or gateway and did not get a response from the upstream service in time. The upstream might be a Node.js app, Gunicorn, PHP-FPM, another Nginx service, an internal API, or a load balancer.
The tempting fix is to raise every timeout to five minutes and move on. Sometimes a longer timeout is the right short-term move, especially for reports, imports, or admin-only jobs. But if a normal user request needs longer than Nginx currently allows, you should also ask why the backend is slow, whether the request should be asynchronous, and whether another proxy in the path has a shorter timeout anyway.
Understanding the 504 Gateway Timeout Error
A 504 Gateway Timeout error occurs when Nginx, acting as a reverse proxy or gateway, does not receive a timely response from the upstream server it is forwarding requests to. In simple terms: Nginx asked the backend for an answer, waited for the configured amount of time, and gave up because no response arrived.
This is different from a 502 Bad Gateway, where Nginx received an invalid or prematurely closed upstream response, and from a 503 Service Unavailable, which often means a service is intentionally unavailable or overloaded. The distinction matters because a 504 points you toward waiting, timing, and upstream latency.
Key Directives Controlling Upstream Timeouts
When proxying requests, Nginx uses several critical directives, primarily located within the http, server, or location blocks, or specifically within an upstream block. Adjusting these values is the primary method for solving 504 errors.
1. proxy_connect_timeout
This sets the timeout for establishing a connection with the upstream server. If Nginx cannot connect within this period, it returns a timeout error.
Default: 60 seconds
proxy_connect_timeout 60s;
2. proxy_send_timeout
This sets the timeout for the time between two successive write operations to the upstream server. This is relevant when sending a large request body.
Default: 60 seconds
proxy_send_timeout 60s;
3. proxy_read_timeout (The Most Common Fix for 504s)
This sets the timeout for waiting for a response from the upstream server after the request headers have been sent. If the backend application takes too long to process the request and generate a response body, this is the directive that needs increasing.
Default: 60 seconds
# Example: Increasing the read timeout to 120 seconds for a slow API
proxy_read_timeout 120s;
If your application frequently exceeds the default, increase this value cautiously and keep investigating. A very high value can keep client connections open while the backend is already unhealthy.
Addressing Client-Side Timeouts
Client-side timeouts are a different failure. The browser, mobile app, load balancer, CDN, or calling service gives up before Nginx finishes the response. In that case, the user may see a browser error or a gateway error from a layer in front of Nginx, while Nginx may log a closed connection rather than a clean 504.
If you are experiencing client timeouts before Nginx logs a 504, you need to look at the connection between the client and Nginx.
1. Client-Side Keepalive
If the client closes the connection prematurely, Nginx might receive an error or the client might simply time out waiting for data.
If the client is another proxy or load balancer, check its timeout settings against Nginx and the backend. The shortest timeout in the chain usually wins. A common pattern is: CDN waits 100 seconds, load balancer waits 60 seconds, Nginx waits 180 seconds, backend takes 120 seconds. Users still fail at 60 seconds because the load balancer gives up first.
2. Nginx send_timeout
This directive controls how long Nginx will wait for the client to acknowledge or receive data (the time between two successive write operations to the client).
Default: 60 seconds
# Set this if clients are timing out while Nginx is sending the response
send_timeout 120s;
Optimizing Buffering for Large Responses
Sometimes the backend starts responding, but delivery is still slow because the response is huge, the client is slow, or Nginx has to buffer more than expected. This is common with generated CSV exports, media downloads routed through an app, or APIs that return very large JSON payloads.
Nginx uses buffers to temporarily hold data received from the upstream before sending it to the client. If the response is very large, these buffers can be exceeded, leading to complex handling or perceived latency.
Key Buffering Directives
These are usually set within the location block or server block:
| Directive | Purpose |
|---|---|
proxy_buffers |
Sets the number and size of buffers used for reading the response from the upstream. Format: number size; |
proxy_buffer_size |
Sets the size of the first buffer, which is used to read the response header. |
proxy_max_temp_file_size |
If the response exceeds available buffers, Nginx writes to temporary files. This sets the max size for these temporary files. |
Example Configuration for High Volume/Large Responses:
location /api/heavy_report {
proxy_pass http://backend_app;
# Increase read timeout
proxy_read_timeout 180s;
# Tune buffering for potentially large response bodies
# Use 8 buffers, each up to 1MB (1024k)
proxy_buffers 8 1024k;
proxy_buffer_size 256k;
# Allow temporary files up to 500MB if buffers overflow
proxy_max_temp_file_size 500m;
}
If your backend response is genuinely huge, consider serving a generated file from object storage or static storage instead of holding the request open through the app. For exports, a common pattern is: enqueue the job, generate the file, then let the user download it from a static URL when it is ready.
Troubleshooting Steps and Log Analysis
Resolving timeouts requires pinpointing where the stall occurred: Client -> Nginx, or Nginx -> Backend.
Step 1: Check Nginx Error Logs
The Nginx error log is your definitive source for determining if Nginx timed out waiting for the backend.
Look for entries containing phrases like:
upstream timed out (110: Connection timed out)upstream prematurely closed connection while reading response header from upstream
If you see these, the issue lies with the proxy_read_timeout or the backend's processing time.
Also look for client prematurely closed connection. That usually means the client or a proxy in front of Nginx gave up first. In that case, raising proxy_read_timeout alone will not help the user.
Step 2: Check Backend Application Logs
If Nginx times out (logs indicate 504), immediately check the logs of the upstream service (e.g., PHP-FPM logs, Gunicorn logs, Java application server logs). You need to confirm if the request even reached the backend and how long it took to complete.
- If the backend logs show the request took longer than your configured
proxy_read_timeout, increase the Nginx timeout. - If the backend logs show the request completed quickly, the issue might be network latency between Nginx and the backend, or a misconfigured client timeout facing Nginx.
Step 3: Use the X-Upstream-Response-Time Header (Optional)
For detailed diagnostics, you can log the exact time the upstream took to respond using the $upstream_response_time variable in your access log format. This helps confirm the backend's actual performance.
In your nginx.conf:
log_format proxy_detailed '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" $request_time $upstream_response_time';
access_log /var/log/nginx/access.log proxy_detailed;
By analyzing $upstream_response_time, you can see the precise duration Nginx waited, independent of Nginx's own timeout settings.
For a quick one-off test, call the upstream directly from the Nginx host:
time curl -sS -o /dev/null -w 'status=%{http_code} total=%{time_total}\n' http://127.0.0.1:3000/slow-route
If the direct upstream call is already slow, Nginx is only reporting the problem. If the direct call is fast but the proxied request times out, inspect proxy configuration, DNS resolution, container networking, TLS between internal services, or another hop between Nginx and the app.
Apply the Smallest Useful Change
A reasonable production fix often looks like this:
location /api/reports/ {
proxy_pass http://backend_app;
proxy_connect_timeout 10s;
proxy_send_timeout 60s;
proxy_read_timeout 180s;
}
That raises the read timeout only for the slow report endpoint. It does not make every request on the site wait three minutes. For a login route, checkout route, health check, or public API endpoint, a long timeout can make failures more painful because clients wait longer for a request that is unlikely to recover.
For PHP-FPM, the equivalent may involve FastCGI directives:
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_read_timeout 120s;
include fastcgi_params;
}
Remember that PHP, Python, Node.js, application servers, queues, databases, CDNs, and load balancers can all have their own timeout settings. Nginx cannot make a backend continue working after the backend's own worker timeout kills the request.
After making any configuration changes (e.g., increasing timeouts or adjusting buffer sizes), always test the configuration syntax and reload Nginx:
sudo nginx -t
sudo systemctl reload nginx
Then watch both Nginx and upstream logs while repeating the same request:
sudo tail -f /var/log/nginx/error.log
The best timeout fix leaves you with a clear reason for the change: this route legitimately takes up to two minutes, this upstream now has matching limits, and slow requests are visible in logs or metrics. Anything less is a temporary patch, and temporary patches should be labeled that way in your config review or incident notes.