← Back to Blog

Fix PHP-FPM SIGSEGV Segmentation Fault Crashes on High-Traffic Servers

Quick Fix for Panicking Engineers

If PHP-FPM children are dying with signal 11 (SIGSEGV) right now, run these three commands:

# 1. Restart FPM to get the site back up immediately
sudo systemctl restart php8.2-fpm

# 2. Disable opcache file_cache (most common cause of SIGSEGV)
sudo sed -i 's/opcache.file_cache=.*/opcache.file_cache=/' /etc/php/8.2/fpm/conf.d/10-opcache.ini
sudo systemctl reload php8.2-fpm

# 3. Check which extension is crashing
sudo grep -i 'sigsegv\|segfault\|signal 11' /var/log/php8.2-fpm.log | tail -20

If that stabilizes it, read on for the permanent fix. If not, jump to strace debugging below.

Your PHP-FPM error log is filling with child exited on signal 11 (SIGSEGV) entries. Response times are spiking. The master process keeps spawning replacement workers that immediately die. You have seen this before, probably after a PHP upgrade, a traffic spike, or a deployment that changed opcache settings. This guide covers every known cause of PHP-FPM segmentation faults, how to isolate each one, and the exact configuration to prevent recurrence on high-traffic production servers.

Identifying the SIGSEGV Pattern

First, confirm what you are dealing with. The PHP-FPM error log shows one of these patterns:

Pattern 1: Child Process Signal 11

[02-Apr-2026 14:23:17] WARNING: [pool www] child 28491 exited on signal 11 (SIGSEGV) after 0.432 seconds from start
[02-Apr-2026 14:23:17] NOTICE: [pool www] child 28495 started
[02-Apr-2026 14:23:18] WARNING: [pool www] child 28495 exited on signal 11 (SIGSEGV) after 0.891 seconds from start
[02-Apr-2026 14:23:18] NOTICE: [pool www] child 28499 started

Workers are spawning and immediately crashing. This is the most severe variant. The FPM master process is healthy but every child it spawns dies on the first or second request.

Pattern 2: Intermittent Crashes Under Load

[02-Apr-2026 14:23:17] WARNING: [pool www] child 12847 exited on signal 11 (SIGSEGV) after 847.221 seconds from start
[02-Apr-2026 14:31:42] WARNING: [pool www] child 12903 exited on signal 11 (SIGSEGV) after 502.118 seconds from start

Workers run for minutes or hours before crashing. This points to memory corruption that accumulates over time, typically from a leaky extension or incorrect pm.max_requests configuration.

Pattern 3: Kernel Log OOM + SIGSEGV Combo

# dmesg output
[423847.291] Out of memory: Killed process 28491 (php-fpm) total-vm:2148392kB, anon-rss:1048576kB
# FPM log shows this as SIGSEGV
[02-Apr-2026 14:23:17] WARNING: [pool www] child 28491 exited on signal 11 (SIGSEGV) after 12.432 seconds from start

The OOM killer is terminating PHP-FPM workers, and FPM reports the kill signal as SIGSEGV. Check dmesg | grep -i oom to distinguish this from a true segfault.

Cause 1: Corrupted Opcache Shared Memory

This is the single most common cause of PHP-FPM SIGSEGV on production servers. The opcache stores compiled PHP bytecode in shared memory. When that shared memory becomes corrupted, every worker that tries to read from it segfaults.

How to Confirm

# Check if opcache is enabled and near capacity
php -r "var_dump(opcache_get_status(false));" | grep -E 'used_memory|free_memory|num_cached|max_accelerated'

# If used_memory is near opcache.memory_consumption, the cache is full
# If num_cached_scripts is near max_accelerated_files, the hash table is full

The Fix

# /etc/php/8.2/fpm/conf.d/10-opcache.ini

opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=32
opcache.max_accelerated_files=30000
opcache.validate_timestamps=0
opcache.revalidate_freq=0
opcache.file_cache=
opcache.file_cache_only=0
opcache.huge_pages=1
opcache.save_comments=1

Key changes: opcache.memory_consumption=256 gives the shared memory segment enough room. The default of 128MB is insufficient for frameworks like Laravel, Magento, or Symfony that load thousands of files. opcache.max_accelerated_files=30000 sizes the hash table appropriately. The actual limit is rounded up to the next prime number (30011). Count your PHP files with find /var/www -name '*.php' | wc -l and set this to at least 1.5x that count.

The opcache.file_cache directive deserves special attention. When set to a directory path, opcache writes a secondary file-based cache. If this directory has permission issues or runs out of disk space, it corrupts the shared memory cache and triggers SIGSEGV. Disable it unless you have a specific reason to use it.

# After changing opcache settings, you must restart (not reload) FPM
# Reload does not reinitialize the shared memory segment
sudo systemctl restart php8.2-fpm

# Verify the new settings took effect
php -r "print_r(opcache_get_configuration()['directives']);"

Cause 2: Buggy or Incompatible PHP Extensions

Extensions compiled against one PHP version running on another, or extensions with known segfault bugs, are the second most common cause.

Common Offenders

  • ionCube Loader: Must exactly match the PHP major.minor version. ionCube 13.0 for PHP 8.2 segfaults on PHP 8.3.
  • Xdebug in production: Xdebug 3.x has known segfaults when xdebug.mode=coverage runs under high concurrency. It should never be loaded in production.
  • imagick: The ImageMagick PHP extension segfaults with certain image formats (WebP, AVIF) if the underlying libmagickwand is outdated.
  • grpc: The gRPC extension versions below 1.57 segfault on PHP 8.2+ due to a protobuf compatibility issue.
  • pcntl: Calling pcntl_fork() inside an FPM worker corrupts the shared opcache memory.

Isolate the Bad Extension

# List all loaded extensions
php -m

# Check for extensions not from the official PHP packages
dpkg -l | grep php8.2 | awk '{print $2}'
# Compare with:
ls /etc/php/8.2/fpm/conf.d/

# Disable extensions one at a time
sudo phpdismod -s fpm imagick
sudo systemctl restart php8.2-fpm
# Monitor for 5 minutes, then test the next one

# Binary search: disable half the non-essential extensions at once
# If crashes stop, the culprit is in the disabled half
# Re-enable half of that group, repeat until isolated

Verify Extension Compatibility

# Check that all .so files match the current PHP API version
php -i | grep 'PHP API'
# Output: PHP API => 20220829 (for PHP 8.2)

# Check each extension .so file
for so in /usr/lib/php/20220829/*.so; do
  echo -n "$so: "
  file "$so" | grep -o 'ELF.*'
done

# Any extension compiled for a different API version will segfault on load

Cause 3: pm.max_children Exhaustion and OOM

When pm.max_children is set too high for available RAM, the Linux OOM killer starts terminating FPM workers. FPM logs these as SIGSEGV because it receives the kill signal without context about why.

Calculate pm.max_children for 64GB RAM

# Step 1: Determine average PHP worker memory usage
ps --no-headers -o rss -C php-fpm | awk '{ sum+=$1; count++ } END { printf "Average: %.0f MB (%d workers)\n", sum/count/1024, count }'

# Typical output for a Magento/Laravel app:
# Average: 82 MB (47 workers)

# Step 2: Calculate available RAM for PHP
# Total RAM: 64GB
# Reserve for OS: 2GB
# Reserve for MySQL/Redis/Nginx: 6GB (adjust for your stack)
# Available for PHP: 64 - 2 - 6 = 56GB = 57344 MB

# Step 3: Calculate max_children
# 57344 / 82 = 699
# Round down and leave headroom: 500-600 is safe

Optimal Pool Configuration for 64GB

# /etc/php/8.2/fpm/pool.d/www.conf

[www]
pm = dynamic
pm.max_children = 500
pm.start_servers = 50
pm.min_spare_servers = 25
pm.max_spare_servers = 75
pm.max_requests = 1000
pm.process_idle_timeout = 10s

; Process priority (lower = higher priority)
process.priority = -5

; Slow log for debugging
request_slowlog_timeout = 5s
slowlog = /var/log/php-fpm/www-slow.log

; Kill workers that exceed this execution time
request_terminate_timeout = 300s

pm.max_requests = 1000 is critical. This recycles workers after 1000 requests, preventing memory leaks from accumulating into corruption. Without this, a worker with a slow leak can grow from 80MB to 500MB+ over hours and eventually segfault.

For Servers Handling 1000+ req/s

# Use pm = static for high-traffic servers
# Eliminates the overhead of dynamic process management
[www]
pm = static
pm.max_children = 500
pm.max_requests = 500

# With static pool, all 500 workers are always running
# No spawn/kill overhead during traffic spikes
# Trade-off: constant memory usage even during low traffic

Cause 4: Memory Corruption from Realpath Cache

PHP's realpath cache maps file paths to their resolved filesystem paths. On deployments that use symlinks (Capistrano, Deployer, Laravel Envoyer), a stale realpath cache causes workers to read from deallocated memory.

# Check realpath cache settings
php -i | grep realpath

# Problem scenario:
# 1. Deploy creates new release: /var/www/releases/v42/
# 2. Symlink updated: /var/www/current -> /var/www/releases/v42/
# 3. Old workers still have /var/www/releases/v41/ paths cached
# 4. v41 directory is deleted -> workers segfault reading freed memory

# Fix: set a reasonable TTL and clear cache on deploy
# /etc/php/8.2/fpm/conf.d/99-custom.ini
realpath_cache_size = 4096K
realpath_cache_ttl = 120

# In your deploy script, after symlink swap:
sudo systemctl reload php8.2-fpm

Cause 5: Kernel and Shared Memory Limits

The Linux kernel limits shared memory segments. If opcache or another extension tries to allocate a shared memory segment larger than the kernel allows, it gets a truncated or corrupted segment.

# Check current kernel shared memory limits
cat /proc/sys/kernel/shmmax
cat /proc/sys/kernel/shmall

# For a 64GB server with opcache.memory_consumption=256:
# shmmax should be at least 268435456 (256MB)
# shmall should be at least 65536 (pages, 256MB / 4096)

# Set permanently
echo 'kernel.shmmax = 268435456' | sudo tee -a /etc/sysctl.d/99-php.conf
echo 'kernel.shmall = 65536' | sudo tee -a /etc/sysctl.d/99-php.conf
sudo sysctl --system

Debugging with strace

When the cause is not obvious from logs and configuration review, attach strace to the FPM master process to catch the exact syscall that triggers the fault.

# Attach to the master process (will trace all child forks)
sudo strace -f -o /tmp/fpm-trace.log -p $(pgrep -o php-fpm) &

# Wait for a crash, then search the trace
grep -B 20 'SIGSEGV' /tmp/fpm-trace.log

# Typical output showing opcache corruption:
# mmap(NULL, 268435456, PROT_READ|PROT_WRITE, MAP_SHARED, 5, 0) = 0x7f3a10000000
# ... (thousands of lines)
# --- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x7f3a1a234567} ---

# The si_addr tells you where the fault occurred
# If it's within the mmap range above, it's opcache shared memory corruption

# For more targeted tracing (memory operations only):
sudo strace -f -e trace=memory,signal -p $(pgrep -o php-fpm) 2>&1 | tee /tmp/fpm-mem-trace.log

Generate a Core Dump for Extension Debugging

# Enable core dumps for PHP-FPM
echo '/tmp/core-%e-%p-%t' | sudo tee /proc/sys/kernel/core_pattern
ulimit -c unlimited

# In php-fpm.conf, set:
rlimit_core = unlimited

# Restart FPM and wait for crash
sudo systemctl restart php8.2-fpm

# Analyze the core dump with gdb
sudo gdb /usr/sbin/php-fpm /tmp/core-php-fpm-28491-1712044997
(gdb) bt
# The backtrace shows the exact function call stack at crash time
# Look for extension .so files in the trace

# Example backtrace pointing to imagick:
# #0  0x00007f3a1b234567 in MagickCore::ReadWebPImage () from /usr/lib/x86_64-linux-gnu/libMagickCore-6.so
# #1  0x00007f3a1c345678 in zim_Imagick_readImageBlob () from /usr/lib/php/20220829/imagick.so
# #2  0x00005621a8123456 in execute_ex () at /build/php8.2/Zend/zend_vm_execute.h:56073

Share Debug Logs Securely

Need to share strace output or core dump analysis with your team? Use SecureBin's encrypted, self-destructing pastes instead of Slack or email.

Share Logs Securely

Cause 6: PHP Version Mismatch After Upgrade

Upgrading PHP (e.g., 8.1 to 8.2) without recompiling all PECL extensions leaves .so files compiled against the old Zend API. PHP loads them anyway but they crash when calling functions with changed signatures.

# After any PHP minor version upgrade:
# 1. Rebuild all PECL extensions
sudo pecl list | tail -n +4 | awk '{print $1}' | while read ext; do
  echo "Rebuilding $ext..."
  sudo pecl install --force "$ext"
done

# 2. Clear opcache file cache if it exists
sudo rm -rf /tmp/opcache/*

# 3. Restart FPM (not reload)
sudo systemctl restart php8.2-fpm

# 4. Verify all extensions load cleanly
php -m 2>&1 | grep -i warning
# Should produce no output

Cause 7: NUMA Memory Access on Multi-Socket Servers

On servers with multiple CPU sockets (common in 64GB+ configurations), PHP-FPM workers may be allocated memory on a different NUMA node than the opcache shared memory segment. Cross-node memory access is slower and, in rare cases with certain kernel versions, can cause corruption.

# Check if your server has multiple NUMA nodes
numactl --hardware

# If you see 2+ nodes, pin FPM to a single node:
# In the systemd unit override:
sudo systemctl edit php8.2-fpm
# Add:
[Service]
ExecStart=
ExecStart=/usr/bin/numactl --interleave=all /usr/sbin/php-fpm8.2 --nodaemonize --fpm-config /etc/php/8.2/fpm/php-fpm.conf

sudo systemctl daemon-reload
sudo systemctl restart php8.2-fpm

Verification: Confirm the Fix

After applying your fix, verify stability:

# 1. Monitor FPM log for SIGSEGV (should be zero)
sudo tail -f /var/log/php8.2-fpm.log | grep -i 'signal 11\|sigsegv'

# 2. Check process stability (workers should have long uptimes)
ps -eo pid,etimes,rss,comm | grep php-fpm | sort -k2 -rn | head -10
# etimes should increase steadily without resets

# 3. Monitor memory usage over time
watch -n 5 'ps --no-headers -o rss -C php-fpm | awk "{ sum+=\$1; count++ } END { printf \"Workers: %d, Total: %.0f MB, Avg: %.0f MB\n\", count, sum/1024, sum/count/1024 }"'

# 4. Check dmesg for OOM events
sudo dmesg -T | grep -i 'oom\|killed process' | tail -10

# 5. Load test to confirm stability under pressure
ab -n 10000 -c 100 https://yourdomain.com/
# Or with wrk:
wrk -t 4 -c 200 -d 60s https://yourdomain.com/

# 6. Verify opcache is healthy
php -r "
\$s = opcache_get_status(false);
printf(\"Memory: %.1f/%.1f MB (%.0f%% used)\n\",
  \$s['memory_usage']['used_memory']/1048576,
  (\$s['memory_usage']['used_memory']+\$s['memory_usage']['free_memory'])/1048576,
  \$s['memory_usage']['used_memory']/(\$s['memory_usage']['used_memory']+\$s['memory_usage']['free_memory'])*100);
printf(\"Scripts: %d/%d cached\n\", \$s['opcache_statistics']['num_cached_scripts'], \$s['opcache_statistics']['max_cached_keys']);
printf(\"Restarts: %d (oom), %d (hash), %d (manual)\n\", \$s['opcache_statistics']['oom_restarts'], \$s['opcache_statistics']['hash_restarts'], \$s['opcache_statistics']['manual_restarts']);
"

Production Monitoring Setup

Prevent future SIGSEGV incidents with proactive monitoring:

# Prometheus PHP-FPM exporter (add to your monitoring stack)
# Tracks: active/idle processes, accepted connections, slow requests, listen queue
# Alert on: max_children reached, listen queue > 0, process restarts > 5/min

# Simple cron-based monitoring (if no Prometheus)
# /etc/cron.d/php-fpm-monitor
*/5 * * * * root grep -c 'signal 11' /var/log/php8.2-fpm.log | \
  awk -v prev=$(cat /tmp/fpm-segv-count 2>/dev/null || echo 0) \
  '{ if ($1 > prev) system("echo PHP-FPM SIGSEGV detected | mail -s ALERT ops@company.com"); print $1 > "/tmp/fpm-segv-count" }'

Complete Recommended Configuration

For a 64GB server running a PHP application (Magento, Laravel, WordPress at scale):

# /etc/php/8.2/fpm/pool.d/www.conf
[www]
user = www-data
group = www-data
listen = /run/php/php8.2-fpm.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

pm = dynamic
pm.max_children = 500
pm.start_servers = 50
pm.min_spare_servers = 25
pm.max_spare_servers = 100
pm.max_requests = 1000
pm.process_idle_timeout = 10s
pm.status_path = /fpm-status

request_terminate_timeout = 300s
request_slowlog_timeout = 5s
slowlog = /var/log/php-fpm/www-slow.log

rlimit_files = 65536
rlimit_core = 0

catch_workers_output = yes
decorate_workers_output = no

# /etc/php/8.2/fpm/conf.d/10-opcache.ini
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=32
opcache.max_accelerated_files=30000
opcache.validate_timestamps=0
opcache.file_cache=
opcache.huge_pages=1
opcache.save_comments=1
opcache.jit_buffer_size=128M
opcache.jit=1255

# /etc/php/8.2/fpm/conf.d/99-custom.ini
realpath_cache_size=4096K
realpath_cache_ttl=120
memory_limit=256M
max_execution_time=300
output_buffering=4096

Share Server Configs Securely

Sharing PHP-FPM configs with your team? Use SecureBin to send server configurations through encrypted, self-destructing links. No credentials leaked in Slack.

Share Config Securely

Summary

PHP-FPM SIGSEGV crashes on high-traffic servers follow a predictable pattern. In order of likelihood: corrupted opcache shared memory (fix: increase opcache.memory_consumption and disable file_cache), buggy extensions (fix: isolate and upgrade), OOM kills misreported as SIGSEGV (fix: tune pm.max_children for your RAM), realpath cache staleness after symlink deploys (fix: reload FPM post-deploy), and kernel shared memory limits (fix: increase shmmax). Set pm.max_requests=1000 to recycle workers before memory leaks accumulate. Monitor for signal 11 in FPM logs and for OOM events in dmesg. A properly tuned 64GB server should handle 500+ PHP-FPM workers without a single segfault.

Related Articles

Continue reading: Fix Nginx 502 Bad Gateway with PHP-FPM, Fix Incomplete SSL Certificate Chain Error, Fix CORS No Access-Control-Allow-Origin Error.

UK
Written by Usman Khan
DevOps Engineer | MSc Cybersecurity | CEH | AWS Solutions Architect

Usman has 10+ years of experience securing enterprise infrastructure, managing high-traffic servers, and building zero-knowledge security tools. Read more about the author.