An Nginx 504 Gateway Timeout error means Nginx waited too long for the next upstream step to complete. The upstream might be a reverse-proxied application, a PHP-FPM pool, a container, an API server, a private backend, a load balancer, or another proxy layer in front of your origin.
The fix is not always a larger timeout. A 504 can come from a slow application, overloaded workers, a dead backend, a DNS or network path problem, PHP-FPM execution limits, a container mapping issue, or an edge proxy that timed out before the request reached your Nginx server. Start by proving where the wait happens, then change only the setting that owns that layer.
Keep the checks distro-neutral: identify whether the 504 belongs to a reverse proxy backend, PHP-FPM, an upstream network, a container, a CDN or load-balancer layer, or a route that genuinely needs more time before changing configuration.
Understand Nginx 504 Gateway Timeout Causes
A 504 error is a timeout while Nginx is acting as a gateway or proxy. Nginx may be waiting to connect to the upstream, send the request to it, or read the response from it. The exact stage usually appears in the Nginx error log.
| Symptom | Likely Cause | First Check |
|---|---|---|
upstream timed out while reading response header | The backend accepted the request but did not start sending a response in time. | Check application logs, worker load, and proxy_read_timeout or fastcgi_read_timeout. |
upstream timed out while connecting to upstream | Nginx could not establish the upstream connection before the connect timeout. | Test the upstream address and port directly from the Nginx host. |
upstream timed out while sending request to upstream | The upstream was too slow to receive the request body or Nginx could not send data quickly enough. | Check upload size, backend load, request body handling, and proxy_send_timeout. |
| 504 appears only for PHP pages | PHP-FPM or the application code is taking longer than Nginx or PHP-FPM allows. | Check PHP-FPM logs, fastcgi_read_timeout, PHP execution limits, and pool capacity. |
| 504 appears only through a CDN or load balancer | The edge layer timed out before or while talking to the origin. | Compare edge requests with a direct origin request using the same host header. |
| 504 appears during large reports, imports, or admin tasks | The request is synchronous and takes too long for a web request path. | Move long work to a background job when possible, or tune timeouts for that one route. |
The directives that usually matter are timeout controls for the specific upstream type. Keep them scoped to the affected server or location block unless the whole site genuinely needs the same behavior.
| Directive | Context | Use in 504 Diagnosis |
|---|---|---|
proxy_connect_timeout | http, server, location | Controls how long Nginx waits to establish a connection to a proxied HTTP upstream. |
proxy_send_timeout | http, server, location | Controls the timeout between successive write operations while sending a request to the upstream. |
proxy_read_timeout | http, server, location | Controls the timeout between successive read operations while receiving the upstream response. |
fastcgi_connect_timeout | http, server, location | Controls how long Nginx waits to connect to a FastCGI service such as PHP-FPM. |
fastcgi_send_timeout | http, server, location | Controls the timeout between successive write operations while sending a request to the FastCGI service. |
fastcgi_read_timeout | http, server, location | Controls the timeout between successive read operations from a FastCGI service such as PHP-FPM. |
uwsgi_read_timeout | http, server, location | Applies to uWSGI upstreams when the site uses uwsgi_pass instead of proxy_pass or fastcgi_pass. |
error_log | main, http, server, location | Shows which timeout stage failed and which upstream address or socket was involved. |
Do not raise every timeout globally as the first fix. A higher timeout can hide a crashed worker, a saturated application pool, a broken upstream address, or a request that should run as a background job instead of an HTTP request.
Diagnose an Nginx 504 Gateway Timeout
First confirm that the response is a 504 and identify which layer returned it. Use the curl command from a client machine or from the server itself:
curl -I http://example.com
A response generated by Nginx often looks like this:
HTTP/1.1 504 Gateway Timeout Server: nginx
If the server header names a CDN, load balancer, reverse proxy manager, or hosting platform, keep that layer in scope. The origin Nginx logs may show no matching request when the timeout happened before the request reached the origin.
Check the Nginx Error Log for 504 Details
The Nginx error log is the fastest way to separate a slow backend from a connection problem. Use the tail command against the common packaged log path first:
sudo tail -n 50 /var/log/nginx/error.log
Relevant timeout fragments often look like one of these lines:
upstream timed out (110: Connection timed out) while connecting to upstream upstream timed out (110: Connection timed out) while sending request to upstream upstream timed out (110: Connection timed out) while reading response header from upstream
When the site uses per-vhost logs or a custom layout, pipe the parsed configuration through the grep command to discover the active log directives:
sudo nginx -T 2>/dev/null | grep -E '^[[:space:]]*(access_log|error_log)'
Relevant lines can show the log file that owns the affected virtual host:
error_log /var/log/nginx/example.error.log warn; access_log /var/log/nginx/example.access.log main;
The Nginx access and error logs guide covers log path discovery, access log filtering, and custom upstream timing fields in more depth.
Confirm the Nginx Configuration Still Parses
Timeout changes, upstream edits, and copied snippets should always pass a syntax test before reload:
sudo nginx -t
A successful syntax test prints:
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful
If Nginx reports a syntax error, fix that first. A configuration that does not parse can leave the old working config active, so later timeout edits may not actually be in use.
Check Whether the 504 Reaches the Origin
Send a request directly to the origin while preserving the host header. Replace 203.0.113.10 with the origin IP address:
curl -I --resolve example.com:80:203.0.113.10 http://example.com/
For HTTPS origins, test the TLS endpoint directly:
curl -I --resolve example.com:443:203.0.113.10 https://example.com/
If the direct origin request works but the public request still returns 504, inspect the CDN, load balancer, proxy manager, or firewall path between clients and the origin. If the direct origin request also returns 504, continue on the Nginx host.
Fix Nginx 504 for Reverse Proxy Backends
Reverse proxy 504 errors usually involve proxy_pass, an upstream block, or an application server behind Nginx. The Nginx reverse proxy configuration guide covers the base proxy pattern; this section focuses on timeout-specific checks.
Test the Nginx Upstream Directly
Find the upstream target in your Nginx configuration first:
sudo nginx -T 2>/dev/null | grep -E '^[[:space:]]*(proxy_pass|upstream|server[[:space:]]+[0-9a-zA-Z_.:-]+)'
Relevant lines might show a loopback backend or a named upstream group:
proxy_pass http://127.0.0.1:3000;
upstream app_backend {
server 127.0.0.1:3000;
}
Test that backend from the Nginx server without going through the public virtual host:
curl -fsS http://127.0.0.1:3000/health
A healthy JSON endpoint might return:
{"status":"ok"}
If the direct backend request is slow, hangs, or fails, fix the application first. Increasing proxy_read_timeout only makes Nginx wait longer for a backend that is already unhealthy.
Check the Upstream Listener for Nginx
Confirm that the backend process is listening on the address and port from proxy_pass:
sudo ss -tulpn | grep ':3000'
Relevant output should show a process listening on the expected address:
tcp LISTEN 0 511 127.0.0.1:3000 0.0.0.0:*
If the backend listens only on a container network, private IP, or Unix socket, the proxy_pass target must match the address that the Nginx worker can actually reach. When path handling also looks wrong, compare your setup with the Nginx proxy_pass trailing slash explanation.
Set Nginx Proxy Timeouts for the Affected Location
Use timeout settings only after the upstream has passed a direct health check. Put them in the location that serves the slow route, not in every virtual host by default.
location /reports/ {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 10s;
proxy_send_timeout 60s;
proxy_read_timeout 120s;
}
proxy_read_timeout is not a full request duration guarantee. It controls the timeout between successive reads from the upstream. If the upstream streams data periodically, the request can stay open longer than the numeric value. If the upstream sends nothing until the job finishes, Nginx can still time out before the first response header or body chunk arrives.
Test and reload after editing the site configuration:
sudo nginx -t && sudo systemctl reload nginx
The syntax test should pass before the reload applies the timeout change:
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful
Check the Application Service Behind Nginx
When the backend is a systemd service, check whether the service is active:
systemctl is-active app.service
A healthy service state prints:
active
Then inspect recent logs for worker restarts, timeouts, database waits, or queue exhaustion:
sudo journalctl -u app.service --since "30 minutes ago"
Replace app.service with the real unit name. A backend that is active but saturated can still time out. Check worker counts, database queries, external API calls, and queue depth before making the Nginx timeout much larger.
Fix Nginx 504 for PHP-FPM and FastCGI
PHP-FPM 504 errors usually appear when PHP takes too long to generate a response. A wrong PHP-FPM socket more often produces a 502, but a slow PHP worker, long database query, blocked filesystem operation, or strict FPM execution limit can produce a 504.
Check PHP-FPM Service and Logs
Find the PHP-FPM service on your system:
systemctl list-unit-files --no-pager 'php*fpm*.service' 'php-fpm.service'
On some Debian and Ubuntu systems, the unit is versioned, such as php8.3-fpm. On Fedora, RHEL, Rocky Linux, and similar systems, the unit is often php-fpm. Use the unit name your package installed when checking its state:
systemctl is-active php-fpm
A healthy PHP-FPM service prints:
active
Replace php-fpm with the detected versioned unit when your distribution uses one.
Check recent PHP-FPM logs with the detected unit name:
sudo journalctl -u php-fpm --since "30 minutes ago"
Look for slow script warnings, worker exhaustion, terminated requests, upstream application errors, or database connection waits. If PHP-FPM is stopped or the socket path is wrong, use the Nginx 502 Bad Gateway troubleshooting guide instead because that is a different failure class.
Set Nginx FastCGI Timeout for Slow PHP Routes
For PHP routes that legitimately need more time, increase fastcgi_read_timeout in the PHP location. Keep the value scoped and realistic.
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_connect_timeout 10s;
fastcgi_send_timeout 60s;
fastcgi_read_timeout 120s;
}
Adjust the fastcgi_pass value to match your PHP-FPM socket or TCP listener. The socket path above is an example, not a universal path.
Validate and reload after editing:
sudo nginx -t && sudo systemctl reload nginx
Check PHP Execution Limits Behind Nginx
If Nginx waits longer but PHP-FPM still kills the request, check PHP and pool-level limits. The exact files differ by distribution and PHP version, so search the common locations:
sudo grep -R "^[[:space:]]*max_execution_time" /etc/php* /etc/php.d /etc/php.ini 2>/dev/null
sudo grep -R "^[[:space:]]*request_terminate_timeout" /etc/php* /etc/php-fpm.d 2>/dev/null
Relevant lines can show whether PHP or PHP-FPM stops the script before Nginx receives a response:
max_execution_time = 30 request_terminate_timeout = 0
Do not raise PHP execution limits for the whole server unless the application requires it. Long imports, exports, cache rebuilds, and report generation usually work better as queued jobs with a status page than as one long PHP request.
Fix Nginx 504 for Containers and Private Upstreams
Containerized applications add another networking layer. A 504 can happen when Nginx points to the wrong published port, an internal-only container address, a service name that only exists inside Docker, or a backend that responds slowly under container resource limits.
Check the Host Port Published to Nginx
List running containers and their published ports:
docker ps --format 'table {{.Names}}\t{{.Ports}}'
Relevant output should show the host port that Nginx can reach:
NAMES PORTS webapp 127.0.0.1:3000->3000/tcp
Test the published host port from the Nginx server:
curl -fsS http://127.0.0.1:3000/health
If Nginx runs on the host, proxy_pass http://webapp:3000; works only when the host can resolve webapp in the relevant network context. Many Docker Compose service names resolve only from containers on the same Compose network. Use the published host port, place Nginx in the same network, or use the correct upstream address for your architecture.
Check Container Logs for Slow or Stuck Requests
Inspect the backend container logs for application-level waits or crashes:
docker logs --since 30m webapp
Replace webapp with the container name. If the log shows database waits, worker restarts, memory pressure, or request queueing, fix the application or resource problem before treating Nginx as the root cause.
Fix Nginx 504 Behind a CDN or Load Balancer
A CDN, cloud load balancer, reverse proxy appliance, or hosting panel can generate its own 504 response. The origin Nginx server might be healthy while the outer layer cannot reach it, cannot complete TLS, hits its own idle timeout, or connects to a different origin address than expected.
Compare Edge and Origin Requests
Check the public path first:
curl -I https://example.com/slow-route
Then test the origin directly with the same hostname mapped to the origin IP address:
curl -I --resolve example.com:443:203.0.113.10 https://example.com/slow-route
If the origin request succeeds but the edge request times out, check the edge timeout, origin firewall, origin certificate, proxy protocol settings, health checks, and target pool. If both requests time out, inspect the origin Nginx and upstream logs.
Check Whether Nginx Saw the Timed-Out Request
Search the origin access log for the timed-out URI:
sudo grep ' /slow-route ' /var/log/nginx/access.log | tail -20
If there is no matching request, the request likely timed out before reaching this Nginx instance or reached a different virtual host/log file. Confirm DNS, load-balancer target settings, firewall rules, and per-site log paths before changing origin timeouts.
Tune Nginx Timeouts Safely
Timeouts should match the job and the upstream type. A public homepage, API request, WebSocket connection, background export, and PHP admin import do not need the same timeout profile.
| Request Type | Better Nginx Scope | Notes |
|---|---|---|
| Normal public pages | Keep defaults or modest values | Long waits usually point to an application or database problem. |
| Admin reports or exports | Specific admin location | Use a longer timeout only for the route that needs it. |
| Large uploads to an app | Upload endpoint | Check body-size limits and app upload handling as well as send/read timeouts. |
| Streaming responses | Streaming location | The upstream should send periodic data or heartbeats if the connection must stay open. |
| PHP imports or migrations | PHP location plus PHP-FPM limits | Nginx and PHP-FPM limits must not contradict each other. |
For slow reverse-proxy routes, a scoped block like this is safer than raising timeouts for every site:
location /admin/export/ {
proxy_pass http://127.0.0.1:3000;
proxy_connect_timeout 10s;
proxy_send_timeout 60s;
proxy_read_timeout 180s;
}
For slow PHP routes, use FastCGI timeouts instead of proxy timeouts:
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_read_timeout 120s;
}
For uWSGI applications, use the matching uWSGI directive instead:
location /app/ {
include uwsgi_params;
uwsgi_pass 127.0.0.1:3031;
uwsgi_read_timeout 120s;
}
Do not mix timeout families. proxy_read_timeout affects proxy_pass. fastcgi_read_timeout affects fastcgi_pass. uwsgi_read_timeout affects uwsgi_pass. If the wrong upstream family owns the request, the directive will not fix the timeout.
Verify the Nginx 504 Gateway Timeout Fix
After applying the focused fix, test the configuration, reload Nginx, and repeat the failing request.
sudo nginx -t && sudo systemctl reload nginx
The syntax test should still show a clean configuration:
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful
Check the failing URL again:
curl -I https://example.com/slow-route
A successful response should no longer return 504:
HTTP/2 200
Then check the error log again to confirm the timeout message stopped during the test window:
sudo tail -n 50 /var/log/nginx/error.log
If the status improved but the route is still slow, keep the application performance issue on the work list. Nginx can wait longer, but it cannot make the upstream faster.
Troubleshoot Common Nginx 504 Gateway Timeout Problems
Use these symptom-focused checks when the first timeout change does not solve the problem. Replace example ports, routes, and service names with the values from your Nginx configuration, then retest with sudo nginx -t, a reload, and the failing request.
Nginx Error Log Shows Timeout While Reading Response Header
This means Nginx connected to the upstream, but the upstream did not send response headers in time. Match the log phrase, then test the backend route directly from the Nginx host:
sudo tail -n 200 /var/log/nginx/error.log | grep 'while reading response header'
curl -fsS http://127.0.0.1:3000/health
If the direct backend request is also slow, check application routes, database queries, external API calls, worker saturation, and upstream logs. If the route is expected to take longer and the backend is otherwise healthy, increase proxy_read_timeout or fastcgi_read_timeout for that route only.
Nginx Error Log Shows Timeout While Connecting to Upstream
This points to a listener, firewall, route, DNS, or network problem. Compare the address in proxy_pass with the actual listener and a direct request:
sudo ss -tulpn | grep ':3000'
curl -fsS http://127.0.0.1:3000/health
If the listener is missing or bound to a different address, fix the backend service, container port, firewall path, or upstream target before changing Nginx timeouts. A higher proxy_connect_timeout rarely fixes a backend that is unreachable.
Nginx 504 Appears Only After Uploads
Uploads can fail at several layers. Check whether the timeout happened while Nginx was sending the request body upstream, then inspect upload-related directives:
sudo tail -n 200 /var/log/nginx/error.log | grep 'while sending request to upstream'
sudo nginx -T 2>/dev/null | grep -E 'client_max_body_size|proxy_request_buffering|proxy_send_timeout|proxy_read_timeout'
If the error matches request-body sending, review upstream upload handling, temporary storage, application logs, and edge proxy limits. Increase proxy_send_timeout only when the backend can receive the upload but needs more time between write operations.
PHP-FPM Still Times Out After Nginx Changes
Nginx timeout settings do not override every PHP or PHP-FPM limit. Confirm the installed FPM unit, then search for PHP and pool-level runtime limits:
systemctl list-unit-files --no-pager 'php*fpm*.service' 'php-fpm.service'
sudo grep -R -E "^[[:space:]]*(max_execution_time|request_terminate_timeout)" /etc/php* /etc/php-fpm.d /etc/php.ini 2>/dev/null
If PHP-FPM kills the request first, align fastcgi_read_timeout, PHP execution time, pool limits, and application-level timeouts for the affected route. If the PHP-FPM socket is missing or inaccessible, the problem is closer to a 502 than a 504.
Nginx 504 Has No Matching Origin Log Entry
No matching origin log entry usually means the request did not reach this Nginx instance, reached another virtual host, or was logged somewhere else. Search the expected access log and confirm which logs the active virtual host writes to:
sudo grep ' /slow-route ' /var/log/nginx/access.log | tail -20
sudo nginx -T 2>/dev/null | grep -E '^[[:space:]]*(server_name|access_log|error_log)'
If the route is absent from origin logs, check the CDN or load balancer, DNS, firewall rules, default server selection, and per-site log configuration before changing origin timeouts. The Nginx server blocks and virtual hosts guide helps when the request may be landing in the wrong virtual host.
Nginx 504 Becomes Nginx 502 After Editing Upstreams
A 502 after an upstream edit often means the timeout problem turned into a connection, socket, protocol, or invalid-response problem. Search the error log for the stable 502 clues:
sudo tail -n 100 /var/log/nginx/error.log | grep -E 'connect\(\) failed|Permission denied|no live upstreams|upstream prematurely closed'
Recheck proxy_pass, fastcgi_pass, upstream names, container ports, and PHP-FPM socket paths. Use the Nginx 502 troubleshooting workflow when the log shows connection refused, missing sockets, permission denied, or invalid upstream headers.
Nginx 504 Returns Only for One Path
One-path timeouts usually come from a route-specific application problem or a different location block. Print the active locations and upstream handoffs, then compare the failing URI with the matching block:
sudo nginx -T 2>/dev/null | grep -E '^[[:space:]]*(location|proxy_pass|fastcgi_pass)'
Apply the timeout or upstream fix in the location that actually handles the URI. The Nginx location block priority guide helps when prefix, exact, and regex locations overlap.
Conclusion
A reliable Nginx 504 Gateway Timeout fix is scoped to the layer that owns the wait: origin reachability, upstream listener, application service, PHP-FPM pool, container mapping, CDN or load-balancer path, or a focused timeout. Keep the Nginx log guide, Nginx 502 Bad Gateway guide, and proxy_pass trailing slash guide nearby when upstream behavior points to a neighboring failure.


Formatting tips for your comment
You can use basic HTML to format your comment. Useful tags currently allowed in published comments:
<code>command</code>command<strong>bold</strong><em>italic</em><blockquote>quote</blockquote>