PHP-FPM on Performance VPS
PHP-FPM runs PHP scripts in processes separate from the web server. On a HolyCloud Performance VPS, a poorly sized pool causes 502 errors (exhausted workers) or saturated RAM (too many max_children).
Prerequisites
- VPS with Nginx or Apache + PHP-FPM (e.g. PHP 8.2/8.3)
- sudo access and knowledge of the pool path (
/etc/php/8.x/fpm/pool.d/) - Per-worker memory estimate (see below)
- Test or staging site to validate under load
Locate configuration
php-fpm8.3 -tt 2>/dev/null | head
ls /etc/php/8.3/fpm/pool.d/
sudo nano /etc/php/8.3/fpm/pool.d/www.conf
Typical [www] pool:
[www]
user = www-data
group = www-data
listen = /run/php/php8.3-fpm.sock
pm = dynamic
pm.max_children = 20
pm.start_servers = 4
pm.min_spare_servers = 2
pm.max_spare_servers = 6
pm.max_requests = 500
Calculate pm.max_children
Starting formula:
max_children ≈ (RAM available for PHP) / (average memory per worker)
Measure worker memory under real load:
ps -o rss,cmd -C php-fpm8.3 | awk '{sum+=$1} END {print sum/NR/1024 " MB moyenne"}'
Example: 8 GB RAM VPS, OS + MySQL + Redis ≈ 3 GB left for PHP, worker ~80 MB:
5120 MB / 80 MB ≈ 64 → start at 40–50 and increase gradually
pm modes: which to choose?
| ------ | ------- |
|---|---|
dynamic |
Default web — balance latency / RAM |
ondemand |
Very sporadic traffic — slower startup |
static |
Constant, predictable traffic — fixed RAM |
Frequent e-commerce production:
pm = dynamic
pm.max_children = 48
pm.start_servers = 8
pm.min_spare_servers = 8
pm.max_spare_servers = 16
Timeouts and queues
request_terminate_timeout = 120s
request_slowlog_timeout = 5s
slowlog = /var/log/php8.3-fpm-slow.log
Avoid request_terminate_timeout = 0 (unlimited) — a stuck script monopolizes a worker.
Opcache (essential)
File /etc/php/8.3/mods-available/opcache.ini:
opcache.enable=1
opcache.memory_consumption=256
opcache.max_accelerated_files=20000
opcache.validate_timestamps=0
validate_timestamps=0in production after controlled deployment; set1in dev.
Restart:
sudo systemctl restart php8.3-fpm
Nginx: gateway to the socket
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_buffers 16 16k;
fastcgi_buffer_size 32k;
fastcgi_read_timeout 120s;
}
Verification under load
# FPM status (if pm.status_path enabled)
curl -s http://127.0.0.1/status?full
# logs
sudo tail -f /var/log/php8.3-fpm.log
Light HTTP test:
hey -n 1000 -c 50 https://votre-site.tld/
Monitor during the test:
watch -n1 'ps -C php-fpm8.3 | wc -l; free -h'
Advanced tuning
; avoid long leaks
pm.max_requests = 1000
; process manager — ping for healthcheck
pm.status_path = /fpm-status
ping.path = /fpm-ping
Protect /fpm-status by IP or Nginx auth.
Troubleshooting
| ---------- | -------- |
|---|---|
| 502 Bad Gateway | Workers exhausted → increase max_children or optimize code |
| Swap increasing | Reduce max_children, enable Redis cache |
| PHP CPU 100% | Profile (Xdebug off in prod), object cache |
| Slowlog filling | SQL queries, loops, synchronous API calls |
Need help?
Attach to support: www.conf excerpt, free -h, slowlog excerpt (no sensitive client data), and Performance VPS plan.