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.

Doing this with brynko devOps Agent

“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.

Download for WindowsSee what else it does

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.

Related reading

Stop guessing whether nginx or PHP is causing 504s

See the upstream response time and active timeout side by side, validate configs, and reload safely without digging through logs by hand.

An AI teammate that builds, runs, fixes, deploys and operates your software, on your machine and on your servers. It reads the real state of your systems, shows you every command before it runs, and never touches production without your approval.

Works with Docker Docker Compose nginx IIS Tomcat MySQL / MariaDB PostgreSQL Redis Linux servers Windows servers AWS Jenkins Let's Encrypt Node.js Python Java .NET Go PHP
  • Every change asks first, and shows you the exact command or diff before it runs.
  • File edits are snapshotted and reversible. Deleted folders are archived, not removed.
  • Private key material never reaches the model, under any setting.
brynko devOps Agent
Web servers

the site is throwing 502, it was fine yesterday

read the error log checked the vhost listed ports

nginx is fine and so is your app. The error log repeats connect() failed (111: Connection refused) for upstream 127.0.0.1:8019, but nothing is listening there. Your container is bound to 9014. The port changed at the last deploy and the vhost still points at the old one.

point nginx at the right one

one line in the vhost nginx -t passed reloaded

Done. I changed the upstream port in your site config, tested it before applying, and reloaded. The site is returning 200 again.

Fixed · 200 OK

Free download for Windows. 300 free credits on signup, no card, no subscription.

Questions

Common questions

Why does nginx return 504 but PHP-FPM works when I curl the socket directly?
A direct curl to the PHP-FPM socket bypasses nginx entirely, so nginx's read timeout does not apply. The 504 occurs because nginx gave up waiting for a response header from PHP-FPM, even though PHP-FPM eventually completes the request.
How do I check if nginx timed out because my PHP script is slow or because fastcgi_read_timeout is too low?
Add $upstream_response_time to the nginx access log and compare the measured value against the configured fastcgi_read_timeout or proxy_read_timeout. If the response time is close to or exceeds the timeout, nginx is aborting a slow request; if it is much lower, the timeout may be misconfigured elsewhere.
Which nginx directive should I change: proxy_read_timeout or fastcgi_read_timeout?
If you use fastcgi_pass to send PHP requests to PHP-FPM, change fastcgi_read_timeout. If you use proxy_pass to an HTTP endpoint, change proxy_read_timeout. Most PHP-FPM setups use fastcgi_pass.
How can I log upstream_response_time in nginx to see how long PHP-FPM takes?
Edit the log_format directive in your nginx config to include $upstream_response_time at the end of the format string. After reloading, each access log entry will show the upstream response time in seconds.
Does raising the nginx timeout fix a slow PHP script?
No, it only gives the script more time to finish before nginx aborts the request. The script remains slow and will still consume resources; you need code-level profiling and optimization to address the root cause.
How do I find the exact config file that sets the timeout for my domain?
Run nginx -T to dump the effective config and grep for server_name and timeout directives. On Linux you can also grep the sites-available, sites-enabled, and conf.d directories; on Windows search the nginx conf directory and any included conf.d files.

Keep reading