
PHP-FPM Production Pool Tuning for High-Traffic Linux Servers
- AuthorLiam K.
- Date
- Time16 minutes
PHP-FPM pool misconfiguration is one of the most common causes of 502 errors, memory exhaustion, and unpredictable latency on Linux web servers. The right worker count depends on available RAM, per-request memory footprint, and concurrency — not generic defaults. This guide shows how to size and tune pools for production traffic.
Step 1: Measure Per-Worker Memory
Run your application under realistic load, then measure RSS per PHP-FPM child process.
ps -ylC php-fpm8.3 --sort=rss | awk 'NR==1{print} NR>1 && NR<=15{print}'
# Example output: each worker ~85 MB RSS for a typical Laravel appStep 2: Calculate Safe max_children
Reserve 20–30% RAM for OS, database, cache, and Nginx. Formula: max_children ≈ (available RAM for PHP) / average worker RSS.
# Example: 8 GB RAM server, reserve 2 GB for system + DB + cache
# Available for PHP: 6 GB = 6144 MB
# Worker RSS: 85 MB
# max_children ≈ 6144 / 85 ≈ 72 (use 60–65 for headroom)Step 3: Configure a Dynamic Pool
sudo tee /etc/php/8.3/fpm/pool.d/www.conf >/dev/null <<'EOF'
[www]
user = www-data
group = www-data
listen = /run/php/php8.3-fpm.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
pm = dynamic
pm.max_children = 64
pm.start_servers = 8
pm.min_spare_servers = 8
pm.max_spare_servers = 16
pm.max_requests = 500
request_terminate_timeout = 60s
request_slowlog_timeout = 5s
slowlog = /var/log/php8.3-fpm-slow.log
php_admin_value[memory_limit] = 256M
php_admin_value[max_execution_time] = 60
php_admin_value[upload_max_filesize] = 32M
php_admin_value[post_max_size] = 32M
catch_workers_output = yes
decorate_workers_output = no
EOFStep 4: Tune OPcache for Production
sudo tee /etc/php/8.3/fpm/conf.d/10-opcache.ini >/dev/null <<'EOF'
opcache.enable=1
opcache.enable_cli=0
opcache.memory_consumption=256
opcache.interned_strings_buffer=32
opcache.max_accelerated_files=20000
opcache.validate_timestamps=0
opcache.revalidate_freq=0
opcache.save_comments=1
opcache.jit=1255
opcache.jit_buffer_size=128M
EOFSet opcache.validate_timestamps=0 only when you reload PHP-FPM after each deploy. For development, keep validation enabled.
Step 5: Add Nginx FastCGI Timeouts
# Inside your PHP location block:
fastcgi_connect_timeout 10s;
fastcgi_send_timeout 60s;
fastcgi_read_timeout 60s;
fastcgi_buffers 16 16k;
fastcgi_buffer_size 32k;Step 6: Validate and Reload
sudo php-fpm8.3 -t
sudo systemctl reload php8.3-fpm
sudo systemctl reload nginx
# Watch pool status during load test
watch -n 2 'ps -ylC php-fpm8.3 | wc -l'Step 7: Monitor Slow Requests
sudo tail -f /var/log/php8.3-fpm-slow.log
# Enable php-fpm status page (restrict to localhost):
# pm.status_path = /fpm-status
# ping.path = /fpm-pingCommon Mistakes to Avoid
- Setting pm.max_children too high — causes OOM kills under traffic spikes.
- Using pm = ondemand for latency-sensitive APIs without measuring cold-start cost.
- Ignoring pm.max_requests — long-lived workers can leak memory over days.
- Deploying code changes without reloading PHP-FPM when OPcache timestamp validation is disabled.
- Running multiple heavy PHP apps in one pool instead of separate pools per site.
"PHP-FPM tuning is arithmetic, not guesswork: measure worker memory, size the pool, then validate under load before you trust it in production."



