The log line is unambiguous:
WARNING: [pool www] child 12345 exited on signal 11 (SIGSEGV) after 3.4 seconds from start
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.
This page covers how to read the log line, what the kernel can tell you that FPM cannot, how to capture a usable stack trace, and how to narrow a noisy crasher to a specific endpoint and cause.
What this means
FPM writes the line when a worker is killed by a signal it cannot recover from. The signal number classifies the failure:
| Signal | Name | What it usually means |
|---|---|---|
| 11 | SIGSEGV | Segmentation fault: native code touched unmapped memory or violated protection. The classic extension or engine bug. |
| 7 | SIGBUS | Memory mapping problem, often a corrupted shared segment or a truncated memory-mapped file. |
| 6 | SIGABRT | Assertion failure inside a native library; the C runtime aborted on purpose. |
| 9 | SIGKILL | OOM kill or external kill. Not a segfault. |
Three facts to internalize:
dmesgis more informative than the FPM log. The FPM line says “child died with signal 11”; the kernel tells you the fault address, the instruction pointer, the offending module, and an error code that classifies the access.- The crash is almost always triggered by a specific request. The FPM status page in
?fullmode shows therequest URIof each active worker, so the dying worker’s URI is the single best lead. emergency_restart_thresholdis disabled by default. Without it, FPM respawns individual crashed workers forever and never does a full restart, which can mask a slow crash loop.
One crash a day is a bug to chase. Several a minute is a crash storm: prioritize mitigation (block the URI, disable a suspect extension, roll back a change) before forensics.
flowchart td
A[SIGSEGV in FPM log] --> B{Rate?}
B -- single or occasional --> C[Identify URI from status page]
B -- storm --> D[Stabilize: block URI, disable suspect]
C --> E[Check dmesg for fault address and module]
D --> E
E --> F{Recent change?}
F -- yes --> G[Roll back PHP, extension, or opcache config]
F -- no --> H[Capture core via rlimit_core, gdb backtrace]
H --> I[Bisect extensions: disable, test, re-enable one by one]
G --> J[Monitor crash rate after fix]
I --> JCommon causes
| Cause | What it looks like | First thing to check |
|---|---|---|
| Native C extension bug (ddtrace, tideways, newrelic, Xdebug, ioncube, APCu or imagick combos) | Crashes started after an extension upgrade, or only on certain request shapes. dmesg names the extension .so. | php -m and the extension changelog. Disable non-core extensions and re-enable one by one. |
Corrupted OPcache shared memory or opcache.file_cache | Crashes cluster after deploys that change file paths, or recur until FPM is restarted. Hit rate looks normal. | Disable opcache.file_cache (keep opcache.enable=1) and reload. |
| JIT tracing bug | Crashes only with opcache.jit enabled, often under specific workloads (CLI tools, long-running workers). | Set opcache.jit=off (or disable) and reload. |
| Deep recursion or stack overflow | One endpoint consistently crashes the worker that handles it. Backtrace shows deep frames. | Reproduce locally. Raising ulimit -s is a stopgap only. |
| PHP version regression after upgrade | Crashes began within hours of a PHP upgrade; reproduces on a clean app. | Roll back to the previous PHP patch release and read the changelog for segfault fixes. |
| CVE or remote trigger | Crashes correlate with specific inbound request patterns (TLS handshake, proxy use). | Check the PHP changelog and security advisories. Apply the next patch release. |
Quick checks
Run these read-only before changing anything. They tell you the rate, the failing endpoint, and what the kernel saw.
# Crash rate from the FPM error log in the current hour
grep "exited on signal 11" /var/log/php-fpm/error.log | grep "$(date '+%d-%b-%Y %H')" | wc -l
# Kernel-side detail the FPM log omits: fault address, IP, error code, module
dmesg --since "1 hour ago" | grep -A2 -B2 php-fpm
journalctl -k --since "1 hour ago" | grep php-fpm
# Crashing endpoint from the per-worker request URI (full status)
curl -s 'http://127.0.0.1/fpm-status?full' | grep -E "request URI|state"
# Process count to spot a crash loop (compare against pm.max_children)
pgrep -cf "php-fpm: pool"
# Is the emergency-restart circuit breaker even configured?
php-fpm -tt 2>&1 | grep -E "emergency_restart_threshold|emergency_restart_interval"
# Recent upgrades that line up with the start of the crashes
zgrep -h "php" /var/log/dpkg.log* /var/log/yum.log* 2>/dev/null | tail -20
The kernel error field in the dmesg segfault line is worth decoding. A common value is error 4: on x86_64 that is a user-space read of a non-present page, the typical signature of a null or stale pointer dereference inside an extension.
How to diagnose it
Get the rate. A few crashes a day is a different problem from three a minute. If the accepted-connections rate is dropping at the same time, there is user impact. If
total processesis bouncing, you are in a respawn loop.Identify the dying worker’s URI. Poll
fpm-status?fullat 1-second intervals during the crash window and capture therequest URIof any worker inRunningstate immediately before a death. Crashes that always cluster on one URI point to a code path; crashes spread evenly across URIs point to engine-level state (OPcache, JIT, extension globals).Pull dmesg for fault detail. For each crashed PID, find the matching kernel segfault line. The pattern
php-fpm[PID]: segfault at ADDR ip IP sp SP error N in MODULE[...]names the module where the fault happened. That alone narrows “PHP crashed” to “the tracing extension crashed” or “opcache.so crashed”.Line up changes. Compare the first crash timestamp against recent deploys, package upgrades, INI edits, and traffic shifts. Segfaults that begin within an hour of a change are usually that change. PHP patch releases, extension upgrades, and INI flips (
opcache.jit=on,opcache.file_cache=/path) are the usual suspects.Capture a core if dmesg is not enough. Core dumps are disabled by default. To enable per pool, set
rlimit_core = unlimitedin the pool config and ensure the systemcore_patternwrites somewhere with space:
# Check the current core pattern
cat /proc/sys/kernel/core_pattern
# On systemd distros this typically pipes to systemd-coredump; read with:
coredumpctl list
coredumpctl info <pid>
# To write plain core files instead (until next reboot).
# WARNING: this changes core_pattern globally for the whole host, not just FPM.
echo '/tmp/core-%e.%p' | sudo tee /proc/sys/kernel/core_pattern
Reload FPM with SIGUSR2, reproduce the crash, and inspect the dump:
# Load the core against the PHP binary that produced it
gdb /path/to/php-fpm /tmp/core-php-fpm.<pid>
# Inside gdb
(gdb) bt full
(gdb) thread apply all bt
Core dumps are large (often hundreds of MB) and fill disk fast under a crash loop. Disable collection as soon as you have one or two good stacks.
- Bisect extensions. If the backtrace names an extension, version-check it against the upstream changelog. If the backtrace is inconclusive, disable every non-core extension and re-enable them one at a time, exercising the crashing URI between each step. Keep OPcache in the picture but toggle
opcache.file_cacheandopcache.jitseparately.
Metrics and signals to monitor
| Signal | Why it matters | Warning sign |
|---|---|---|
| Worker exit rate (signal 11 only) | Direct measure of crash frequency. Sustained non-zero rate is abnormal. | More than one SIGSEGV per minute, or any step up from a flat baseline. |
| Emergency restart events | FPM crossed emergency_restart_threshold and execvp()’d itself. Users see a brief 502 window. | grep "failed processes threshold" /var/log/php-fpm/error.log returns anything. Two or more in 15 minutes is a persistent crasher. |
| Total process count | Crash-loop signature: count oscillates instead of staying flat (static) or tracking demand (dynamic). | active + idle consistently below pm.max_children in a static pool. |
Per-worker request URI | Identifies the endpoint that triggers the crash; the highest-value field during an incident. | One URI dominating the Running set right before each crash. |
| Accepted connections rate | Drops when workers cannot stay alive long enough to accept work. | Throughput falling while upstream traffic holds steady. |
| dmesg segfault lines | Provides fault address, instruction pointer, and module name that the FPM log omits. | Any new php-fpm[...]: segfault at ... line. |
Fixes
Treat these as mitigation first, root cause second. A SIGSEGV is a native bug; the durable fix is almost always a configuration change, an extension upgrade, a PHP patch release, or a code change to avoid the trigger.
OPcache file_cache corruption
If crashes cluster after symlink-style releases or never resolve until FPM is fully restarted, disable opcache.file_cache and keep opcache.enable=1. File-cache corruption after path-changing deploys is a well-repeated production failure mode. Clearing the cache directory and reloading is the short-term fix; leaving opcache.file_cache off is the long-term fix on deployment patterns that move file paths.
JIT tracing bugs
If crashes coincide with opcache.jit being on, set opcache.jit=off (or disable) and reload. JIT tracing faults are typically workload-specific, so a clean test suite will not reproduce them. Disable JIT first, confirm the crash stops, then track the upstream bug.
Extension bugs
If dmesg or the gdb backtrace names an extension, version-check it. The standard sequence: snapshot loaded extensions (php -m), disable every non-core extension, reload, exercise the crashing URI, and re-enable one at a time. Common offenders are tracing APM extensions (ddtrace, tideways, newrelic), ioncube loaders, and certain APCu or imagick version combinations. The extension changelog usually lists segfault fixes by version.
Stack overflow
If the backtrace is dominated by hundreds of repeating frames, raising the stack size limit is only a stopgap. The real fix is in the code: unbounded recursion, recursive serializers, or template engines that recurse on user-supplied input.
PHP version regression
If the first crash lines up with a PHP upgrade, roll back to the previous patch release, then read the upstream changelog for segfault fixes in newer releases. Test upgrades in staging with realistic traffic replay before promoting. Native crashes rarely surface under low-volume smoke tests.
CVEs and remote triggers
If crashes correlate with specific inbound traffic (a TLS handshake, a proxied HTTPS request, a malformed body), treat it as a potential security issue. Check the PHP changelog and security advisories, and stage the next patch release. Network-level rate limiting or WAF rules can buy time while the patch rolls out.
Prevention
- Configure
emergency_restart_thresholdandemergency_restart_interval. Both default to 0 (disabled). A reasonable starting point isemergency_restart_threshold = 10withemergency_restart_interval = 60, giving FPM a circuit breaker against a runaway crasher. - Alert on the SIGSEGV rate, not the absolute count. A counter that drifts up over a week is as actionable as a sudden spike; a daily absolute hides both.
- Track FPM, PHP, and extension versions in your inventory. When a segfault wave starts, the first question is “what changed”. Without version history you cannot answer it.
- Test PHP upgrades with traffic replay. Segfaults are workload-specific. A smoke test of the top three endpoints will miss a crash that only fires on the fortieth.
- Keep core dump plumbing ready but off. Know your
core_pattern, budget the disk space, and rehearse thegdbcommands before you need them at 3 a.m. - Pin extensions explicitly. Auto-updated extensions have introduced segfaults in the wild. Treat extension upgrades with the same change-management rigor as PHP upgrades.
How Netdata helps
- Per-second worker-death tracking shows a crasher starting within seconds instead of after a polling delay, and the rate-of-change view distinguishes a single bad request from a storm.
- Anomaly detection on the SIGSEGV rate surfaces a slow upward drift that a fixed threshold would miss, and on the accepted-connections rate catches the throughput drop that often precedes user-visible 502s.
- The full-status poll captures the per-worker
request URI, so the dying worker’s endpoint is recorded alongside the crash instead of reconstructed from logs after the fact. - Correlating crash rate against deployment markers, package installs, and PHP version changes answers the “what changed” question without manual log archaeology.
- Emergency restart events are tracked as discrete signals rather than buried in log volume, so a brief self-heal does not pass silently.
Related guides
- PHP-FPM 504 Gateway Timeout: requests accepted but never finishing in time
- PHP-FPM active processes near max_children: reading pool utilization
- PHP-FPM in containers: cgroup limits and the silent OOM kill
- How PHP-FPM actually works in production: a mental model for operators
- PHP-FPM idle processes at zero: no burst headroom left
- PHP-FPM listen queue growing: the earliest signal of saturation
- PHP-FPM “server reached pm.max_children setting (N), consider raising it”
- PHP-FPM pm.max_requests: worker recycling as the memory-leak safety net
- PHP-FPM memory leak: per-worker RSS climbing until the box runs out
- PHP-FPM memory_limit vs worker RSS: why workers exceed the limit you set
- PHP-FPM monitoring checklist: the signals every production pool needs
- PHP-FPM monitoring maturity model: from survival to expert






