An e-commerce site crashes every Monday morning. The team upgrades the VPS plan. Next Monday, same symptom: blank pages, Nginx returns 502 Bad Gateway. The real issue was not CPU — PHP-FPM had exhausted workers and RAM at once. Tuning FPM means balancing response capacity and memory ceiling. Misconfigured, the server becomes a gamble: visitors wait, or the kernel kills processes.
PHP-FPM manages a pool of PHP processes. Each dynamic request consumes a worker. If all 20 workers are busy, request 21 waits in queue — TTFB spikes. If you launch 200 on 4 GB RAM, the OOM killer steps in.
Read current state before changing anything
Before any forum copy-paste pm.max_children = 50:
# FPM processes and RAM (example www pool)
ps --no-headers -o rss,cmd -C php-fpm8.2 | awk '{sum+=$1} END {print sum/1024 " MB total"}'
Also check:
listen.queueinphp-fpm status— waiting requests.- 502/504 correlated with peaks in Nginx logs.
slowlog— scripts holding workers too long.
| Signal | Interpretation |
|---|---|
| Queue > 0 regularly | Not enough workers or scripts too slow |
| RAM swap in use | Too many workers or PHP memory leak |
| idle workers = max always | Undersized pool |
| idle workers = 0, RAM OK | Slow scripts, not necessarily small pool |
Calculate pm.max_children without magic formulas
Base formula:
max_children ≈ (RAM allocated to PHP pool) / (average RAM per worker under load)
Measure per-worker RAM with realistic traffic (not an empty homepage). Heavy WordPress often runs 60–120 MB per process. Laravel or Magento go higher.
On an 8 GB VPS where PHP can use 4 GB:
- Average worker: 100 MB → theoretical max 40.
- Keep 20–30% margin for OS, MySQL, Redis → aim for 28–32, not 40.
pm modes:
| Mode | Behaviour | Use case |
|---|---|---|
| dynamic | Spawn between min and max by load | Variable sites, good compromise |
| static | Always max active workers | Stable load, minimal latency |
| ondemand | Spawn on demand, idle timeout | Tight RAM, accept cold start |
slowlog, request_terminate_timeout and common mistakes
request_slowlog_timeout (e.g. 5 s) + slowlog = scripts monopolising workers. Fix code before adding processes.
request_terminate_timeout kills runaway scripts — useful, but can truncate legitimate imports if set too low.
Classic errors:
- Copying a production pool to shared hosting — different limits, suspended account.
- Ignoring MySQL — 50 workers × slow queries = 50 saturated DB connections.
- OPcache disabled or undersized — each worker reloads bytecode, RAM swells (see OPcache).
- High max_children + no page cache — WordPress rebuilds everything each hit.
The peak: more workers hide slow application code
This is what "unlimited" offers gloss over: unlimited visitors does not mean unlimited PHP processes.
Decide and move forward without blind spots
- Measure RAM/worker and queue length under real load.
- Calculate max_children with margin; choose dynamic or static.
- Enable slowlog, fix listed scripts.
- Retest load and TTFB; compare hosting only if the pool is healthy.
For upstream diagnosis, read High TTFB. To filter VPS with full pool access, browse the directory or comparator.
Frequently asked questions
How do I calculate pm.max_children?
RAM available to PHP divided by average worker use under load, with 20–30% margin.
dynamic, ondemand or static — which to choose?
dynamic for variable traffic; static for stable load; ondemand if RAM is very tight and you accept startup latency.
What if slowlog fills the disk?
Realistic threshold, fix scripts, not just log rotation.
Can the host block my FPM changes?
Yes on shared hosting; on VPS you control the pool — check contractual limits.
Before raising workers, ask: how many MB per request and seconds per script? If you do not know, FPM tuning is gambling — not operations.