Skip to main content

Cloudsoft Zone Build, deploy and scale with practical guides to PHP, Node.js, React, DevOps and AI automation. Explore the guides

Cloudsoft Zone
PHPDevOpsTroubleshooting guide

Fix PHP-FPM 'server reached pm.max_children' and 502 errors

Why PHP-FPM runs out of workers, how to read the status page and slow log, and how to size pm.max_children from real memory use instead of guessing.

Cloudsoft Zone

Updated 7 min read

Busy fast-food kitchen with staff in white uniforms and red aprons preparing orders at a counter
Photograph: El Suizo / CC BY-SA 4.0, via Wikimedia Commons
In this article

When PHP-FPM logs server reached pm.max_children setting, every worker in the pool is busy and new requests wait in a queue. Users see slow pages, then 504 Gateway Timeout or 502 Bad Gateway errors from Nginx. The fix is not to raise the number blindly: measure how much memory a worker uses, size pm.max_children to the RAM you have, and find the slow requests that keep workers busy.

We reproduced the problem on PHP 8.5.7 with a deliberately tiny pool, and every log line and status field below comes from that run.

What the warning means

PHP-FPM runs a fixed maximum number of worker processes per pool. Each worker handles one request at a time. The manual describes pm.max_children as the limit on simultaneous requests that will be served. When all workers are active and another request arrives, FPM logs a warning and the request waits for a free worker.

We configured a pool with pm.max_children = 2 and sent four requests to a script that sleeps for 2.5 seconds. The error log showed:

Text
WARNING: [pool demo] server reached pm.max_children setting (2), consider raising it
WARNING: [pool demo] child 91338, script '/srv/www/slow.php' (request: "GET /slow.php") executing too slow (1.005765 sec), logging

The first line is the capacity problem. The second comes from the slow log, which we enabled with request_slowlog_timeout = 1s. Together they tell the usual story: requests are slow, so workers stay busy, so the pool runs out.

How this turns into 502 and 504 errors

Queued requests are not lost straight away. Nginx waits for a response for fastcgi_read_timeout, which defaults to 60 seconds. If the request is still waiting or running after that, Nginx returns 504. A 502 usually means Nginx could not talk to FPM at all: the socket path is wrong, FPM is down, a worker was killed mid-request (for example by request_terminate_timeout or the kernel's out-of-memory killer), or the connection queue was full.

Step 1: read the status page

Enable the built-in status page in the pool with pm.status_path = /fpm-status and expose it to localhost only:

Nginx
location = /fpm-status {
    allow 127.0.0.1;
    deny all;
    include fastcgi_params;
    fastcgi_param SCRIPT_NAME /fpm-status;
    fastcgi_pass unix:/run/php/php8.5-fpm.sock;
}

During our test the status page reported these fields (trimmed):

Text
pool:                 demo
process manager:      dynamic
accepted conn:        5
listen queue:         0
max listen queue:     0
idle processes:       0
active processes:     2
total processes:      2
max active processes: 2
max children reached: 1
slow requests:        4
FieldWhat it tells you
max children reachedHow many times the pool hit pm.max_children since start. Any value above zero under normal traffic needs attention.
listen queue / max listen queueRequests waiting for a free worker now, and the worst case since start.
active processes / idle processesBusy versus spare workers. Zero idle at peak means no headroom.
slow requestsRequests that exceeded request_slowlog_timeout.

Add ?full to see each worker's state, request URI, duration (in microseconds), CPU and memory for its last request. Add ?json, ?xml, ?html or ?openmetrics to feed a monitoring system. The ping.path setting gives a simpler health check that answers pong, which we confirmed in our test.

Shell
curl -s 'http://127.0.0.1/fpm-status?json'
curl -s 'http://127.0.0.1/fpm-status?full'
Server rack with network switches and recorders connected by bundles of white and blue cables
Restrict the FPM status page to localhost or your monitoring network; it exposes request details.

Step 2: size pm.max_children from memory

Every worker is a separate process with its own memory. The safe maximum is the memory you can give PHP divided by the memory a typical worker uses. Measure the average under real traffic:

Shell
# Average resident memory per worker, in MB (Linux; adjust the process name)
ps --no-headers -o rss -C php-fpm8.5 | awk '{ sum += $1; n++ } END { printf "%.0f MB avg over %d processes\n", sum / n / 1024, n }'

A worked example: a server with 4 GB of RAM runs Nginx and PHP-FPM, while the database lives elsewhere. Reserve about 1 GB for the operating system, Nginx and OPcache shared memory, leaving roughly 3 GB. If workers average 80 MB, then 3072 / 80 is about 38, so set pm.max_children = 35 to keep a margin. If workers average 200 MB, the same server supports only about 15.

Raising pm.max_children beyond what memory allows trades a queue for swapping or the out-of-memory killer, which is worse. If the arithmetic gives too few workers, reduce per-request memory, move heavy work to a queue or add servers.

Step 3: choose the process manager mode

The pm directive is mandatory and accepts three values:

ModeBehaviourGood fit
staticAlways runs exactly pm.max_children workers.Dedicated servers with steady traffic and known memory.
dynamicStarts pm.start_servers and keeps idle workers between pm.min_spare_servers and pm.max_spare_servers, up to pm.max_children.Most production servers; the usual default.
ondemandStarts workers only when requests arrive and kills them after pm.process_idle_timeout (default 10s).Many low-traffic pools on one host, such as shared hosting.

In dynamic mode, pm.start_servers defaults to the midpoint of the two spare settings, and pm.max_spawn_rate (default 32) limits how many workers FPM creates at once during a burst.

Step 4: find what keeps workers busy

A pool that runs out of workers is usually a symptom of slow requests. A request that takes 10 seconds holds a worker ten times longer than one that takes one second. Turn on the slow log:

  • request_slowlog_timeout (default 0, off) dumps a PHP backtrace to the slowlog file for any request that runs longer than the value.
  • request_terminate_timeout (default 0, off) kills a worker whose request runs longer than the value. The manual suggests it for cases where max_execution_time does not stop a script, for example while it waits on external calls.

Writing the backtrace requires the FPM master to trace the worker. In our macOS test the slow requests were detected and counted, but tracing failed with an insufficient privileges error, so the slow log file stayed empty. On Linux containers you may hit the same limit if ptrace is restricted. Check the slow log file after enabling it, not just the counter.

Common causes found in slow logs are external HTTP calls without timeouts, unindexed database queries, sessions locked by long requests, and report generation or image processing done inside the web request.

Kitchen order keypad with Serve, Zoom, Next Order and Print buttons beneath a receipt printer
Slow requests hold a worker for their whole duration, so fixing them frees capacity fastest.

A balanced pool configuration

This is a reasonable starting point for the 4 GB example above. Adjust pm.max_children to your own measurement.

INI
; /etc/php/8.5/fpm/pool.d/www.conf  (path varies by distribution)
[www]
listen = /run/php/php8.5-fpm.sock

pm = dynamic
pm.max_children = 35
pm.start_servers = 8
pm.min_spare_servers = 4
pm.max_spare_servers = 12
pm.max_requests = 1000

pm.status_path = /fpm-status
ping.path = /ping

request_slowlog_timeout = 5s
slowlog = /var/log/php-fpm/$pool.slow.log
request_terminate_timeout = 60s

pm.max_requests = 1000 recycles each worker after a thousand requests, which the manual describes as a workaround for memory leaks in third-party libraries. The default of 0 never recycles workers.

Gotchas

  • Several pools share one machine. Add up pm.max_children across all pools before comparing with available memory.
  • Status page exposed publicly. It reveals request URIs and timings; restrict it to localhost or your monitoring network.
  • Timeouts that do not line up. If request_terminate_timeout is longer than Nginx's fastcgi_read_timeout, users get a 504 while the worker keeps running. Keep the FPM limit equal to or shorter than the proxy limit.
  • Restarting instead of reloading. Prefer a graceful reload after config changes so in-flight requests finish. The global process_control_timeout sets how long workers get to react to the master's signals.
  • Crash loops. The global emergency_restart_threshold and emergency_restart_interval restart FPM if too many workers die with SIGSEGV or SIGBUS in a short time.

What to do next

Enable the status page and slow log on one server today. Watch max children reached and slow requests for a few days of real traffic, measure worker memory, then set pm.max_children from the arithmetic rather than from a forum post. Fix the slowest endpoints in the slow log first: shorter requests free workers faster than any configuration change.

Frequently asked questions

  • What does 'server reached pm.max_children setting' mean?

    All worker processes in the pool were busy when a new request arrived, so the request had to wait. It is a capacity warning, usually caused by slow requests or a pool sized too small for the traffic.

  • How do I calculate pm.max_children?

    Divide the memory available to PHP-FPM by the average resident memory of one worker, then leave a margin. For example, 3 GB available and 80 MB per worker gives about 38; setting 35 leaves headroom.

  • Should I use pm static, dynamic or ondemand?

    dynamic suits most servers. static fits dedicated servers with steady load, and ondemand fits hosts running many small, mostly idle pools.

  • Why does Nginx return 502 with PHP-FPM?

    A 502 means Nginx could not get a valid response from FPM: FPM is down, the socket path or permissions are wrong, a worker was killed mid-request, or the connection queue was full. Check the FPM error log first.

  • How do I see which PHP requests are slow?

    Set request_slowlog_timeout and slowlog in the pool. FPM writes a PHP backtrace for each request that exceeds the timeout, provided the master process is allowed to trace workers.

  • What does pm.max_requests do?

    It recycles a worker after a set number of requests. The default 0 never recycles; a value such as 500 or 1000 limits the impact of memory leaks in extensions or libraries.

Advertise on CloudSoftZone

Your next customer could start right here.

Introduce your tool, product or business to readers exploring technology.

Advertise with us

Explore placements and request a private quote

Sources

  1. FPM configuration — PHP Manual
  2. FPM status page — PHP Manual
  3. Module ngx_http_fastcgi_module — nginx
  4. Supported Versions — The PHP Group

Written with substantial AI assistance. Facts checked against cited sources. Editorial policy.

Comments

No comments yet. Be the first to join the conversation.

Leave a comment

Every comment is read by a moderator before it appears. Your email address is never published; it is used only to contact you about this comment.

Never published. Used only to contact you about this comment.

Loading the security check…

Comments are moderated before publication.