Avertissement php-fpm max_children : trouver la cause dans les journaux avant d'ajouter des workers

Mention d'affiliation v3.1
Mention d'affiliation v3.2 (mobile)
Environnement de test Famille RHELAlmaLinux 9.8Compte rootVérifié le 12/10/2026

L'avertissement php-fpm max_children signale que tous les processus workers PHP traitent des requêtes et que les nouvelles requêtes passent en file d'attente. Sur un serveur WordPress Nginx + Apache avec php-fpm où cet avertissement s'accumulait depuis des semaines, j'ai isolé dans les journaux les requêtes qui remplissaient les workers : au lieu de relever la valeur du réglage, la solution a été une limitation de débit dans nginx.

Lors du contrôle hebdomadaire, l'avertissement était enregistré 27 à 187 fois par jour, mais sur la même période, les visiteurs n'ont reçu aucune erreur 502 ou 504 en quatre jours : ce qui remplissait les workers, ce n'étaient pas les visiteurs, mais un robot qui demandait sans cesse les adresses d'administration et un scanner qui demandait des chemins inexistants.

1️⃣ php-fpm max_children & seems busy

Quand l'avertissement s'accumule 66 fois par jour, on lit facilement qu'il a manqué des workers 66 fois. Le journal de php-fpm mêle deux avertissements de nature différente, et la plupart sont des avertissements préalables, enregistrés avant d'atteindre la limite.

[09-Oct-2026 00:16:18] WARNING: [pool www] seems busy (you may need to increase pm.start_servers, or pm.min/max_spare_servers), spawning 8 children, there are 0 idle, and 15 total children
[09-Oct-2026 13:02:05] WARNING: [pool www] server reached pm.max_children setting (26), consider raising it

[seems busy] indique qu'il ne reste plus de worker en attente et qu'un nouveau processus est en cours de création ; avec une gestion des processus en dynamic, il apparaît au moindre pic de requêtes. [server reached pm.max_children] marque le moment où la limite de 26 est atteinte et où aucun processus de plus ne peut être créé : à partir de là, les nouvelles requêtes attendent.

C'est la différence, au restaurant, entre appeler un serveur de plus et faire attendre dehors des clients parce que toutes les tables sont prises.

En les comptant par type sur une journée, on obtient 58 seems busy et 6 max_children. La routine de contrôle a additionné les deux avertissements pour signaler 66 occurrences, mais la limite n'a réellement été atteinte que six fois.

grep '09-Oct-2026' /var/log/php-fpm/error.log | grep 'pool www' | grep -c 'seems busy'
58
grep '09-Oct-2026' /var/log/php-fpm/error.log | grep 'max_children' | cut -c2-21
09-Oct-2026 13:02:05
09-Oct-2026 13:52:10
09-Oct-2026 18:09:20
09-Oct-2026 18:42:17
09-Oct-2026 18:46:07
09-Oct-2026 19:12:44

Les six horaires affichés par la deuxième commande servent de point de départ pour remonter à la cause ; l'étape suivante n'utilise que ces horaires.

2️⃣ Pourquoi augmenter max_children n'est pas la réponse

Relever la limite de 26 peut réduire le nombre d'avertissements, mais cela ne change rien à la nature des requêtes qui remplissent les workers.

Le calcul habituel consiste à diviser la mémoire disponible par la mémoire moyenne d'un worker. Sur le serveur mesuré, les 26 workers utilisaient en moyenne 131 Mo, au maximum 214 Mo, soit 3,4 Go au total, et 4,1 Go des 15,7 Go de mémoire étaient disponibles : en chiffres, on pourrait en créer plus de 30 de plus.

ps -o rss= -u www -C php-fpm | awk '{s+=$1; n++; if($1>m) m=$1} END{print n, s/n/1024, m/1024, s/1024}'
26 130.579 214.27 3395.05
free -m
               total        used        free      shared  buff/cache   available
Mem:           15783       11658         397         340        4383        4124
Swap:           8191        1615        6576
nproc
4

Les quatre valeurs de la première sortie sont le nombre de workers, la mémoire moyenne (Mo), la mémoire maximale (Mo) et le total (Mo) ; la colonne available de la sortie de free est la mémoire disponible.

Avec un CPU de 4 cœurs, ajouter des workers n'augmente pas le nombre de requêtes traitées en même temps : seul le temps de chaque requête s'allonge. Tant que les visiteurs ne reçoivent aucune erreur 502 ou 504, relever la limite ne rapporte rien et donne plus de processus aux robots et aux scanners qui occupaient les workers.

Avant de relever la limite, il faut vérifier si les requêtes qui occupaient les workers aux heures des avertissements venaient de visiteurs ou non ; la méthode de vérification dans access.log est détaillée dans la section suivante.

3️⃣ Remonter à la cause dans access.log : heure, IP, chemin, code de réponse

Choisissez l'heure d'un avertissement : la réponse se trouve dans l'access.log de ces 1 ou 2 minutes. On extrait d'abord les lignes par l'heure, on les compte par IP, puis on regarde les chemins et les codes de réponse des IP en tête.

grep '09/Oct/2026:18:09' /var/log/nginx/access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head
    115 140.245.x.x
     61 187.15.x.x
     17 3.36.x.x
      8 91.239.x.x
      4 79.127.x.x
      3 109.248.x.x
      3 109.248.x.x
      3 109.248.x.x
      2 79.127.x.x
      2 40.77.x.x
grep '09/Oct/2026:18:09' /var/log/nginx/access.log | awk '{print $7, $9}' | sort | uniq -c | sort -rn | head
     55 / 200
     13 /wp-login.php 404
     13 /wp-admin/plugins.php 302
     13 /wp-admin/index.php 302
     13 /wp-admin/edit.php 302
     12 /wp-admin/profile.php 302
      4 / 499
      3 /xmlrpc.php 302
      2 /xmlrpc.php 499
      2 /wp-content/themes/kinsta/images/duos/small/wheelchair.svg 404

Le 9 octobre à 18 h 09, 286 requêtes ont été enregistrées en une minute, dont 115 d'une seule IP ; les chemins étaient des adresses d'administration comme /wp-admin/profile.php, et les codes de réponse tous des 302.

Le code de réponse indique si la requête est arrivée jusqu'à PHP. Un 200 pour du HTML en cache servi directement par nginx, ainsi que les 444 et 400, ne passent pas par PHP, tandis que les 302, les 404 et les 200 non mis en cache occupent un worker. Quand on demande une adresse d'administration sans être connecté, WordPress répond par un 302 vers la page de connexion, et chacun de ces 302 correspond à une exécution de PHP.

Quand un serveur héberge plusieurs sites, l'access.log global contient les lignes de tous ces sites. J'ai d'abord cru que ce robot attaquait le premier site, mais en comparant avec les journaux par site, 22 390 des 22 391 requêtes concernaient le deuxième site.

4️⃣ Trois types de requêtes remplissaient les workers : le wp-admin d'un robot, les 404 d'un scanner, le préchargement du cache

En appliquant la même méthode à toutes les heures d'avertissement, les requêtes qui remplissaient les workers étaient de trois types.

Le premier était un robot qui demandait sans cesse les adresses d'administration. Une IP a envoyé 22 391 requêtes en trois heures, dont 9 944 sur quatre chemins, parmi lesquels /wp-admin/profile.php ; comme chaque 302 sans connexion correspond à une exécution de PHP, cela a occupé un worker 9 944 fois.

Le deuxième était un scanner de vulnérabilités qui demandait des chemins inexistants. Même si le User-Agent indique Googlebot, ce n'est pas Googlebot : l'IP qui a envoyé 14 672 requêtes en une journée était un serveur virtuel Google Cloud dont le DNS inverse se terminait par googleusercontent.com, alors que celui de Googlebot se termine par googlebot.com. La même IP alternait entre bingbot, Applebot, GPTBot et ClaudeBot.

Les requêtes de fichiers comme .env sont bloquées par nginx en 444 en 0,04 seconde, mais pour des chemins ordinaires absents des règles de blocage, comme /userfiles ou /secrets.yml, WordPress génère une page 404. Cette page 404 pèse 266 Ko et prend 1,4 à 2,9 secondes, le même coût que la génération d'un article normal, et une seule IP en a demandé 4 069 par jour.

Le troisième était le serveur lui-même. Quand on enregistre un article ou une traduction, le cache est vidé et le préchargement de WP Rocket redemande toutes les adresses : 8 489 requêtes par jour, jusqu'à 60 par minute. La vérification de l'état d'indexation de Rank Math occupait aussi un worker 754 fois par jour via Action Scheduler. Ces deux tâches se concentrent sur les heures de travail et ne provoquent pas d'erreurs côté visiteurs : je les ai donc exclues des mesures.

Dans le simulateur ci-dessous, vous voyez à quelle couche chaque requête s'arrête et si elle occupe un worker PHP.

🧭 Simulateur du chemin des requêtesPage d'administration wp-adminSimulateur explicatif
🗂️ Requêtes qui remplissaient les workers
Requêtes wp-admin d'un robot
Quand on demande une adresse d'administration sans être connecté, WordPress répond par un 302 vers la page de connexion. Chacun de ces 302 correspond à une exécution de PHP.
Scanner : fichiers de type .env
Les requêtes de fichiers comme .env sont coupées en 444 par une règle de blocage nginx. 0,04 seconde, sans passer par PHP.
Scanner : faux chemins ordinaires
Pour des chemins ordinaires absents des règles de blocage, comme /userfiles ou /secrets.yml, WordPress génère une page 404. 266 Ko, 1,4 à 2,9 secondes : le même coût que la génération d'un article normal.
Préchargement du cache
Quand on enregistre un article ou une traduction, le cache est vidé et le préchargement de WP Rocket redemande toutes les adresses. Jusqu'à 60 par minute, chaque requête occupant un worker.
Visite d'un article en cache
S'il existe un fichier de cache, nginx répond directement, sans passer par PHP.
Visite d'un article non mis en cache
Sans fichier de cache, PHP génère la page et enregistre ce HTML comme fichier de cache.
Vue d'ensemble
Une requête descend de haut en bas et s'arrête à la couche où la réponse se termine. Seules les requêtes qui descendent jusqu'à WordPress occupent un worker PHP.
RequêteCouche d'arrêtRéponseWorkers
wp-admin d'un robotWordPress302 (vers la page de connexion)1 occupé
Scanner, fichiers .envRègle de blocage nginx444 (0.04 s)Non occupé
Scanner, faux chemin ordinaireWordPressPage 404 (266 Ko · 1.4–2.9 s)1 occupé
Préchargement du cacheWordPress200 · enregistré en fichier de cache1 occupé
Visite d'un article en cacheFichier de cache200 (nginx répond directement)Non occupé
Visite d'un article non mis en cacheWordPress200 · enregistré en fichier de cache1 occupé
Un simulateur explicatif qui ne fonctionne que dans votre navigateur. Les réponses et les durées viennent des journaux et des mesures d'un serveur Nginx + Apache en octobre 2026.

5️⃣ Limitation de débit nginx : limiter les requêtes sans connexion et rétablir la vraie IP

J'ai choisi la limitation de débit plutôt que le blocage : si l'on bloque un chemin, le scanner en change, et si l'on bloque une IP, une autre se connecte le lendemain. Seules les requêtes sans connexion qui atteignent PHP sont limitées à 30 par minute et par IP, avec des pointes de 60, et l'excédent reçoit une réponse 429.

Pour un serveur derrière un CDN, ce réglage seul ne convient pas : l'IP que voit nginx est l'adresse d'un nœud edge du CDN, si bien que tous les visiteurs passant par ce nœud seraient regroupés sous une seule IP. Il faut d'abord enregistrer la liste des IP edge avec set_real_ip_from pour rétablir la vraie IP de X-Forwarded-For. Les requêtes avec un cookie de connexion sont exemptées, mais pas sur la base du User-Agent.

# contexte http
real_ip_header X-Forwarded-For;
real_ip_recursive on;
set_real_ip_from 203.0.113.0/24;   # liste des nœuds edge du CDN
map $http_cookie $php_anon_key {
    default                 $binary_remote_addr;
    ~*wordpress_logged_in   '';
}
limit_req_zone $php_anon_key zone=php_anon:10m rate=30r/m;

# location qui transmet à PHP
location @apache {
    limit_req zone=php_anon burst=60 nodelay;
    limit_req_status 429;
    proxy_pass http://127.0.0.1:8080;
}

Les pages en cache sont servies dans nginx par try_files et n'arrivent donc jamais à cette location, et admin-ajax.php, qui a sa propre location, n'est pas concerné par la limite.

On vérifie avec des requêtes simultanées. En séquence, chaque 404 prend 1,5 seconde : on ne dépasse jamais 30 requêtes par minute et la limite ne se déclenche pas. En envoyant 100 requêtes, 20 à la fois, on obtient 67 réponses 404 et 33 réponses 429, ce qui confirme que la limite fonctionne.

seq 1 100 | xargs -P 20 -I{} curl -s -o /dev/null -w '%{http_code}\n' https://example.com/zz-{} | sort | uniq -c

Si la sortie compte des 429, la limite fonctionne ; si vous avez exempté l'IP du serveur lui-même, levez temporairement l'exemption pendant le test ou lancez-le depuis un autre réseau.

6️⃣ Avant et après

La limite sur les adresses d'administration sans connexion a été appliquée le soir du 9 octobre, celle sur les requêtes qui atteignent PHP le soir du 11. Les 302 sur les adresses d'administration sont passés de 9 996 le 9 à 11 le 10, puis à 1 le 11, et l'IP qui avait envoyé 22 391 requêtes en trois heures n'en a plus envoyé aucune dès le lendemain.

Côté scanner, la limite venait d'être appliquée et il n'y a pas encore de chiffres sur une journée complète : on jugera au contrôle de la semaine suivante, d'après la baisse du nombre de pages 404 générées par WordPress (2 563 le 9, 2 612 le 10, 1 630 le 11). Les erreurs 502 et 504 côté visiteurs sont restées à 0 avant comme après.

Ces chiffres ont été comptés directement dans les journaux d'un serveur WordPress tournant avec Nginx, Apache et php-fpm, et j'ai jugé non pas sur le nombre d'avertissements, mais sur les erreurs des visiteurs et le nombre de requêtes atteignant PHP.

📊Avant et après la limitation de débitPar jour · décompte des journaux
AvantAprès
Réponses 302 sur les adresses d'administration
Les 302 envoyés par WordPress aux requêtes wp-admin sans connexion. La limite sur les adresses d'administration sans connexion est appliquée depuis le soir du 9 octobre.
02,4994,9987,4979,996
10/99,996Avant
10/1011Après
10/111Après
Requêtes de l'IP du robot
Requêtes quotidiennes de l'IP qui avait envoyé 22 391 requêtes en trois heures.
05,59811,19616,79322,391
10/922,391Avant
10/100Aucune requête
10/110Aucune requête
Pages 404 générées par WordPress
Les requêtes du scanner sur de faux chemins ordinaires. La limite sur les requêtes atteignant PHP étant appliquée depuis le soir du 11 octobre, on jugera avec les chiffres du contrôle de la semaine suivante.
06531,3061,9592,612
10/92,563Avant
10/102,612Avant
10/111,630Avant · appliquée le soir du 11
Chiffres du décompte quotidien de l'access.log de nginx du 9 au 11 octobre 2026.

🔢 FAQ & contenus recommandés

dynamic crée et arrête les workers selon le volume de requêtes, et enregistre l'avertissement seems busy quand il n'y a plus de worker en attente. static garde toujours max_children workers, sans délai de création ni avertissement, mais occupe la mémoire en permanence. Le serveur de cet article est resté en dynamic avec 26 workers ; pour un serveur qui a de la marge en mémoire et un volume de requêtes constant toute la journée, static convient.

Activez-le quand, aux heures des avertissements, certaines requêtes semblent s'exécuter longtemps. En indiquant dans la configuration du pool un chemin [slowlog] et un [request_slowlog_timeout], l'emplacement d'exécution des requêtes qui dépassent ce délai est enregistré. Dans le cas de cet article, la cause était le nombre de requêtes : le décompte de l'access.log a suffi, sans slowlog.

La limite ne s'applique qu'aux requêtes sans connexion qui atteignent PHP sans passer par le cache, et seul l'excédent reçoit un 429 quand une IP dépasse 30 requêtes par minute et des pointes de 60. Dans le décompte du 11 octobre, la seule IP au-delà de ce seuil était un scanner, et le dépassement se lève en moins d'une minute : les requêtes suivantes reçoivent une réponse normale.

Je ne l'ai pas réduit : le préchargement se concentrait sur les heures où des articles et des traductions étaient enregistrés et ne provoquait pas d'erreurs côté visiteurs. Le critère n'est pas le nombre d'avertissements, mais la présence d'erreurs 502 ou 504 côté visiteurs : si ce nombre est de 0, on laisse le préchargement tel quel.

ℹ️ Mention d'affiliation
Le contenu de ce site contient des liens d'affiliation. Lorsqu'un visiteur achète un produit ou un service via l'un de ces liens, le site perçoit une commission du vendeur. Le montant payé par l'acheteur(il baisse lors des promotions ↓), lui, n'augmente pas. Les prix, remises et stocks indiqués correspondent à la date de rédaction et peuvent différer ; vérifiez-les donc auprès du vendeur avant d'acheter. Les produits sont choisis et évalués selon nos propres critères, et les commissions n'influencent ni l'ordre de présentation ni le contenu des évaluations.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Prouvez que vous êtes humain : 2   +   3   =