PHP-FPM Pool Tuning — Match Workers to Your Server's RAM

How to tune PHP-FPM worker processes for your VPS RAM, calculate the right pm.max_children value, and prevent the out-of-memory crashes that happen when WordPress gets unexpected traffic.

Terminal showing free -m output alongside PHP-FPM worker count calculation

PHP-FPM’s default configuration doesn’t know how much RAM your server has or how many WordPress plugins you’re running. The defaults are conservative — they work, but they may not be optimal for your specific setup.


Step 1 — Measure Actual RAM Per PHP Worker

With your WordPress site running and having received a few requests, check how much RAM each PHP-FPM worker is using:

ps aux | grep php-fpm | grep -v grep | awk '{print $6}' | sort -n

This shows the RSS (Resident Set Size) in KB for each worker process. Calculate the average:

ps aux | grep php-fpm | grep -v grep | awk '{sum+=$6; count++} END {print "Average:", sum/count/1024, "MB"}'

A typical WordPress site with 15–25 active plugins uses 30–60MB per PHP worker. A leaner site (fewer plugins, simpler theme) uses 20–35MB.


Step 2 — Calculate pm.max_children

The formula:

pm.max_children = Available RAM for PHP ÷ RAM per worker

Available RAM for PHP = Total VPS RAM minus what Nginx, MariaDB, Redis, and the OS need.

Rough allocation for a 2GB VPS running WordPress:

  • OS + Nginx: ~200MB
  • MariaDB: ~200MB
  • Redis: ~50MB
  • Available for PHP: ~1,550MB

If each PHP worker uses 40MB:

1,550 ÷ 40 = ~38 workers maximum

But set pm.max_children lower than the theoretical maximum — you need headroom for traffic spikes. A practical value for a 2GB VPS: 10–15 workers.

For a 4GB VPS: 20–30 workers.

Diagram showing VPS RAM allocation: OS+Nginx 200MB, MariaDB 200MB, Redis 50MB, remaining available for PHP-FPM workers
RAM allocation across services. The remaining RAM divided by per-worker usage gives your safe maximum worker count.

Step 3 — Update www.conf

sudo nano /etc/php-fpm.d/www.conf

Find and update the process manager settings:

; Process manager type — dynamic scales workers based on load
pm = dynamic

; Maximum workers — set based on your calculation
pm.max_children = 15

; Workers to start when PHP-FPM starts
pm.start_servers = 4

; Minimum idle workers to keep running
pm.min_spare_servers = 2

; Maximum idle workers before killing extras
pm.max_spare_servers = 6

; Recycle workers after this many requests (prevents memory leaks)
pm.max_requests = 500

Save and restart PHP-FPM:

sudo systemctl restart php-fpm
sudo nginx -t && sudo systemctl reload nginx

Understanding pm = dynamic

Three process manager modes exist:

static — always runs exactly pm.max_children workers, regardless of load. Simple but wastes RAM during quiet periods.

dynamic — starts with pm.start_servers, scales up to pm.max_children under load, scales down to pm.min_spare_servers when quiet. Best for most sites.

ondemand — starts workers only when requests arrive. Lower idle RAM usage but adds latency on the first request after an idle period. Good for very low traffic sites.

For a WordPress VPS with moderate traffic: pm = dynamic is the right choice.


Monitor After Tuning

After adjusting settings, watch PHP-FPM status:

# Check current worker count
ps aux | grep php-fpm | grep -v grep | wc -l

# Watch RAM usage overall
watch -n 2 free -m

# Check for OOM (out-of-memory) events
sudo dmesg | grep -i "out of memory"

If you see OOM events, reduce pm.max_children. If requests are consistently queuing (visible in Nginx logs as high response times), increase it.

The goal: all requests handled within available workers, no OOM events, idle RAM available for MariaDB and Redis.