The HTTP 504 Gateway Timeout error occurs when a web server acting as a gateway or reverse proxy (such as Nginx, Cloudflare, or AWS ALB) attempts to connect to an upstream application server (such as PHP-FPM, Apache, Python Gunicorn, or Node.js), but the upstream server fails to respond before a predefined timeout threshold expires.
By default, Nginx waits 60 seconds for an upstream process to finish. If a script executes an unindexed database query, calls a stalled external API, or exhausts all available worker threads, Nginx drops the connection and returns a 504 error.
Quick Answer
- Identify the failing upstream in your Nginx error log:
grep -i "upstream timed out" /var/log/nginx/error.log | tail -n 10 - If the request requires more than 60 seconds to finish (such as large reports or imports), increase Nginx timeouts inside your
location ~ \.php$block:fastcgi_read_timeout 300s; fastcgi_send_timeout 300s; proxy_read_timeout 300s; - Match the timeout in your PHP-FPM configuration (
request_terminate_timeout = 300s) andphp.ini(max_execution_time = 300). - Check if PHP-FPM worker pools are exhausted:
grep "server reached pm.max_children" /var/log/php*-fpm.log.
Symptoms
- The browser shows “504 Gateway Timeout” with an Nginx footer after exactly 60 seconds of waiting.
- In Cloudflare-managed sites, Cloudflare displays a branded Error 504: Gateway Timeout screen.
- Heavy tasks (like CSV exports, WooCommerce reporting, or backup creations) fail consistently at the 1-minute mark.
- During traffic spikes, the entire website becomes unresponsive and starts returning 504 errors across all pages.
Common Causes
- Slow Database Queries: MySQL or PostgreSQL running complex full-table scans that take longer than 60 seconds to return data.
- PHP-FPM Worker Starvation (
pm.max_childrenReached): All available PHP workers are busy handling slow requests. Incoming requests queue up and time out before a worker becomes free. - Hung External API Calls: A web application makes a synchronous
curlcall to a third-party payment gateway, shipping API, or license server that is down or lagging. - Insufficient Timeout Configurations: Valid, long-running administrative tasks (such as database migrations or report generation) exceeding default 60-second timeouts.
- High Server CPU/Disk I/O Wait: System resources at 100% saturation, slowing down PHP script execution across the board.
Before You Start
- Distinguish between raising timeouts (for legitimate long tasks) and fixing performance bottlenecks (for slow code). Simply raising timeouts to 600s will not fix a site if traffic spikes are exhausting your workers.
- Review our guide on server performance optimization for holistic database and caching audits.
Step 1 — Check the Nginx Error Log
Inspect /var/log/nginx/error.log to confirm the exact upstream service timing out:
tail -n 30 /var/log/nginx/error.log
Sample Log Entry
2026/09/18 14:10:22 [error] 14201#14201: *8912 upstream timed out (110: Connection timed out) while reading response header from upstream, client: 203.0.113.45, server: yourdomain.com, request: "POST /wp-admin/admin-ajax.php HTTP/2.0", upstream: "fastcgi://unix:/run/php/php8.2-fpm.sock", host: "yourdomain.com"
What This Tells You
- The client requested
POST /wp-admin/admin-ajax.php. - The upstream was PHP-FPM over a Unix socket (
unix:/run/php/php8.2-fpm.sock). - PHP-FPM did not return any headers within the timeout window.
Step 2 — Increase Nginx FastCGI and Proxy Timeouts
If your application legitimately requires more than 60 seconds to process a batch job, increase Nginx’s timeout directives.
Open your site’s Nginx configuration:
sudo nano /etc/nginx/sites-available/yourdomain.com.conf
For PHP-FPM FastCGI Upstreams:
Add or increase these directives inside the location ~ \.php$ block:
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
# Extend FastCGI timeouts to 300 seconds (5 minutes)
fastcgi_read_timeout 300s;
fastcgi_connect_timeout 300s;
fastcgi_send_timeout 300s;
}
For Reverse Proxy Upstreams (Node.js, Python Gunicorn, Apache):
If Nginx proxies HTTP traffic to an internal port:
location / {
proxy_pass http://127.0.0.1:3000;
proxy_read_timeout 300s;
proxy_connect_timeout 300s;
proxy_send_timeout 300s;
}
Test syntax and reload Nginx:
sudo nginx -t && sudo systemctl reload nginx
Step 3 — Synchronize PHP-FPM Execution Limits
Nginx may now wait 300 seconds, but if PHP-FPM is configured to kill scripts after 30 or 60 seconds, your site will switch from a 504 to a 502 error.
1. Update php.ini
Edit your active php.ini (e.g., /etc/php/8.2/fpm/php.ini):
max_execution_time = 300
max_input_time = 300
2. Update the PHP-FPM Pool Configuration
Edit /etc/php/8.2/fpm/pool.d/www.conf:
; Terminate request if it takes longer than 300 seconds
request_terminate_timeout = 300s
Reload PHP-FPM:
sudo systemctl reload php8.2-fpm
Step 4 — Check for PHP-FPM Worker Starvation
If 504 errors appear across the entire site simultaneously during traffic surges, your PHP-FPM pool has run out of available worker processes.
Check your PHP-FPM log:
grep -i "max_children" /var/log/php*-fpm.log | tail -n 10
Critical Warning Entry
[18-Sep-2026 14:15:02] WARNING: [pool www] server reached pm.max_children setting (10), consider raising it
When this warning occurs, all 10 workers are busy. New incoming requests sit in the socket listen backlog until Nginx’s fastcgi_connect_timeout expires, returning a 504 error.
How to Fix Worker Limits
Edit /etc/php/8.2/fpm/pool.d/www.conf and adjust worker calculations based on your available RAM:
pm = dynamic
pm.max_children = 50
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 15
pm.max_requests = 1000
Calculation Formula: Total RAM allocated to PHP $\div$ Average PHP process size (typically 60MB–90MB) =
pm.max_children. For example, 4GB of RAM $\div$ 80MB $\approx$ 50 workers.
Reload PHP-FPM to apply changes.
Step 5 — Trace Slow Database Queries
If PHP workers are getting stuck, they are almost always waiting for slow database queries.
Check running queries inside MySQL:
mysql -e "SHOW FULL PROCESSLIST;" | grep -v "Sleep"
Look for queries with execution times (Time) exceeding 30 to 60 seconds.
Enable the slow query log in /etc/mysql/my.cnf:
[mysqld]
slow_query_log = 1
slow_query_log_file = /var/log/mysql/mysql-slow.log
long_query_time = 2
Reload MySQL and inspect /var/log/mysql/mysql-slow.log to identify the unindexed queries dragging down your upstream processes.
Step 6 — Verify Resolution
- Re-execute the request that previously triggered the 504 timeout.
- Monitor Nginx access logs in real time:
tail -f /var/log/nginx/access.log | grep -E " 504 | 200 " - Confirm that the status code returns
200 OKand that response times stay within acceptable operational parameters.
Common Mistakes
- Increasing Nginx timeout without increasing PHP timeout: Setting Nginx to 300s while PHP
max_execution_timeremains 30s results in a 502 Bad Gateway instead of fixing the problem. - Setting
pm.max_childrentoo high: Setting 200 workers on a server with only 2GB of RAM will trigger Linux kernel Out of Memory (OOM) kills, crashing the entire server. - Masking bad code with massive timeouts: Setting timeouts to 15 minutes allows unindexed queries or stuck external API loops to tie up server resources, degrading overall performance.
Prevention Checklist
- Implement Redis or Memcached object caching to reduce repetitive database queries.
- Enforce strict timeout limits on all external cURL requests in your code (e.g.,
CURLOPT_TIMEOUT = 10). - Monitor PHP-FPM active processes via the
/statuspage. - Set up infrastructure monitoring to alert when upstream response latency exceeds 5 seconds.
Quick Reference Directives
| Component | Directive | Default | Recommended |
|---|---|---|---|
| Nginx | fastcgi_read_timeout | 60s | 300s |
| Nginx | proxy_read_timeout | 60s | 300s |
| PHP-FPM | request_terminate_timeout | 0 | 300s |
php.ini | max_execution_time | 30 | 300 |
| PHP-FPM | pm.max_children | 5 | Tuned to RAM |
Frequently Asked Questions
What is the difference between a 502 and a 504 error?
- 502 Bad Gateway: The upstream server (PHP-FPM) actively crashed, refused the connection, or closed the socket immediately.
- 504 Gateway Timeout: The upstream server accepted the connection, but took too long to complete the work and never finished before the timer ran out.
Why does Cloudflare show Error 504?
Cloudflare waits a maximum of 100 seconds for your origin server to respond. If your origin server (Nginx/Apache) takes longer than 100 seconds, Cloudflare automatically drops the client connection and serves its branded Error 504 screen.
Can an overloaded database server cause 504 timeouts?
Yes. If MySQL CPU usage hits 100%, individual queries take minutes instead of milliseconds. Every PHP-FPM worker stalls waiting for query results, eventually causing Nginx to trigger 504 Gateway Timeout errors for all visitors.
How does ServerCare360 assist with upstream timeout issues?
Our certified server performance optimization and emergency server support teams analyze slow query dumps, configure Redis caching, optimize PHP-FPM concurrency pools, and eliminate upstream latency bottlenecks 24/7.