pm.max_children : pourquoi la moyenne vous ment et met votre production en danger
Configurer PHP-FPM semble simple : on divise la RAM disponible par la mémoire moyenne d'un worker, et on obtient pm.max_children. Propre, rapide, expédié en production. Pourtant, cette approche est l'une des causes les plus fréquentes d'instabilité sur des serveurs qui, sur le papier, devraient tenir la charge.
Cet article s'appuie sur une analyse publiée par Mahmoud Alatrash sur dev.to et vous propose une méthode de dimensionnement plus robuste, adaptée aux réalités du trafic en production.
Le piège du dimensionnement par la moyenne
Toutes les requêtes ne se ressemblent pas. La majorité de votre trafic est probablement légère : appels API simples, lectures rapides en base de données. Mais certaines requêtes sont bien plus gourmandes : exports de données, rapports complexes, sérialisation de gros volumes.
Prenons un exemple concret :
| Indicateur | Valeur | |---|---| | Mémoire moyenne d'un worker | 50 Mo | | Mémoire P95 d'un worker | 120 Mo | | RAM disponible sur le serveur | 8 Go |
Avec la formule classique :
pm.max_children = 8192 Mo / 50 Mo = 163 workers
En apparence, c'est cohérent. En pratique, si 20 % de vos workers traitent simultanément des requêtes lourdes à 120 Mo, vous consommez déjà :
163 × 0.20 × 120 Mo + 163 × 0.80 × 50 Mo ≈ 3912 Mo + 6520 Mo = 10 432 Mo
Votre serveur commence à swapper, le kernel OOM-killer entre en action, et vos workers tombent les uns après les autres. Ce scénario n'est pas exceptionnel — il correspond exactement à un pic de trafic normal sur une application métier.
La bonne métrique : le P95 RSS mesuré sous charge réelle
Plutôt que de travailler sur la mémoire moyenne, il faut dimensionner pour ce qui casse réellement votre serveur : la mémoire au 95e percentile (P95), mesurée sous trafic de pointe.
Comment mesurer le P95 RSS
Le RSS (Resident Set Size) correspond à la mémoire physique réellement occupée par un processus. Pour l'observer sur vos workers PHP-FPM en conditions réelles :
# Lister les workers FPM et leur consommation mémoire RSS (en Ko)
ps -eo pid,rss,comm | grep php-fpm | sort -k2 -n
Ou via le statut FPM directement (à activer dans votre pool) :
pm.status_path = /fpm-status
Une fois les données collectées, calculez le P95 : triez vos valeurs et prenez celle au 95e percentile. C'est cette valeur, et non la moyenne, qui doit servir de base au calcul.
Appliquer un facteur de sécurité
Une fois le P95 identifié, appliquez un facteur de sécurité de 1.2x pour absorber les variations imprévues :
pm.max_children = RAM disponible / (P95_RSS × 1.2)
Avec notre exemple :
pm.max_children = 8192 Mo / (120 Mo × 1.2) ≈ 56 workers
Soixante workers au lieu de cent soixante-trois. La différence est significative, mais ce sont 56 workers stables plutôt que 163 workers qui risquent de faire tomber le serveur au premier pic.
Plafonner par le CPU, pas seulement par la RAM
La mémoire n'est pas le seul facteur limitant. Le nombre de workers doit également être cohérent avec la capacité CPU de votre serveur, selon la nature de vos traitements :
- Applicatifs I/O-bound (beaucoup d'attente : requêtes BDD, appels API externes) : 8 à 12 workers par cœur CPU
- Applicatifs CPU-bound (calculs intensifs, traitement d'images, cryptographie) : 2 à 4 workers par cœur CPU
Sur un serveur 4 cœurs avec une application majoritairement I/O-bound :
Plafond CPU = 4 × 10 = 40 workers
La valeur finale de pm.max_children est alors le minimum entre le calcul mémoire et le plafond CPU. Dépasser la capacité CPU génère de la contention, des temps de réponse qui s'allongent et une dégradation progressive de la qualité de service.
Paramètres complémentaires à ne pas négliger
Bien dimensionner pm.max_children est nécessaire, mais insuffisant. Deux autres directives sont essentielles à la stabilité :
pm.max_requests : recycler les workers
Les applications PHP accumulent parfois des fuites mémoire au fil du temps. pm.max_requests force le recyclage d'un worker après un certain nombre de requêtes traitées, ce qui garantit que la mémoire est libérée régulièrement :
pm.max_requests = 500
Une valeur entre 200 et 1000 est généralement adaptée selon la nature de l'application.
request_terminate_timeout : tuer les workers bloqués
Sans cette directive, un worker bloqué sur une requête longue ou une connexion externe qui ne répond pas peut rester actif indéfiniment, monopolisant une slot parmi vos workers disponibles :
request_terminate_timeout = 30s
Adaptez cette valeur aux besoins légitimes de votre application (certains traitements batch peuvent nécessiter des timeouts plus longs), mais veillez à toujours définir une limite explicite.
Surveiller le statut FPM
La page de statut PHP-FPM expose des métriques précieuses pour détecter la saturation avant qu'elle ne devienne critique :
curl http://localhost/fpm-status?full
Les indicateurs à surveiller en priorité :
listen queue: si cette valeur monte, vos workers ne suffisent plus à absorber les requêtes entrantesactive processesvsmax children reached: indique si le plafond a déjà été atteint
Intégrez ces métriques dans votre stack de monitoring (Prometheus, Datadog, Grafana...) pour être alerté dès les premiers signes de saturation.
Conclusion
La configuration de pm.max_children basée sur la mémoire moyenne est une simplification dangereuse. Elle fonctionne bien par temps calme, mais elle transforme chaque pic de trafic en incident potentiel.
Adopter une approche fondée sur le P95 RSS mesuré sous charge réelle, avec un facteur de sécurité et un plafonnement par CPU, vous permet de construire une configuration qui résiste aux conditions réelles de production — pas seulement aux conditions idéales.
En résumé :
- Mesurez le P95 RSS de vos workers sous trafic de pointe
- Appliquez un facteur de sécurité de 1.2x
- Plafonnez selon le type de charge (I/O-bound ou CPU-bound)
- Configurez
pm.max_requestsetrequest_terminate_timeout- Surveillez activement la page de statut FPM
Une configuration PHP-FPM solide est invisible en production — et c'est exactement l'objectif.