pm.max_children and hope it goes away. Sometimes that is the right fix. Often it only hides a slow database, a stuck external API call, or a PHP worker that keeps crashing. Before you change any numbers, use the logs to confirm two things: PHP-FPM actually ran out of workers, and the web server's errors happened at the same moments customers saw the 502.
This checklist covers which lines to look for, in what order, and how to line up their timestamps with what was reported.
Why a full pool does not always mean an instant 502
pm.max_children caps how many can exist at once. When every worker is busy, new requests do not fail right away. They wait in the socket's listen queue until a worker frees up.
The web server returns an error only when that waiting breaks down. One way is a full queue, so the connection gets refused, which usually shows up as a 502. The other is a wait longer than the web server's read timeout, which usually shows up as a 504. So a max_children problem leaves a pattern rather than a single line: FPM reports it hit the limit, and the web server logs connection or timeout errors toward the FPM socket in the same window.
If you want the bigger picture on how pools get starved under heavy traffic and how to restructure them, Unraveling PHP-FPM Pool Starvation in High-Concurrency WordPress Hosting goes deeper. This article stays focused on proof.
Checks on the PHP-FPM side
-
Find the real FPM log first. The location comes from the
error_logdirective in the global FPM config, not the pool file, and it differs per distro, PHP version, and control panel. Open the config and read the path instead of guessing. Otherwise you can end up searching an empty file and wrongly conclude FPM is fine. -
Search for the max_children warning. The line you want reads like
WARNING: [pool www] server reached pm.max_children setting (N), consider raising it. The pool name in brackets matters on servers with one pool per site or per account, because it tells you which site saturated. FPM usually writes this once per saturation episode rather than once per waiting request. A single line can therefore stand for minutes of queued traffic, so don't judge severity by how many lines you see. -
Note the related warnings around it. With
pm = dynamicyou may also see a line that starts withseems busy (you may need to increase pm.start_servers, or pm.min/max_spare_servers). That means FPM was spawning workers quickly, which often comes right before hitting the cap. Lines likechild N exited on signalorexecution timed outpoint to a different story: workers being killed or crashing. That can produce 502s even when the pool never filled. -
Check the status page if one is enabled. When
pm.status_pathis configured, the status output includesmax children reached,listen queue, andmax listen queue. A risingmax children reachedcounter and a non-zeromax listen queueback up the log warning. Both counters reset when FPM restarts, so read them before anyone reloads the service.
Checks on the web server side
-
Read the web server error log, not just the access log. The access log tells you a 502 happened. The error log tells you why. In Nginx, look at upstream errors pointing at your FPM socket or port:
connect() to unix:... failed (11: Resource temporarily unavailable)usually means the socket's listen queue was full, which fits pool saturation.upstream timed out (110: Connection timed out) while reading response header from upstreammeans the request waited too long. Expect a 504 here, not a 502.connect() failed (111: Connection refused)orNo such file or directoryon the socket means FPM was down or the socket path was wrong. That is a different problem.recv() failed (104: Connection reset by peer)orupstream prematurely closed connectionusually means a worker died mid-request. Look for crash or kill lines in the FPM log at the same time.
-
On Apache with mod_proxy_fcgi, look for the proxy errors instead. Lines such as
AH01067: Failed to read FastCGI headerorAH01075: Error dispatching request toappear when the backend cannot answer. They are less specific than the Nginx messages, so the FPM-side warning carries more weight in your conclusion. On LiteSpeed, PHP often runs through lsphp rather than FPM, so the FPM log lines may not exist at all. Check what is actually serving PHP before spending time here. - Confirm the error points at the right upstream. Shared servers and multi-site VPSs often have several pools. An Nginx error that names one socket and an FPM warning that names a different pool are not the same incident, even if the times match.
Matching timestamps to what customers reported
-
Normalize timezones before comparing. The FPM log uses a format like
[20-Oct-2026 14:05:12], while the Nginx error log uses2026/10/20 14:05:12. Depending on configuration, they may not use the same timezone. Customer reports, uptime monitors, and CDN dashboards often show UTC or the user's local time. Write down the offset for each source before you line anything up. A mismatched hour can make an unrelated warning look like the cause. - Search in a narrow window around each report. Start with a few minutes on either side of the reported time, then widen if needed. Remember that logs rotate, so the incident may sit in a compressed older file. These are read-only searches. The paths are examples, so swap in the ones from your own config:
BASH
# PHP-FPM side: max_children warnings, including rotated .gz logs
zgrep -h 'max_children' /var/log/php-fpm/error.log*
# Nginx side: upstream errors during one example hour
zgrep -h 'upstream' /var/log/nginx/error.log* | grep '2026/10/20 14:'
# Access log: 502 and 504 responses in the same example hour
zgrep -h ' 50[24] ' /var/log/nginx/access.log* | grep '20/Oct/2026:14:'- Check the order of events, not just that they overlap. The FPM warning should appear at or just before the first burst of upstream errors, and the errors should taper off after traffic drops. If the 502s start well before any FPM warning, or keep going long after FPM recovered, the pool limit probably isn't the main cause.
-
Compare with the traffic at that time. Look at the request count in the access log for the same window. A spike in requests, a crawler, or a burst of
wp-login.phporxmlrpc.phphits explains a lot. A full pool with normal traffic is a different conclusion: each request is taking longer than it used to.
When the logs point somewhere else
request_slowlog_timeout plus slowlog) shows stack traces of the requests that stuck, which usually names the plugin or function.
Raising pm.max_children without checking memory has its own risk. Each worker uses RAM, and too many can push the server into swap or trigger the OOM killer, which turns occasional 502s into a full outage. A common rule of thumb is to base the limit on available memory divided by typical worker size, measured on your own server rather than copied from a guide.
In support, it helps to say this plainly to the site owner. 'The PHP pool filled up at these times' is a finding. 'Increase max_children' is only one possible fix, and the slowlog often shows a cheaper one.
In practice: find the FPM server reached pm.max_children line, find the matching upstream errors in the web server log for the same pool, line both up with the reported times after fixing timezones, and only then decide between more workers and faster requests. If any one of those three doesn't line up, keep looking before you change the config.