Nginx 504 Gateway Timeout PHP-FPM Slow Script Diagnosis
When nginx returns 504 Gateway Timeout for PHP-FPM, the cause is either a timeout shorter than the script's execution time or a genuinely slow script. This guide shows how to isolate the two using upstream response time from nginx logs.
Rao Aadil, India 8 min read
Confirm the 504 comes from nginx, not PHP-FPM
Check the nginx error log as soon as you see "504 Gateway Time-out". On Linux:
tail -f /var/log/nginx/error.log
On Windows PowerShell:
Get-Content C:\nginx\logs\error.log -Tail 100 -Wait
Look for a line like upstream timed out (110: Connection timed out) while reading response header from upstream. Some nginx versions with fastcgi_pass print fastcgi timed out instead. Either message means nginx gave up waiting for the PHP backend.
Next, determine whether PHP-FPM is responding at all. On Linux, if PHP-FPM listens on a Unix socket, send a status request with curl:
curl --unix-socket /var/run/php-fpm.sock http://localhost/status
The status page must be enabled in the PHP-FPM pool config. If the socket is unreachable or the status page is disabled, hit a known-fast PHP script through nginx and watch the error log. On Windows, PHP-FPM is not officially supported; if you run PHP FastCGI under IIS or nginx with php-cgi, check that the PHP process is running and test a simple PHP page through the web server.
Note whether the 504 hits every request or only specific scripts. If all PHP requests time out, suspect a global misconfiguration or backend outage. If only a few endpoints fail, focus on those scripts.
“The page hangs and then dies. The app looks fine when you check it directly, which makes it look like nginx is the problem.”
It compares how long the upstream really takes against the proxy read timeout, so you find out whether to raise the timeout or fix the slow request, rather than guessing.
It reads the real state of the server before it says anything, shows you the exact command, and waits for your approval. Windows and Linux, over the SSH access you already have.
Measure the actual upstream response time from nginx access logs
Add $upstream_response_time to your nginx access log format. It records how many seconds nginx waited for the upstream response header.
On Linux, edit /etc/nginx/nginx.conf. On Windows, edit C:\nginx\conf\nginx.conf. Find the log_format directive and append $upstream_response_time at the end of the format string. For example:
log_format main '$remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" $upstream_response_time';
After reloading nginx, each access log line ends with the upstream response time in seconds, like 0.204 or 30.012.
Tail the access log while reproducing the failing request. On Linux:
tail -f /var/log/nginx/access.log | grep '/slow-script.php'
On Windows PowerShell:
Get-Content C:\nginx\logs\access.log -Tail 100 -Wait | Select-String 'slow-script.php'
If you appended $upstream_response_time as the last field, extract it with awk on Linux:
tail -n 100 /var/log/nginx/access.log | awk '{print $NF}'
Compare that measured value to the read timeout configured in nginx. If the upstream response time is close to or greater than the read timeout, nginx is aborting the request. If it is much lower but the browser still sees a 504, the timeout may be set in a different location or the request is hitting a different upstream.
Identify which timeout governs the PHP-FPM upstream
nginx has two common read timeouts: proxy_read_timeout for proxy_pass upstreams and fastcgi_read_timeout for fastcgi_pass upstreams. PHP-FPM is almost always reached via fastcgi_pass, so fastcgi_read_timeout is the relevant directive. If you use proxy_pass to a PHP-FPM HTTP endpoint (uncommon), proxy_read_timeout applies.
Dump the full config nginx actually loads:
On Linux:
nginx -T | grep -E 'server_name|fastcgi_read_timeout|proxy_read_timeout'
On Windows PowerShell:
nginx -T | Select-String 'server_name|fastcgi_read_timeout|proxy_read_timeout'
nginx -T prints the merged config from all included files. It also shows the file path for each directive, which helps you locate the active server block.
Find the exact file serving your domain by searching the standard include directories. On Linux:
grep -R "server_name example.com" /etc/nginx/sites-available/ /etc/nginx/sites-enabled/ /etc/nginx/conf.d/
On Windows, nginx does not use the sites-available/sites-enabled pattern by default. The main config C:\nginx\conf\nginx.conf typically includes files from a conf.d directory or explicit paths. Use PowerShell:
Select-String -Path C:\nginx\conf\*.conf,C:\nginx\conf\conf.d\*.conf -Pattern 'server_name example.com'
The output shows the file and line number for the server block handling your domain.
Determine if the script is genuinely slow or the timeout is too low
With the measured upstream response time and the configured timeout in hand, the comparison is straightforward.
If upstream_response_time is consistently high—say 30 seconds—and your timeout is 60 seconds, the script is slow but still under the limit. Users see long page loads, not 504s. If the timeout is 10 seconds and the upstream response time is 12 seconds, the timeout is too short for the script's normal execution time.
Check the PHP-FPM slow log. On Linux, enable it in the PHP-FPM pool config (typically /etc/php-fpm.d/www.conf) with:
slowlog = /var/log/php-fpm/slow.log
request_slowlog_timeout = 5s
Any request taking longer than 5 seconds gets logged with a stack trace. On Windows with PHP FastCGI under IIS, there is no PHP-FPM slow log; use IIS Failed Request Tracing or check the PHP error log for timeouts instead.
Also check whether PHP kills the script before nginx gives up. PHP-FPM has request_terminate_timeout in the pool config, and PHP has max_execution_time in php.ini. If either is lower than the nginx timeout, the script may be terminated first, which can still surface as a 504 if the connection closes abruptly. On Linux, these are usually in /etc/php-fpm.d/www.conf and /etc/php.ini. On Windows, check php.ini and the FastCGI activity timeout in IIS.
A tool that reads nginx access logs and compares the actual upstream response time against the timeout configured for the active server block can give you this verdict without manual log digging. It does not profile PHP code, but it tells you whether to raise the nginx timeout or investigate the application.
Fix the correct layer: nginx timeout, PHP-FPM, or application code
If the timeout is too short for the script's normal execution time, raise fastcgi_read_timeout in the correct server block. On Linux, edit the file you located earlier, for example /etc/nginx/sites-available/your-site. On Windows, edit C:\nginx\conf\conf.d\your-site.conf.
Inside the location ~ \.php$ block, set:
fastcgi_pass unix:/var/run/php-fpm.sock; # Linux socket
fastcgi_read_timeout 60s;
On Windows using nginx with php-cgi over TCP, fastcgi_pass might be 127.0.0.1:9000 and the same fastcgi_read_timeout applies.
If the script is genuinely slow, increasing PHP-FPM request_terminate_timeout and PHP max_execution_time buys time while you profile the application code. This is not a permanent fix; a slow script needs code-level optimization, database query analysis, or caching. On Linux, set these in the PHP-FPM pool config and php.ini:
request_terminate_timeout = 60s
max_execution_time = 60
On Windows with IIS FastCGI, adjust the activityTimeout in the FastCGI settings instead of nginx. The path varies, but it is typically set in IIS Manager under FastCGI Settings or in applicationHost.config.
After making changes, test the nginx configuration before reloading:
On Linux:
nginx -t && systemctl reload nginx
On Windows:
nginx -t && nginx -s reload
If you use a wrapper script for root-owned reloads, use it here. On Windows, a PowerShell script with appropriate permissions can serve the same purpose.
Prevent recurrence with config validation and monitoring
Run nginx -t after every config change. It catches syntax errors before reload, preventing downtime. Reload through your wrapper script or standard commands, never a raw shell that might lack the needed permissions.
Keep the access log format with $upstream_response_time enabled. Over time, it gives you a baseline for each PHP application. When a new deployment starts returning 504s, compare the new upstream response times against that baseline.
Set up monitoring or scheduled checks that compare upstream response time against the configured fastcgi_read_timeout or proxy_read_timeout. Alert when the margin becomes too small—for example, when the 95th percentile upstream response time exceeds 80% of the timeout. This gives early warning before users see 504s.
Document the baseline timeout and upstream response time for each PHP application. That forces deliberate changes: when someone raises a timeout, they should be able to explain why the script needs more time and what profiling data supports it.
On Windows, the same nginx -t and nginx -s reload commands apply. The wrapper script idea can be replaced by a PowerShell script executed with appropriate permissions.