Introduction
PHP-FPM (FastCGI Process Manager) est le moyen standard d'executer PHP en prod derriere Nginx ou Apache. Il gere :
- Un pool de workers PHP pre-forks
- Le recyclage memoire (max_requests)
- L'isolation par utilisateur / site
- Le slow log pour identifier les requetes lentes
- Le healthcheck pour le load balancer
Une mauvaise config = 502 Bad Gateway, OOM, RAM gaspillee. Une bonne config = stabilite et performance.
Prerequis
- VPS Debian / Ubuntu
- Nginx ou Apache deja installe
- Connaissance de base PHP
Etape 1 : Installation
sudo apt update
sudo apt install -y php8.2-fpm php8.2-mysql php8.2-redis php8.2-curl php8.2-mbstring php8.2-xml php8.2-zip php8.2-gd php8.2-intl
sudo systemctl enable --now php8.2-fpm
Etape 2 : Anatomie d'un pool
/etc/php/8.2/fpm/pool.d/www.conf est le pool par defaut.
[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 = 50
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 10
pm.max_requests = 500
pm.status_path = /fpm-status
ping.path = /fpm-ping
slowlog = /var/log/php-fpm/slow.log
request_slowlog_timeout = 5s
request_terminate_timeout = 60s
catch_workers_output = yes
php_admin_value[error_log] = /var/log/php-fpm/error.log
php_admin_flag[log_errors] = on
Etape 3 : Process Manager (pm)
3 modes :
- static : nombre fixe de workers (
pm.max_children). Predictible, RAM stable. Convient pour des charges constantes. - dynamic : varie entre min et max. Bon equilibre.
- ondemand : workers crees a la demande, tues apres
pm.process_idle_timeout. Tres econome en RAM, mais latence accrue.
Recommandation prod : dynamic pour la plupart des cas.
Etape 4 : Calcul du max_children
Formule : pm.max_children = (RAM disponible) / (RAM moyenne par worker)
Mesurer la RAM par worker :
ps -ylC php-fpm8.2 --sort:rss | head
Colonne RSS en Ko. Si moyenne 50 Mo, et 4 Go de RAM disponible pour PHP :
pm.max_children = 4096 / 50 = ~80
Restez conservateur : 60-70.
Etape 5 : Un pool par site
Pour isoler vos sites, creez un pool dedie par site :
sudo nano /etc/php/8.2/fpm/pool.d/site1.conf
[site1]
user = site1
group = site1
listen = /run/php/php8.2-site1.sock
listen.owner = www-data
listen.group = www-data
pm = dynamic
pm.max_children = 20
pm.start_servers = 3
pm.min_spare_servers = 2
pm.max_spare_servers = 5
slowlog = /var/log/php-fpm/site1-slow.log
request_slowlog_timeout = 3s
php_admin_value[open_basedir] = /var/www/site1:/tmp
php_admin_value[upload_tmp_dir] = /var/www/site1/tmp
php_admin_value[session.save_path] = /var/www/site1/sessions
php_admin_value[memory_limit] = 256M
Creez l'user :
sudo useradd -r -s /bin/false site1
sudo mkdir -p /var/www/site1/{tmp,sessions}
sudo chown -R site1:site1 /var/www/site1
Restart :
sudo systemctl restart php8.2-fpm
Etape 6 : Configuration Nginx pour le pool
server {
listen 443 ssl http2;
server_name site1.fr;
root /var/www/site1;
index index.php;
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.2-site1.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
Etape 7 : Opcache
Opcache compile les scripts PHP en bytecode et le garde en RAM. C'est essentiel en prod.
/etc/php/8.2/fpm/php.ini :
opcache.enable=1
opcache.enable_cli=0
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=10000
opcache.validate_timestamps=1
opcache.revalidate_freq=60
opcache.fast_shutdown=1
opcache.save_comments=1
En prod stable, mettez opcache.validate_timestamps=0 pour ne plus jamais re-verifier le disque (10-15% de gain perf, mais necessite un systemctl reload php8.2-fpm apres chaque deploiement).
Etape 8 : JIT (PHP 8+)
Le JIT compile certaines portions a la volee :
opcache.jit=1255
opcache.jit_buffer_size=128M
Le mode 1255 est equilibre. Gain : 5-15% sur du PHP intense (calculs), peu visible sur du Wordpress / Laravel classique.
Etape 9 : Slow log
Pour identifier les scripts lents :
slowlog = /var/log/php-fpm/slow.log
request_slowlog_timeout = 3s
sudo tail -f /var/log/php-fpm/slow.log
Format :
[16-May-2026 14:32:01] [pool www] pid 12345
script_filename = /var/www/site1/slow.php
[0x00007f8c12345000] sleep() /var/www/site1/slow.php:5
[0x00007f8c12345001] processData() /var/www/site1/slow.php:10
Identifie precisement la stack trace au moment du depassement.
Etape 10 : FPM status + monitoring
pm.status_path = /fpm-status
ping.path = /fpm-ping
Nginx :
location ~ ^/(fpm-status|fpm-ping)$ {
access_log off;
allow 127.0.0.1;
deny all;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
include fastcgi_params;
}
Recuperer le status :
curl http://localhost/fpm-status?full
Affiche :
pool: www
process manager: dynamic
total processes: 10
active processes: 2
idle processes: 8
listen queue: 0
max listen queue: 0
slow requests: 3
Integrez dans Prometheus avec php-fpm_exporter.
Etape 11 : Healthcheck pour load balancer
ping.path retourne pong si FPM est UP :
curl http://localhost/fpm-ping
Utilisez ce path dans votre load balancer (HAProxy, Traefik, AWS ELB).
Etape 12 : memory_limit et timeouts
Per pool :
php_admin_value[memory_limit] = 256M
php_admin_value[max_execution_time] = 60
php_admin_value[max_input_time] = 60
php_admin_value[post_max_size] = 64M
php_admin_value[upload_max_filesize] = 64M
request_terminate_timeout (sigkill apres ce delai si pas de reponse) :
request_terminate_timeout = 65s
Mettez-le legerement plus grand que max_execution_time pour laisser PHP terminer proprement avant le kill.
Depannage
502 Bad Gateway
PHP-FPM down, surcharge, ou socket pas accessible :
sudo systemctl status php8.2-fpm
sudo tail -f /var/log/php-fpm/error.log
sudo journalctl -u php8.2-fpm -n 50
Verifiez les permissions du socket :
ls -la /run/php/php8.2-fpm.sock
"server reached pm.max_children setting"
Saturation. Augmentez pm.max_children, ou identifiez les requetes lentes via le slow log.
Memory leak
Reduisez pm.max_requests (recycle les workers plus souvent) :
pm.max_requests = 100
"MySQL has gone away"
Les workers FPM gardent des connexions ouvertes longtemps. Augmentez wait_timeout MySQL ou utilisez PDO::ATTR_PERSISTENT = false.
File upload limite
Verifiez upload_max_filesize, post_max_size, et la limite Nginx client_max_body_size.
Commandes utiles
# Status
sudo systemctl status php8.2-fpm
# Reload (no downtime)
sudo systemctl reload php8.2-fpm
# Tester la config
sudo php-fpm8.2 -t
# Lister les pools
sudo php-fpm8.2 -tt
# Monitorer en temps reel
watch 'curl -s http://localhost/fpm-status'
# RAM utilisee par pool
ps -eo rss,cmd | grep php-fpm | awk '{sum+=$1} END {print sum/1024 " MB"}'
# Top des process FPM
ps aux --sort=-rss | grep php-fpm | head
Conclusion
Une config PHP-FPM saine vous donne :
- Memoire predictible
- Isolation entre sites
- Visibilite sur les requetes lentes
- Healthcheck pour load balancer
Pour aller plus loin :
- Combinez avec OPcache file cache (cache entre restarts)
- Migrez vers RoadRunner pour des perf extremes (PHP daemon mode)
- Utilisez FrankenPHP pour avoir PHP + serveur web integre
Ressources
- Documentation officielle : https://www.php.net/manual/en/install.fpm.configuration.php
- php-fpm_exporter : https://github.com/hipages/php-fpm_exporter
- RoadRunner : https://roadrunner.dev
- FrankenPHP : https://frankenphp.dev


















