<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>PHP-FPM Operations Guides on Netdata</title><link>https://www.netdata.cloud/guides/php-fpm/</link><description>Recent content in PHP-FPM Operations Guides on Netdata</description><generator>Hugo</generator><language>en-us</language><atom:link href="https://www.netdata.cloud/guides/php-fpm/index.xml" rel="self" type="application/rss+xml"/><item><title>How PHP-FPM actually works in production: a mental model for operators</title><link>https://www.netdata.cloud/guides/php-fpm/php-fpm-how-it-works-in-production/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.netdata.cloud/guides/php-fpm/php-fpm-how-it-works-in-production/</guid><description>&lt;p>PHP-FPM looks like a single service from the outside: a socket, a process, a stream of responses. Inside, it is a process-based concurrency system with a small number of moving parts, each of which fails in a specific, predictable way. Most production incidents trace back to a misunderstanding of one of those parts.&lt;/p>
&lt;p>The single most important fact about PHP-FPM: each worker process handles exactly one request at a time. There is no in-process concurrency. Your maximum concurrent request capacity equals your number of active worker processes. Everything else in the system exists to feed, queue, recycle, or protect those workers.&lt;/p></description></item><item><title>PHP-FPM "child N exited on signal 11 (SIGSEGV)": worker segfaults</title><link>https://www.netdata.cloud/guides/php-fpm/php-fpm-child-exited-on-signal-sigsegv/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.netdata.cloud/guides/php-fpm/php-fpm-child-exited-on-signal-sigsegv/</guid><description>&lt;p>The log line is unambiguous:&lt;/p>
&lt;pre tabindex="0">&lt;code>WARNING: [pool www] child 12345 exited on signal 11 (SIGSEGV) after 3.4 seconds from start
&lt;/code>&lt;/pre>&lt;p>A worker hit a memory protection fault and the kernel killed it. FPM forks a replacement, but the in-flight request is gone: the client sees a truncated response or a 502, and in a tight loop a whole pool can die to a crash storm. A SIGSEGV is almost never a bug in your PHP code. PHP-level fatal errors exit cleanly with a 500 and a stack trace. Signal 11 means native code dereferenced a bad pointer, so the fault lives in a C extension, OPcache, the JIT, or the PHP runtime itself.&lt;/p></description></item><item><title>PHP-FPM "connect() failed (111: Connection refused) while connecting to upstream"</title><link>https://www.netdata.cloud/guides/php-fpm/php-fpm-connect-failed-connection-refused/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.netdata.cloud/guides/php-fpm/php-fpm-connect-failed-connection-refused/</guid><description>&lt;p>The error string in nginx &lt;code>error.log&lt;/code>:&lt;/p>
&lt;pre tabindex="0">&lt;code>*1234 connect() failed (111: Connection refused) while connecting to upstream, client: 10.0.0.5, server: example.com, request: &amp;#34;GET / HTTP/1.1&amp;#34;, upstream: &amp;#34;fastcgi://unix:/run/php/php8.2-fpm.sock:&amp;#34;
&lt;/code>&lt;/pre>&lt;p>&lt;code>errno 111&lt;/code> is &lt;code>ECONNREFUSED&lt;/code>. nginx tried to open a connection to the FastCGI backend and the kernel on the PHP-FPM side rejected it before any FastCGI bytes were exchanged. Users see HTTP 502 Bad Gateway in batches.&lt;/p>
&lt;p>Four failure modes present identically from nginx&amp;rsquo;s seat: the PHP-FPM master is not running; the configured socket path or TCP address does not match what nginx is dialing; the kernel dropped the connection because the listen backlog is full; or the socket exists and paths match, but something environmental prevents nginx from reaching it (private tmp namespace, container network boundary, IPv6/IPv4 mismatch, socket permissions). One read-only check on the FPM host disambiguates all four: is anything actually listening on the address nginx is trying to reach?&lt;/p></description></item><item><title>PHP-FPM "connect() to unix:/... failed (11: Resource temporarily unavailable)"</title><link>https://www.netdata.cloud/guides/php-fpm/php-fpm-resource-temporarily-unavailable/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.netdata.cloud/guides/php-fpm/php-fpm-resource-temporarily-unavailable/</guid><description>&lt;p>The nginx error &lt;code>connect() to unix:/run/php/php-fpm.sock failed (11: Resource temporarily unavailable) while connecting to upstream&lt;/code> looks like a connection problem. It is not. Errno 11 is &lt;code>EAGAIN&lt;/code>. nginx uses non-blocking sockets, and in this context the kernel&amp;rsquo;s accept queue for the PHP-FPM Unix socket is full. The kernel had nowhere to buffer the new FastCGI connection, so it refused the &lt;code>connect(2)&lt;/code> immediately.&lt;/p>
&lt;p>This is a saturation error, not a connection error. PHP-FPM is alive, the socket file exists, the master is listening, and &lt;code>systemctl status php-fpm&lt;/code> will look healthy. The pool is out of workers, and the backlog buffer between nginx and PHP-FPM has filled.&lt;/p></description></item><item><title>PHP-FPM "connect() to unix:/... failed (13: Permission denied)": socket ownership</title><link>https://www.netdata.cloud/guides/php-fpm/php-fpm-socket-permission-denied/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.netdata.cloud/guides/php-fpm/php-fpm-socket-permission-denied/</guid><description>&lt;p>The nginx error &lt;code>connect() to unix:/var/run/php/php-fpm.sock failed (13: Permission denied)&lt;/code> is a Unix socket ownership problem between the web server and PHP-FPM. Every PHP request returns 502 while static assets serve fine. The PHP-FPM master is up, the socket file exists, workers are idle, and yet no traffic flows.&lt;/p>
&lt;p>The &lt;code>(13: Permission denied)&lt;/code> suffix is errno 13, &lt;code>EACCES&lt;/code>. nginx calls &lt;code>connect(2)&lt;/code> on the socket path and the kernel rejects it before any FastCGI bytes are exchanged. The cause is on the socket inode or on a parent directory, not in the FastCGI protocol, the application, or pool sizing.&lt;/p></description></item><item><title>PHP-FPM "server reached pm.max_children setting (N), consider raising it"</title><link>https://www.netdata.cloud/guides/php-fpm/php-fpm-max-children-reached/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.netdata.cloud/guides/php-fpm/php-fpm-max-children-reached/</guid><description>&lt;p>The warning appears in your PHP-FPM error log verbatim:&lt;/p>
&lt;pre tabindex="0">&lt;code>WARNING: [pool www] server reached pm.max_children setting (5), consider raising it
&lt;/code>&lt;/pre>&lt;p>The pool tried to fork another worker and hit the configured ceiling. Each occurrence is a request that had to wait for a worker to free up instead of being served immediately. The line is emitted at warning level. &lt;!-- TODO: verify "cannot be suppressed through configuration as of PHP 8.x" - log_level=error would mask it at the cost of all warnings -->&lt;/p></description></item><item><title>PHP-FPM "Too many open files": file descriptor exhaustion</title><link>https://www.netdata.cloud/guides/php-fpm/php-fpm-too-many-open-files/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.netdata.cloud/guides/php-fpm/php-fpm-too-many-open-files/</guid><description>&lt;p>The signature line in the PHP-FPM error log is:&lt;/p>
&lt;pre tabindex="0">&lt;code>ERROR: failed to prepare the stderr pipe: Too many open files (24)
&lt;/code>&lt;/pre>&lt;p>The &lt;code>(24)&lt;/code> is the C errno &lt;code>EMFILE&lt;/code>: the calling process has hit its per-process open file descriptor limit. By the time this line appears, the master has already failed to fork a usable child, and application code has probably been failing intermittently for minutes beforehand with random connection refusals, unreadable files, and sessions that refuse to start.&lt;/p></description></item><item><title>PHP-FPM "upstream prematurely closed connection while reading response header"</title><link>https://www.netdata.cloud/guides/php-fpm/php-fpm-upstream-prematurely-closed/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.netdata.cloud/guides/php-fpm/php-fpm-upstream-prematurely-closed/</guid><description>&lt;p>nginx logs this error at &lt;code>[error]&lt;/code> level when the FastCGI connection to PHP-FPM closes before response headers arrive. The client gets a 502 Bad Gateway. Unlike &lt;code>connect() failed (111: Connection refused)&lt;/code> (no worker available) or &lt;code>upstream timed out (110: Connection timed out)&lt;/code> (worker too slow), this error means the connection was established, the worker began executing PHP, and the worker vanished before delivering a response.&lt;/p>
&lt;p>The worker process died mid-request. The PHP-FPM master reaps the dead child via SIGCHLD and forks a replacement, but the in-flight request is lost. nginx sees the socket close while still waiting on headers and logs &amp;ldquo;upstream prematurely closed connection while reading response header from upstream&amp;rdquo;.&lt;/p></description></item><item><title>PHP-FPM 502 Bad Gateway: the web server cannot reach the pool</title><link>https://www.netdata.cloud/guides/php-fpm/php-fpm-502-bad-gateway/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.netdata.cloud/guides/php-fpm/php-fpm-502-bad-gateway/</guid><description>&lt;p>A 502 Bad Gateway means the upstream PHP-FPM pool never returned a valid FastCGI response. nginx, Apache, or Caddy tried to open a connection to the FPM socket and either the connection never established, was refused by the kernel, or was torn down mid-request.&lt;/p>
&lt;p>The fastest triage move is to test both ends of the FastCGI connection: probe the FPM ping endpoint, then read the web server error log for the exact upstream message. Those two signals narrow the cause in seconds.&lt;/p></description></item><item><title>PHP-FPM 504 Gateway Timeout: requests accepted but never finishing in time</title><link>https://www.netdata.cloud/guides/php-fpm/php-fpm-504-gateway-timeout/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.netdata.cloud/guides/php-fpm/php-fpm-504-gateway-timeout/</guid><description>&lt;p>A 504 from nginx means the FastCGI connection was established, the request was handed to a worker, and the worker never produced a response before nginx gave up. This is a different failure from a 502, where nginx could not connect at all (socket missing, backlog full, or master dead). The FastCGI channel was healthy. The work was not.&lt;/p>
&lt;p>The default &lt;code>fastcgi_read_timeout&lt;/code> in nginx is 60 seconds, measured between successive read operations on the socket, not for the response as a whole. Once it fires, nginx closes its side and returns 504. PHP-FPM has no idea this happened. The worker keeps executing, builds the full response, and writes it to a socket nobody is reading. This is the phantom worker problem, and it is the central operational trap of the 504 symptom.&lt;/p></description></item><item><title>PHP-FPM active processes near max_children: reading pool utilization</title><link>https://www.netdata.cloud/guides/php-fpm/php-fpm-active-processes-high/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.netdata.cloud/guides/php-fpm/php-fpm-active-processes-high/</guid><description>&lt;p>The ratio of &lt;code>active processes&lt;/code> to &lt;code>pm.max_children&lt;/code> is the most important saturation signal PHP-FPM exposes. Each worker handles one request at a time, so the active worker count is your current concurrent request load. Divided by the configured ceiling, it behaves like a capacity gauge: near 1.0 means no headroom; sustained near 1.0 means one slow query away from queuing.&lt;/p>
&lt;p>This is a reading guide for that ratio: what the numerator and denominator actually mean, how to distinguish a normal burst from a sustained problem, how to use the high-water mark (&lt;code>max active processes&lt;/code>) for capacity planning, and which correlated signals disambiguate &amp;ldquo;busy&amp;rdquo;, &amp;ldquo;saturated&amp;rdquo;, and &amp;ldquo;broken&amp;rdquo;. It is not a tuning guide; see the related guides at the end for that.&lt;/p></description></item><item><title>PHP-FPM and nginx timeout mismatch: phantom workers and confusing 504s</title><link>https://www.netdata.cloud/guides/php-fpm/php-fpm-nginx-timeout-mismatch/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.netdata.cloud/guides/php-fpm/php-fpm-nginx-timeout-mismatch/</guid><description>&lt;p>Users see 504 Gateway Timeout. You check PHP-FPM and the pool is not saturated, or it is saturated but the active workers are running requests that should have finished minutes ago. The nginx error log shows upstream timeouts. The FPM error log shows nothing unusual.&lt;/p>
&lt;p>nginx and PHP-FPM each have their own notion of how long a request may run. When those notions disagree, you get two failure modes: phantom workers (nginx gave up, FPM kept going) and confusing 502/504 patterns (FPM killed a worker nginx was still waiting on). The configuration is incoherent across the request path.&lt;/p></description></item><item><title>PHP-FPM crash loop and fork storm: workers dying faster than they serve</title><link>https://www.netdata.cloud/guides/php-fpm/php-fpm-crash-loop/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.netdata.cloud/guides/php-fpm/php-fpm-crash-loop/</guid><description>&lt;p>Workers start, accept a request, hit a crash-inducing code path, die with SIGSEGV or SIGBUS, and get respawned by the master. The replacement picks up another request from the same traffic pattern, hits the same code, and dies again. The pool spends more time forking and dying than serving. Each fork costs the kernel time to copy page tables and costs PHP time to initialize the runtime. Users see intermittent 502s while &lt;code>total processes&lt;/code> oscillates and the &lt;code>accepted conn&lt;/code> rate collapses.&lt;/p></description></item><item><title>PHP-FPM dynamic mode scaling lag: why the pool cannot keep up with bursts</title><link>https://www.netdata.cloud/guides/php-fpm/php-fpm-dynamic-mode-scaling-lag/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.netdata.cloud/guides/php-fpm/php-fpm-dynamic-mode-scaling-lag/</guid><description>&lt;p>During a traffic burst, your PHP-FPM pool reports total processes well below &lt;code>pm.max_children&lt;/code>, yet the listen queue fills and latency spikes. The status page shows idle workers hitting zero, then climbing back a few seconds later as new workers come online. By then, requests have already queued and some users have already seen 502s or 504s.&lt;/p>
&lt;p>This is dynamic mode scaling lag. The dynamic process manager is reactive, not predictive. It checks idle-worker counts on a timer and forks replacement workers after the deficit is already visible. When a burst arrives faster than the check-and-fork cycle can respond, the idle pool drains to zero and the listen backlog absorbs the overflow until new workers are ready.&lt;/p></description></item><item><title>PHP-FPM emergency restart: "failed processes threshold reached, initiating reload"</title><link>https://www.netdata.cloud/guides/php-fpm/php-fpm-emergency-restart/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.netdata.cloud/guides/php-fpm/php-fpm-emergency-restart/</guid><description>&lt;p>You see a NOTICE line in the PHP-FPM error log:&lt;/p>
&lt;pre tabindex="0">&lt;code>NOTICE: failed processes threshold (N in M sec) is reached, initiating reload
&lt;/code>&lt;/pre>&lt;p>The master has watched enough children die from SIGSEGV or SIGBUS inside a configured interval and is giving up on soft recovery. It is about to &lt;code>execvp()&lt;/code> itself: every pool is recycled, the OPcache shared memory segment is destroyed, and for a few seconds no PHP request can be served. When traffic returns, every worker pays a compilation penalty while the cache warms.&lt;/p></description></item><item><title>PHP-FPM executing PHP in upload directories: webshell RCE detection</title><link>https://www.netdata.cloud/guides/php-fpm/php-fpm-php-execution-restricted-paths/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.netdata.cloud/guides/php-fpm/php-fpm-php-execution-restricted-paths/</guid><description>&lt;p>A correctly configured web server and PHP-FPM stack never returns HTTP 200 for a &lt;code>.php&lt;/code> file inside an upload, temporary, or media directory. If it does, that file executed as PHP code. In production, that file is a webshell, and the host is compromised.&lt;/p>
&lt;p>This is a binary condition. The PHP-FPM playbook assigns it PAGE severity because a healthy server cannot produce this signal. If your access logs show a 200 response for a PHP file in a restricted path, start incident response immediately: isolate the host, preserve forensic data, and assume the attacker has achieved code execution within the PHP-FPM worker&amp;rsquo;s user context.&lt;/p></description></item><item><title>PHP-FPM graceful reload: the brief no-worker window on SIGUSR2</title><link>https://www.netdata.cloud/guides/php-fpm/php-fpm-graceful-reload-window/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.netdata.cloud/guides/php-fpm/php-fpm-graceful-reload-window/</guid><description>&lt;p>A PHP-FPM graceful reload (&lt;code>kill -USR2 &amp;lt;master_pid&amp;gt;&lt;/code>, &lt;code>systemctl reload php8.3-fpm&lt;/code>) is not graceful in the nginx sense. There is no overlap window where new workers handle fresh traffic while old workers drain. The old pool is torn down first, the master re-execs itself, and only then does it fork replacement workers. For a measurable interval zero workers are serving requests.&lt;/p>
&lt;p>If you page on a 502 spike, a listen-queue jump, or a failed ping probe during a deploy, you have probably already met this window. The pattern is narrow, predictable, and bounded by &lt;code>process_control_timeout&lt;/code>. Treating it as an incident is one of the most common PHP-FPM false alarms.&lt;/p></description></item><item><title>PHP-FPM idle processes at zero: no burst headroom left</title><link>https://www.netdata.cloud/guides/php-fpm/php-fpm-idle-processes-zero/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.netdata.cloud/guides/php-fpm/php-fpm-idle-processes-zero/</guid><description>&lt;p>The PHP-FPM status page reports &lt;code>idle processes&lt;/code> at or near zero. In &lt;code>static&lt;/code> and &lt;code>dynamic&lt;/code> modes this is the leading indicator that the pool is about to start queuing. The next request that arrives has no worker ready to accept it, so it lands in the kernel-managed socket backlog. If demand keeps rising, the backlog fills and the web server starts returning 502s.&lt;/p>
&lt;p>Zero idle is not itself a failure but the warning that one burst or one slow dependency will turn into visible user impact within seconds. Once the listen queue is non-zero, users are already waiting on extra latency. Once the backlog overflows, you are dropping connections that PHP-FPM cannot see.&lt;/p></description></item><item><title>PHP-FPM in containers: cgroup limits and the silent OOM kill</title><link>https://www.netdata.cloud/guides/php-fpm/php-fpm-cgroup-oom-container/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.netdata.cloud/guides/php-fpm/php-fpm-cgroup-oom-container/</guid><description>&lt;p>The container restarts, or the PHP-FPM error log fills with &lt;code>child N exited on signal 9 (SIGKILL)&lt;/code> entries, but &lt;code>dmesg&lt;/code> shows no global OOM event. The host has plenty of free RAM. The PHP &lt;code>memory_limit&lt;/code> is set well below the kill threshold. None of the obvious explanations fit.&lt;/p>
&lt;p>PHP-FPM has no awareness of cgroup memory limits. Workers allocate against the PHP heap (capped by &lt;code>memory_limit&lt;/code>), against extension memory (Imagick, libxml, glibc arenas), and against page cache for mmap&amp;rsquo;d files. None of those allocations consult &lt;code>memory.max&lt;/code> in the cgroup. When the cgroup as a whole crosses its limit, the kernel cgroup OOM killer fires a SIGKILL at a process inside the cgroup. Whether the victim is a single worker or the master process determines whether you see a silent kill or a container restart.&lt;/p></description></item><item><title>PHP-FPM listen backlog overflow: the kernel silently dropping connections</title><link>https://www.netdata.cloud/guides/php-fpm/php-fpm-listen-backlog-overflow/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.netdata.cloud/guides/php-fpm/php-fpm-listen-backlog-overflow/</guid><description>&lt;p>The signature: a PHP-FPM pool where every status page field looks healthy yet the web server sporadically returns 502s. Active processes are not pinned at &lt;code>pm.max_children&lt;/code>, the &lt;code>listen queue&lt;/code> field reads zero, the slow log is quiet, opcache hit rate is normal. The 502s cluster into short bursts that may or may not line up with known load events.&lt;/p>
&lt;p>The kernel is dropping incoming FastCGI connections because the listen backlog has filled. PHP-FPM has no visibility into these drops. Once the backlog is at capacity, the kernel refuses to enqueue new connections, and the status page cannot report a queue depth above the configured &lt;code>listen.backlog&lt;/code> value because those connections never enter a queue FPM could observe. The failure happens entirely below PHP-FPM&amp;rsquo;s instrumentation layer.&lt;/p></description></item><item><title>PHP-FPM listen queue growing: the earliest signal of saturation</title><link>https://www.netdata.cloud/guides/php-fpm/php-fpm-listen-queue-growing/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.netdata.cloud/guides/php-fpm/php-fpm-listen-queue-growing/</guid><description>&lt;p>The listen queue is the kernel-managed socket backlog between the web server and PHP-FPM workers. When it carries a sustained non-zero depth, requests are arriving faster than workers can drain them. By the time the web server logs a 502, the queue has already overflowed and the kernel has started dropping connections.&lt;/p>
&lt;p>The signal is easy to miss for two reasons. First, the FPM status page field is a point-in-time snapshot: a 10-second poller can report 0 even while bursts spill into the queue and drain between polls. Second, on Unix domain sockets (the most common production transport), the status page always reports 0 for all three queue fields due to a long-standing PHP bug. You have to read the kernel&amp;rsquo;s Recv-Q via &lt;code>ss&lt;/code>, or poll at 1-second intervals, to see the real depth.&lt;/p></description></item><item><title>PHP-FPM listen.backlog tuning: somaxconn and version defaults</title><link>https://www.netdata.cloud/guides/php-fpm/php-fpm-listen-backlog-tuning/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.netdata.cloud/guides/php-fpm/php-fpm-listen-backlog-tuning/</guid><description>&lt;p>When the PHP-FPM pool saturates, the socket backlog is the buffer between &amp;ldquo;requests are waiting&amp;rdquo; and &amp;ldquo;requests are dropped.&amp;rdquo; Operators who hit 502 storms often reach for &lt;code>listen.backlog&lt;/code> first, bump it to a large number, reload PHP-FPM, and see no change. The reason is almost always one of two things: the kernel silently clamped the request to &lt;code>net.core.somaxconn&lt;/code>, or the backlog was never the bottleneck.&lt;/p>
&lt;p>The short version: a larger backlog only lengthens the queue. It buys a few seconds of burst absorption. It never fixes a worker pool that cannot keep up.&lt;/p></description></item><item><title>PHP-FPM memory leak: per-worker RSS climbing until the box runs out</title><link>https://www.netdata.cloud/guides/php-fpm/php-fpm-memory-leak/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.netdata.cloud/guides/php-fpm/php-fpm-memory-leak/</guid><description>&lt;p>PHP-FPM workers are slowly bloating. RSS climbs hour by hour, the box runs out of RAM, the OOM killer shoots workers (or the master), and a restart makes everything look fine again. Hours or days later, the cycle repeats.&lt;/p>
&lt;p>This is the slow-burn memory leak pattern, and it is almost always enabled by a single configuration value: &lt;code>pm.max_requests = 0&lt;/code>. With worker recycling disabled, every byte a worker fails to release accumulates indefinitely. The leak itself may live in your application code, in a C extension, or in the PHP runtime. The diagnosis is not the same as the fix.&lt;/p></description></item><item><title>PHP-FPM memory_limit vs worker RSS: why workers exceed the limit you set</title><link>https://www.netdata.cloud/guides/php-fpm/php-fpm-memory-limit-vs-rss/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.netdata.cloud/guides/php-fpm/php-fpm-memory-limit-vs-rss/</guid><description>&lt;p>You set &lt;code>memory_limit = 256M&lt;/code> in php.ini. You check &lt;code>ps&lt;/code> and see PHP-FPM workers sitting at 350 MB, 400 MB, sometimes more. No PHP error fired. No &amp;ldquo;Allowed memory size exhausted&amp;rdquo; in the logs. The workers did not crash. They are just bigger than the limit you set.&lt;/p>
&lt;p>This is not a bug, and usually not a leak. It is the expected result of how PHP&amp;rsquo;s memory manager is scoped, which most operators learn only after being paged at 3 a.m. for an OOM kill they cannot explain.&lt;/p></description></item><item><title>PHP-FPM monitoring checklist: the signals every production pool needs</title><link>https://www.netdata.cloud/guides/php-fpm/php-fpm-monitoring-checklist/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.netdata.cloud/guides/php-fpm/php-fpm-monitoring-checklist/</guid><description>&lt;p>PHP-FPM is a process-based concurrency model. Each worker handles exactly one request at a time, so the worker pool is the binding constraint. When all workers are occupied, new requests queue in the socket backlog. Once that fills, the kernel drops connections silently. Every PHP-FPM incident is a story about worker capacity, worker health, or what workers are blocked on.&lt;/p>
&lt;p>This checklist organizes production signals into four maturity levels: survival, operational, mature, and expert. Use it as a gap audit, or as a triage guide during incidents when workers are exhausted or the site returns 502s.&lt;/p></description></item><item><title>PHP-FPM monitoring maturity model: from survival to expert</title><link>https://www.netdata.cloud/guides/php-fpm/php-fpm-monitoring-maturity-model/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.netdata.cloud/guides/php-fpm/php-fpm-monitoring-maturity-model/</guid><description>&lt;p>PHP-FPM uses a master-worker process model where each worker handles exactly one request at a time. Concurrent request capacity equals the active worker count, and the path from healthy to users-seeing-502 runs through worker slots, the socket backlog, and kernel connection drops. This is a four-level reference, from minimum liveness checks to the expert signals that explain phantom workers and cgroup OOM kills. Use it as an audit: read down the levels, mark which signals you already collect, and fill the first gap. Levels are cumulative: Level 2 assumes Level 1, Level 3 assumes Level 2.&lt;/p></description></item><item><title>PHP-FPM ondemand cold start: fork and warmup latency on the first request</title><link>https://www.netdata.cloud/guides/php-fpm/php-fpm-ondemand-cold-start/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.netdata.cloud/guides/php-fpm/php-fpm-ondemand-cold-start/</guid><description>&lt;p>PHP-FPM&amp;rsquo;s &lt;code>ondemand&lt;/code> process manager exists to reclaim memory when a pool is doing nothing. The tradeoff is paid in latency on the first request after an idle period: the master must fork a worker, that worker must handle its first request, and if OPcache has no compiled bytecode for the requested scripts, PHP must parse and compile them from disk. On a warm &lt;code>dynamic&lt;/code> or &lt;code>static&lt;/code> pool, none of that happens. On an &lt;code>ondemand&lt;/code> pool that has been idle, all of it happens on the critical path of a single request.&lt;/p></description></item><item><title>PHP-FPM OPcache cold start: the CPU stampede after a restart or deploy</title><link>https://www.netdata.cloud/guides/php-fpm/php-fpm-opcache-cold-start/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.netdata.cloud/guides/php-fpm/php-fpm-opcache-cold-start/</guid><description>&lt;p>After a PHP-FPM restart, deploy, or graceful reload, CPU climbs, latency rises, and throughput drops for the first few minutes. The OPcache shared memory segment is empty, so every worker compiles PHP source from disk before it can execute. Under enough traffic, those slow compiles tie up workers long enough to cascade into pool exhaustion, listen queue growth, and 502 or 504 errors at the edge.&lt;/p>
&lt;p>This is expected behavior after any restart. It becomes an incident when a deploy hits every instance at once, when traffic is high during the window, or when monitoring fires on the CPU spike before the cache has had time to warm.&lt;/p></description></item><item><title>PHP-FPM OPcache hit rate below 99%: silent CPU and latency tax</title><link>https://www.netdata.cloud/guides/php-fpm/php-fpm-opcache-hit-rate-low/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.netdata.cloud/guides/php-fpm/php-fpm-opcache-hit-rate-low/</guid><description>&lt;p>A PHP-FPM pool with a healthy OPcache sits above 99% hit rate after warmup. Every request that misses recompiles PHP source into bytecode: parse, compile, optimize, and store. That work costs CPU on the worker handling the request and adds latency. When the cache is full and cannot admit new scripts, every worker handling those uncached scripts pays the compile tax on every request.&lt;/p>
&lt;p>OPcache hit rate is easy to misread. It is cumulative since the shared memory segment was last allocated, so a long history of healthy hits can paper over a live problem. A pool that served ten million hits then started missing every other request minutes ago will still report a hit rate of 99.99%. Track the miss rate live, not the cumulative percentage.&lt;/p></description></item><item><title>PHP-FPM OPcache out of memory: oom_restarts and the recompile cliff</title><link>https://www.netdata.cloud/guides/php-fpm/php-fpm-opcache-memory-full/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.netdata.cloud/guides/php-fpm/php-fpm-opcache-memory-full/</guid><description>&lt;p>Hours or days of normal operation. Then CPU spikes across every PHP-FPM worker at once, request latency jumps uniformly, and throughput drops. When you check OPcache status, &lt;code>oom_restarts&lt;/code> has incremented and &lt;code>free_memory&lt;/code> is near zero. The shared memory segment filled, OPcache force-cleared the entire cache, and every worker is now recompiling PHP from disk simultaneously.&lt;/p>
&lt;p>This is the recompile cliff. OPcache has no LRU eviction. When it runs out of space it either restarts (clearing everything) or silently stops caching new scripts.&lt;/p></description></item><item><title>PHP-FPM OPcache thrashing: a full cache recompiling PHP on every request</title><link>https://www.netdata.cloud/guides/php-fpm/php-fpm-opcache-thrashing/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.netdata.cloud/guides/php-fpm/php-fpm-opcache-thrashing/</guid><description>&lt;p>Every PHP request is suddenly slow. Latency is up across every endpoint, not just one. CPU is pinned across every worker, not a subset. The PHP-FPM status page shows workers running, the listen queue may be empty, and &lt;code>pm.max_children&lt;/code> is not the binding constraint. A graceful reload helps for a minute, then the slowness returns. You are probably inside an OPcache thrash.&lt;/p>
&lt;p>OPcache is the shared memory segment every worker in a PHP-FPM pool reads precompiled bytecode from. When the segment fills, OPcache evicts scripts still in use, recompiles them on the next request, and evicts other scripts to make room. The result is a continuous evict/compile cycle: each request landing on an uncached script pays the full parse and compile cost inside the worker.&lt;/p></description></item><item><title>PHP-FPM OPcache wasted memory: deploy fragmentation and when to reset</title><link>https://www.netdata.cloud/guides/php-fpm/php-fpm-opcache-wasted-memory/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.netdata.cloud/guides/php-fpm/php-fpm-opcache-wasted-memory/</guid><description>&lt;p>OPcache&amp;rsquo;s &lt;code>wasted_memory&lt;/code> counter grows on every deploy and never goes down on its own. When you ship new PHP files without clearing the cache, the old compiled bytecode stays in the shared memory segment alongside the new bytecode, occupying space that cannot be reclaimed until a full reset. Over a day of frequent deploys, this fragmentation starves the cache of room for hot scripts.&lt;/p>
&lt;p>The &lt;code>opcache.max_wasted_percentage&lt;/code> directive sounds like an automatic garbage collector, but the restart it gates only fires when a second condition is met, and that condition is almost never reached in practice. Hit rate drifts down, CPU drifts up, and the only durable fix is a reset that itself carries a thundering-herd cost.&lt;/p></description></item><item><title>PHP-FPM opcache.validate_timestamps: the development setting that costs production</title><link>https://www.netdata.cloud/guides/php-fpm/php-fpm-opcache-validate-timestamps/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.netdata.cloud/guides/php-fpm/php-fpm-opcache-validate-timestamps/</guid><description>&lt;p>The default &lt;code>opcache.validate_timestamps = 1&lt;/code> is convenient for development: edit a file, refresh the browser, see the change immediately. In production, that same convenience becomes a per-request filesystem tax and, with frequent deploys, a silent source of OPcache fragmentation.&lt;/p>
&lt;p>The trade-off is not subtle. Leave &lt;code>validate_timestamps&lt;/code> enabled and PHP stats source files on every request (or at the interval set by &lt;code>revalidate_freq&lt;/code>), burning syscalls and CPU. Disable it and PHP serves cached bytecode forever, which means a deploy that does not explicitly clear OPcache will serve stale code to every user until someone intervenes. Neither option is free. The question is which cost you can control.&lt;/p></description></item><item><title>PHP-FPM open_basedir and disable_functions: sandboxing worker pools</title><link>https://www.netdata.cloud/guides/php-fpm/php-fpm-open-basedir-disable-functions/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.netdata.cloud/guides/php-fpm/php-fpm-open-basedir-disable-functions/</guid><description>&lt;p>PHP-FPM runs each worker as an OS user. Whatever that user can read or execute, the PHP code inside the worker can read or execute. On a host running more than one application, or on any host where a compromise is plausible, that is a wide blast radius. A single uploaded webshell in one pool can read &lt;code>/etc/passwd&lt;/code>, walk into adjacent application directories for credentials, or shell out to enumerate the network.&lt;/p></description></item><item><title>PHP-FPM phpinfo() exposed in production: mapping the attack surface</title><link>https://www.netdata.cloud/guides/php-fpm/php-fpm-phpinfo-exposed/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.netdata.cloud/guides/php-fpm/php-fpm-phpinfo-exposed/</guid><description>&lt;p>A reachable &lt;code>phpinfo()&lt;/code> page in production is not a direct code execution vulnerability, which is why teams often deprioritize it. But the output is a reconnaissance tool handed to an attacker for free: PHP version, every loaded extension, absolute file paths, environment variables (which frequently contain database passwords, API keys, and S3 credentials in containerized deployments), database connection settings, and internal network topology.&lt;/p>
&lt;p>Automated scanners probe for &lt;code>info.php&lt;/code>, &lt;code>phpinfo.php&lt;/code>, &lt;code>test.php&lt;/code>, &lt;code>debug.php&lt;/code>, and similar filenames continuously. If any return a 200 with the full PHP configuration page, the attacker has a detailed map of your runtime. From there, version-specific CVEs, extension-specific exploits, and credential reuse attacks become targeted rather than speculative.&lt;/p></description></item><item><title>PHP-FPM pm.max_requests: worker recycling as the memory-leak safety net</title><link>https://www.netdata.cloud/guides/php-fpm/php-fpm-max-requests-recycling/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.netdata.cloud/guides/php-fpm/php-fpm-max-requests-recycling/</guid><description>&lt;p>PHP-FPM runs fine for hours or days, then per-worker RSS slowly climbs, the box runs low on RAM, and the OOM killer shoots workers or the master. Restarting PHP-FPM fixes it immediately, and the cycle repeats. The root cause is usually a memory leak in application code, an extension, or allocator fragmentation. The reason it becomes an incident instead of a slow nuisance is almost always the same missing safety net: &lt;code>pm.max_requests = 0&lt;/code>.&lt;/p></description></item><item><title>PHP-FPM pool running as root or a shared user: privilege and isolation risk</title><link>https://www.netdata.cloud/guides/php-fpm/php-fpm-pool-running-as-root/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.netdata.cloud/guides/php-fpm/php-fpm-pool-running-as-root/</guid><description>&lt;p>A single line of PHP-FPM configuration decides how far an attacker travels after a remote code execution bug in your application. The pool&amp;rsquo;s &lt;code>user&lt;/code> and &lt;code>group&lt;/code> directives set the identity of every worker that runs your code. That identity is the blast radius of a successful exploit.&lt;/p>
&lt;p>The detection commands below are read-only. The lockdown changes require a graceful reload (&lt;code>SIGUSR2&lt;/code> or &lt;code>systemctl reload php-fpm&lt;/code>), which re-reads the pool configuration and cycles workers while in-flight requests finish. Plan each change as a deploy.&lt;/p></description></item><item><title>PHP-FPM request duration climbing: spotting stuck and outlier workers</title><link>https://www.netdata.cloud/guides/php-fpm/php-fpm-request-duration-high/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.netdata.cloud/guides/php-fpm/php-fpm-request-duration-high/</guid><description>&lt;p>When you pull &lt;code>?full&lt;/code> from the PHP-FPM status page during an incident and the &lt;code>request duration&lt;/code> column looks alarming, verify your units first. The field is microseconds, not seconds or milliseconds, and it is the single most misread value in the FPM status surface. A worker showing &lt;code>4500000&lt;/code> has been running for 4.5 seconds, not 4.5 million seconds. Convert before you escalate.&lt;/p>
&lt;p>The second trap is interpretation. &lt;code>request duration&lt;/code> has two meanings depending on worker state. For Running workers, it is the elapsed time of the current in-flight request. For Idle workers, it is the duration of the last completed request that worker handled. A pool full of idle workers showing high durations is not stuck. It served slow requests in the past and is now waiting for new work.&lt;/p></description></item><item><title>PHP-FPM request_terminate_timeout: stopping stuck requests from eroding the pool</title><link>https://www.netdata.cloud/guides/php-fpm/php-fpm-request-terminate-timeout/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.netdata.cloud/guides/php-fpm/php-fpm-request-terminate-timeout/</guid><description>&lt;p>PHP-FPM pools silently lose capacity when workers get stuck and never return to idle. The default &lt;code>request_terminate_timeout = 0&lt;/code> means a single deadlocked request, infinite loop, or hung database call occupies a worker forever. Over time, these stuck workers accumulate. The pool keeps running, requests keep being accepted, but effective concurrency shrinks with no error and no alert.&lt;/p>
&lt;p>The symptom is subtle. Stuck workers are still counted as active in the FPM status page, so the count looks normal. In static mode, &lt;code>active processes&lt;/code> sits at &lt;code>pm.max_children&lt;/code> with &lt;code>idle processes&lt;/code> at 0, but throughput is lower than the traffic should produce. The web server does not complain. The full status page reveals workers with request durations far exceeding your baseline. Eventually a traffic spike hits the reduced effective pool, the listen queue fills, and users see 502s. By the time the outage is visible, capacity has been eroding for hours or days.&lt;/p></description></item><item><title>PHP-FPM session lock contention: file sessions serializing a user's requests</title><link>https://www.netdata.cloud/guides/php-fpm/php-fpm-session-lock-contention/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.netdata.cloud/guides/php-fpm/php-fpm-session-lock-contention/</guid><description>&lt;p>A single user reports AJAX-heavy pages loading slowly, but the server has spare worker capacity and CPU is barely loaded. The PHP-FPM status page shows active workers climbing toward &lt;code>max_children&lt;/code>. You raise &lt;code>max_children&lt;/code> and nothing improves. The slow log, when configured, shows stack traces parked at &lt;code>session_start()&lt;/code>.&lt;/p>
&lt;p>This is session lock contention. PHP&amp;rsquo;s default file-based session handler acquires an exclusive &lt;code>flock(LOCK_EX)&lt;/code> on the session file at &lt;code>session_start()&lt;/code> and holds it until the script ends or &lt;code>session_write_close()&lt;/code> is called. When one browser session makes concurrent requests (parallel AJAX calls, SPA data fetching, long-polling, upload progress checks), those requests serialize completely behind that single lock.&lt;/p></description></item><item><title>PHP-FPM session_write_close and Redis sessions: fixing session serialization</title><link>https://www.netdata.cloud/guides/php-fpm/php-fpm-session-write-close/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.netdata.cloud/guides/php-fpm/php-fpm-session-write-close/</guid><description>&lt;p>Same-user requests that should run in parallel are completing one after another. AJAX panels load sequentially, dashboards stall on parallel fetches, and the slow log shows workers blocked at &lt;code>session_start()&lt;/code>. The PHP-FPM pool has spare capacity and CPU is low. This is session lock serialization, and the fix is almost always one of two moves: release the file lock earlier with &lt;code>session_write_close()&lt;/code>, or move sessions to a backend whose locking semantics do not serialize the same user.&lt;/p></description></item><item><title>PHP-FPM sizing pm.max_children: by memory, not by CPU cores</title><link>https://www.netdata.cloud/guides/php-fpm/php-fpm-sizing-max-children/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.netdata.cloud/guides/php-fpm/php-fpm-sizing-max-children/</guid><description>&lt;p>&lt;code>pm.max_children&lt;/code> is the hard ceiling on concurrent request processing in a PHP-FPM pool. Each worker handles exactly one request at a time. Once all workers are busy, new requests queue in the socket backlog. Once the backlog fills, the kernel drops connections and users see 502 errors.&lt;/p>
&lt;p>The common sizing mistake is anchoring this number to CPU core count. Teams pick 4 workers for a 4-core box, or 8 for an 8-core box, and assume they have sized correctly. They have not. PHP-FPM workers spend most of their lifetime blocked on I/O: database queries, external API calls, filesystem reads, session locks. While a worker waits on a slow database response, it holds a process slot but uses essentially zero CPU. A 4-core machine can run 50 to 200 workers if memory allows.&lt;/p></description></item><item><title>PHP-FPM slow log: turning on request_slowlog_timeout to see what is slow</title><link>https://www.netdata.cloud/guides/php-fpm/php-fpm-slow-log-setup/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.netdata.cloud/guides/php-fpm/php-fpm-slow-log-setup/</guid><description>&lt;p>When PHP-FPM workers pile up, the status page tells you they are busy. It does not tell you why. Active processes climb toward &lt;code>pm.max_children&lt;/code>, the listen queue fills, and you end up correlating timestamps against database slow query logs, APM traces, and application error logs to reconstruct what happened. The slow log closes that gap. When a request exceeds &lt;code>request_slowlog_timeout&lt;/code>, the FPM master ptraces the worker and writes a backtrace naming the exact script and call that blocked.&lt;/p></description></item><item><title>PHP-FPM slow request cascade: one slow dependency drains the whole pool</title><link>https://www.netdata.cloud/guides/php-fpm/php-fpm-slow-request-cascade/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.netdata.cloud/guides/php-fpm/php-fpm-slow-request-cascade/</guid><description>&lt;p>Your PHP-FPM pool is pinned at &lt;code>max_children&lt;/code>. The listen queue is climbing. Users see 502 Bad Gateway or 504 Gateway Timeout. CPU on the box is flat and the application error log is quiet. The PHP code is fine; it is waiting on a slow dependency.&lt;/p>
&lt;p>This is the slow request cascade. A database query regresses, an external API starts timing out, an NFS mount stalls, or DNS resolution hangs. A request that normally completes in 50ms now takes 5 to 30 seconds. Each slow request holds a worker for the full duration. The pool drains in seconds, the socket backlog fills, and the kernel starts refusing connections. This is the most common PHP-FPM failure mode in production, and the fix is almost never inside PHP-FPM itself.&lt;/p></description></item><item><title>PHP-FPM static vs dynamic vs ondemand: choosing a process manager mode</title><link>https://www.netdata.cloud/guides/php-fpm/php-fpm-static-vs-dynamic-vs-ondemand/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.netdata.cloud/guides/php-fpm/php-fpm-static-vs-dynamic-vs-ondemand/</guid><description>&lt;p>PHP-FPM&amp;rsquo;s &lt;code>pm&lt;/code> directive controls how the master process decides how many worker processes to keep alive. There are three modes: &lt;code>static&lt;/code>, &lt;code>dynamic&lt;/code>, and &lt;code>ondemand&lt;/code>. The choice changes how your pool reacts to a traffic burst, how much idle memory you pay for, and which status page counters are meaningful.&lt;/p>
&lt;p>Each worker handles exactly one request at a time, so worker count is your concurrency ceiling. The pm mode determines whether that ceiling is pre-allocated, scaled up reactively, or created on demand. Pick the wrong mode for your traffic pattern and you get one of three failure shapes: RAM burned on idle workers, latency spikes when the pool cannot fork fast enough, or a pool that scales down to zero and has to cold-start back up at the worst moment.&lt;/p></description></item><item><title>PHP-FPM status and ping page exposed: operational information disclosure</title><link>https://www.netdata.cloud/guides/php-fpm/php-fpm-status-page-exposed/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.netdata.cloud/guides/php-fpm/php-fpm-status-page-exposed/</guid><description>&lt;p>The PHP-FPM status page and ping endpoint are operational tools meant for monitoring and health checks from inside the network. When reachable from the public internet, they leak data that aids reconnaissance: pool names, process manager configuration, worker counts, PIDs, request durations, and in full mode the exact script paths and query strings of every active request. This is a binary condition. If the status page returns HTTP 200 from an external IP, it is exposed and needs immediate restriction.&lt;/p></description></item><item><title>PHP-FPM total processes below max_children: workers not spawning</title><link>https://www.netdata.cloud/guides/php-fpm/php-fpm-total-processes-mismatch/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.netdata.cloud/guides/php-fpm/php-fpm-total-processes-mismatch/</guid><description>&lt;p>The PHP-FPM status page reports &lt;code>total processes&lt;/code> below &lt;code>pm.max_children&lt;/code>, and the count is not climbing. Whether this is a problem depends on which process manager mode the pool runs and whether traffic is present.&lt;/p>
&lt;p>In &lt;code>static&lt;/code> mode, &lt;code>total processes&lt;/code> must equal &lt;code>pm.max_children&lt;/code> at all times. Any shortfall means workers have died and the master has not replaced them, or the master cannot fork new ones. This is always abnormal.&lt;/p>
&lt;p>In &lt;code>dynamic&lt;/code> mode, the count legitimately fluctuates between &lt;code>pm.min_spare_servers&lt;/code> and &lt;code>pm.max_children&lt;/code>. A low count during low traffic is expected. A low count under load, with &lt;code>idle processes&lt;/code> stuck at zero and the listen queue building, means the pool cannot scale up.&lt;/p></description></item><item><title>PHP-FPM will not start: bind failures, config errors, and PID file problems</title><link>https://www.netdata.cloud/guides/php-fpm/php-fpm-wont-start/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.netdata.cloud/guides/php-fpm/php-fpm-wont-start/</guid><description>&lt;p>&lt;code>systemctl start php-fpm&lt;/code> returns failed, the master process never appears in the process table, and the web server returns 502 for every PHP request. Workers are never forked. The failure happens during master initialization, before the &lt;code>ready to handle connections&lt;/code> notice.&lt;/p>
&lt;p>This is a control-plane failure, distinct from worker exhaustion or saturation cascades. Those affect a running master. Here, the usual FPM metrics (active processes, listen queue, max children reached) are unavailable because the status page and ping endpoint do not exist. The diagnostic path is different: read the first fatal log line.&lt;/p></description></item><item><title>PHP-FPM worker exhaustion: all workers busy and requests piling into the backlog</title><link>https://www.netdata.cloud/guides/php-fpm/php-fpm-worker-exhaustion/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.netdata.cloud/guides/php-fpm/php-fpm-worker-exhaustion/</guid><description>&lt;p>PHP-FPM worker exhaustion is the most common PHP-FPM failure mode in production. The signature: &lt;code>active processes&lt;/code> equals &lt;code>pm.max_children&lt;/code>, the listen queue is growing, the &lt;code>max children reached&lt;/code> counter is incrementing, and the web server starts returning 502 and 504 responses.&lt;/p>
&lt;p>The counterintuitive part: CPU is often LOW. Workers spend most of their time waiting on I/O, not computing. When all workers block on a slow database query or an unresponsive external API, CPU drops while requests pile up. If you are judging saturation by CPU alone, you will miss the problem.&lt;/p></description></item><item><title>PHP-FPM worker exit rate: normal recycling vs abnormal deaths</title><link>https://www.netdata.cloud/guides/php-fpm/php-fpm-worker-exit-rate/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.netdata.cloud/guides/php-fpm/php-fpm-worker-exit-rate/</guid><description>&lt;p>PHP-FPM workers exit constantly in a healthy pool. &lt;code>pm.max_requests&lt;/code> exists precisely to make workers exit on purpose after serving a fixed number of requests. The master forks a replacement, and the pool continues serving traffic. An exit rate of zero over hours means &lt;code>pm.max_requests&lt;/code> is set to 0 (unlimited), which means workers never recycle and any memory leak accumulates without bound.&lt;/p>
&lt;p>The question is never &amp;ldquo;are workers exiting?&amp;rdquo; but &amp;ldquo;why are workers exiting?&amp;rdquo; The master writes a line for every child termination with enough detail to classify it as normal recycling or an abnormal death. Only two specific signals feed into &lt;code>emergency_restart_threshold&lt;/code>, so the wrong exit pattern can mean anything from a harmless extension quirk to a crash loop that pages you at 3 a.m.&lt;/p></description></item><item><title>PHP-FPM worker memory sizing: RSS, PSS, and honest capacity math</title><link>https://www.netdata.cloud/guides/php-fpm/php-fpm-worker-memory-sizing/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.netdata.cloud/guides/php-fpm/php-fpm-worker-memory-sizing/</guid><description>&lt;p>The standard PHP-FPM capacity formula is &lt;code>max_children = available_memory / average_worker_RSS&lt;/code>. It shows up in every tuning guide, and it overcounts unique memory by a predictable margin. Forked PHP-FPM workers share a large block of read-only memory (OPcache bytecode, shared libraries, interned strings), and tools like &lt;code>ps&lt;/code> count those shared pages in every worker&amp;rsquo;s RSS. Sum RSS across workers and divide, and you overcount by roughly 30 to 50 percent.&lt;/p></description></item><item><title>PHP-FPM workers busy at low CPU: blocked on the database, API, or filesystem</title><link>https://www.netdata.cloud/guides/php-fpm/php-fpm-workers-blocked-on-io/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.netdata.cloud/guides/php-fpm/php-fpm-workers-blocked-on-io/</guid><description>&lt;p>PHP-FPM &lt;code>active processes&lt;/code> is climbing toward &lt;code>pm.max_children&lt;/code>, the listen queue is building, users are seeing slow responses or intermittent 502s, and yet system CPU is flat at 10 to 20 percent. The pool looks saturated in terms of worker slots, but the CPU headroom suggests the box is barely working.&lt;/p>
&lt;p>Each PHP-FPM worker handles exactly one request at a time. When a worker blocks on I/O (a database query, an external API response, a DNS lookup, or a filesystem operation), it holds its worker slot for the entire duration of that wait while consuming essentially zero CPU. It is &amp;ldquo;active&amp;rdquo; from FPM&amp;rsquo;s scoreboard perspective but idle from the kernel scheduler&amp;rsquo;s perspective.&lt;/p></description></item><item><title>PHP-FPM workers OOM-killed: "child N exited on signal 9 (SIGKILL)" and the memory cliff</title><link>https://www.netdata.cloud/guides/php-fpm/php-fpm-oom-killed-workers/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.netdata.cloud/guides/php-fpm/php-fpm-oom-killed-workers/</guid><description>&lt;p>The PHP-FPM error log shows the same line: &lt;code>WARNING: [pool www] child 12345 exited on signal 9 (SIGKILL)&lt;/code>. No segfault, no stack trace, no &amp;ldquo;core dumped&amp;rdquo;. Just signal 9. Users see 502s, requests fail in batches, and reloading PHP-FPM makes it go away for a while. Hours or days later, it returns.&lt;/p>
&lt;p>Signal 9 is not a PHP crash. It is the kernel or cgroup OOM killer terminating the worker because the process exceeded available memory. The master sees the worker die, logs the SIGKILL, and forks a replacement. It has no idea why the worker was killed. The log line is a consequence, not a cause.&lt;/p></description></item></channel></rss>