On this page
A 504 response means a gateway waited too long for an upstream response. It does not automatically mean Nginx needs a larger timeout, nor does it prove that PHP-FPM has too few workers.
A busy PHP pool can queue requests behind slow database queries, external API calls, or exhausted memory. This guide traces the request path, sizes a pool from measurements, and tests changes without turning a slow application into an out-of-memory incident.
Identify what is timing out
Map the request path: browser, CDN, reverse proxy, PHP-FPM, database, and external services. Any intermediate gateway can impose a timeout. First determine which component produced the 504 and correlate its timestamp with the application and server logs.
Use the Regex Tester below to prototype filters for repeated upstream errors. Paste sanitized log lines and match phrases such as “upstream timed out” or “max_children”. The tool helps organize evidence; it does not inspect your server or establish causation from one message.
Build a correlation window around a failing request rather than searching an entire log without context. Record the route, status, duration, request identifier when available, and whether neighboring requests also slowed down. Remove cookies, tokens, and personal information before pasting logs into any external tool.
A gateway timeout while connecting to an upstream differs from one while reading its response. Read the complete error message. The Nginx documentation explains FastCGI timeout directives; a read timeout measures inactivity between reads, not necessarily total application runtime.
Collect pool and host evidence
Find the active pool configuration rather than editing the first file named www.conf. Distribution packages may support multiple PHP versions and multiple pools. Container deployments can use different filesystem paths and memory limits from the host.
This example uses a common Ubuntu-style PHP 8.3 installation. Replace the version and service names with the installed ones, and run status commands during the problem window.
systemctl status php8.3-fpm --no-pager
sudo journalctl -u php8.3-fpm --since '20 minutes ago' --no-pager
sudo tail -n 100 /var/log/nginx/error.log
ps -eo pid,ppid,rss,args | grep '[p]hp-fpm'
free -mLook for worker saturation, increasing request queues, process restarts, and kernel out-of-memory events. A max_children warning means the pool reached its configured limit, but it does not explain why requests kept workers occupied for so long.
PHP-FPM supports a status endpoint with useful counters, including active processes, idle processes, and queue information. Expose it only through a restricted local route or authenticated administrative network. A dedicated status listener can remain reachable when the main pool is busy, but it needs careful configuration and access control.
Consult the PHP-FPM manual for process-manager options and supported settings. Also inspect container memory limits when applicable; unused host RAM is irrelevant if the pool's container is capped far below it.
Size workers from a conservative memory budget
Determine the RAM available to PHP after reserving capacity for the operating system, database, web server, monitoring, and filesystem activity. This is your pool budget, not the VPS's advertised total memory.
Measure representative worker memory after warmup and during heavy routes. RSS includes shared pages, so summing it can overestimate physical use. Proportional set size is more informative when available, but a conservative RSS-based estimate can still provide a safe initial ceiling.
For illustration, a 900 MiB PHP budget divided by a measured 90 MiB per busy worker suggests ten workers before additional headroom. That is arithmetic, not a recommended configuration. Recalculate for your actual plugins, traffic mix, and peak request behavior.
Choose the process manager deliberately. Dynamic mode maintains a configurable set of spare processes, while ondemand starts them as requests arrive and retires idle workers. Static mode keeps a fixed worker count. Each has startup, latency, and memory trade-offs.
; Illustrative pool values, not a universal tuning preset.
[www]
pm = dynamic
pm.max_children = 8
pm.start_servers = 2
pm.min_spare_servers = 2
pm.max_spare_servers = 4
pm.max_requests = 500
request_slowlog_timeout = 5s
slowlog = /var/log/php8.3-fpm-www-slow.logEnsure the service can write the slow-log path. pm.max_requests recycles workers after a request count; it can limit accumulated memory growth, but it does not repair a leak or constrain an individual request's runtime. Raising max_children also raises potential database connections and upstream API concurrency.
Find the requests occupying workers
Enable a slow log temporarily with an appropriate threshold and check whether your deployment permits collecting stack traces. Slow logs can reveal repeated plugin functions, external HTTP calls, and database operations. Keep them access-controlled because paths and application details may be sensitive.
Use the regular expression tool to refine a pattern against sanitized samples before applying a similar filter locally. JavaScript-style regex behavior can differ from command-line grep, so choose a simple portable expression when transferring it.
sudo grep -E 'upstream timed out|max_children|child .* exited' /var/log/nginx/error.log /var/log/php8.3-fpm.log
sudo tail -n 100 /var/log/php8.3-fpm-www-slow.logLogs vary by package and rotation policy; verify paths first. Correlate application symptoms with slow-query data rather than assuming every busy worker is CPU-bound. The MySQL documentation explains database diagnostics that help distinguish query execution from connection or lock waits.
If many workers wait on one remote service, set application-level connect and response timeouts and add graceful failure behavior. If an expensive report is responsible, move it to a bounded background queue rather than holding an interactive request open indefinitely.
Cache only where application semantics permit it. Public article caching can reduce PHP demand, but user-specific pages, checkout state, and authenticated responses require careful exclusion. The Redis caching guide discusses another layer that can reduce repeated application work.
Change one limit and test the whole system
Validate configuration before a controlled reload. Keep the previous file and record the change so rollback is straightforward. Use the distribution's PHP-FPM configuration-test command and service reload procedure; do not assume all installations share one binary name.
Test a representative mix at a modest, authorized request rate, then increase gradually within your environment's limits. Observe queue depth, error rate, high-percentile latency, memory pressure, CPU utilization, and database connections together. A lower queue with rising swap activity is not a healthy improvement.
Keep timeout layers coherent. An application dependency should usually fail with enough time left for PHP to return a useful response before a gateway gives up. Longer gateway waits can be appropriate for a justified workload, but they also keep resources occupied longer.
Do not broadly raise execution limits to hide slow requests. Some legitimate long-running tasks belong in command-line workers with explicit job deadlines. For interactive pages, prioritize bounded work, indexed queries, and predictable dependency behavior.
After a successful test, monitor a full traffic cycle. Quiet-hour results can miss scheduled imports, backups, or checkout peaks. Preserve the measurements that justified the final settings and repeat them after significant application changes.
Key takeaways
- Diagnose the request path before treating every 504 as a worker shortage.
- Size worker limits from measured memory and downstream capacity, not CPU count alone.
- Use protected status data and slow logs to identify long-held requests.
- Fix slow dependencies and database bottlenecks before expanding concurrency.
- Validate changes with queue, memory, and latency measurements, and keep rollback ready.
Reach designers & developers at work
Put your product next to the tools people use every day. One sponsor per placement, no tracking pixels.
Become a sponsor