php-fpm max_children-Warnung: Vor dem Aufstocken der Worker die Ursache in den Logs finden
Die php-fpm max_children-Warnung bedeutet, dass alle PHP-Worker-Prozesse gerade Anfragen bearbeiten und neue Anfragen in die Warteschlange kommen. Auf einem WordPress-Server mit Nginx, Apache und php-fpm, auf dem sich diese Warnung wochenlang angesammelt hatte, habe ich die Anfragen, die die Worker füllten, in den Logs herausgefiltert und das Problem statt mit einem höheren Wert mit einem nginx Rate Limit gelöst.
In der wöchentlichen Prüfung wurde die Warnung 27–187 Mal pro Tag protokolliert, doch im selben Zeitraum bekamen Besucher an vier Tagen 0 Fehler 502 oder 504. Gefüllt wurden die Worker nicht von Besuchern, sondern von einem Bot, der immer wieder Admin-Adressen aufrief, und von einem Scanner, der nicht vorhandene Pfade anfragte.
1️⃣ php-fpm max_children & seems busy
Wenn sich 66 Warnungen an einem Tag ansammeln, liest man das leicht als 66 Mal zu wenig Worker. Im php-fpm-Log stecken aber zwei verschiedene Warnungen, und die meisten sind Vorwarnungen, die protokolliert werden, bevor die Obergrenze erreicht ist.
[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] heißt, dass kein Worker mehr frei ist und gerade ein neuer Prozess gestartet wird; steht das Prozessmanagement auf dynamic, wird das schon bei einem kurzen Anstieg der Anfragen protokolliert. [server reached pm.max_children] ist der Moment, in dem die Obergrenze von 26 erreicht ist und kein weiterer Prozess mehr gestartet werden kann – ab dann warten neue Anfragen.
Das ist der Unterschied zwischen einer zusätzlichen Bedienung im Restaurant und Gästen, die draußen warten müssen, weil alle Plätze besetzt sind.
Nach Art gezählt ergab ein Tag 58 Mal seems busy und 6 Mal max_children. Die Prüfroutine hat beide Warnungen zu 66 zusammengezählt, tatsächlich erreicht wurde die Obergrenze aber nur sechs Mal.
grep '09-Oct-2026' /var/log/php-fpm/error.log | grep 'pool www' | grep -c 'seems busy'58grep '09-Oct-2026' /var/log/php-fpm/error.log | grep 'max_children' | cut -c2-2109-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:44Die sechs Zeitpunkte aus dem zweiten Befehl sind der Ausgangspunkt für die Ursachensuche; im nächsten Schritt nutzt du nur diese Zeitpunkte.
2️⃣ Warum mehr max_children nicht die Lösung ist
Wenn du die Obergrenze von 26 anhebst, sinkt vielleicht die Zahl der Warnungen, aber welche Anfragen die Worker gefüllt haben, ändert sich dadurch nicht.
Üblich ist, den verfügbaren Speicher durch den durchschnittlichen Speicher eines Workers zu teilen. Auf dem gemessenen Server nutzten 26 Worker im Schnitt 131 MB und höchstens 214 MB – mit dem verbleibenden verfügbaren Speicher gerechnet, gab es rein rechnerisch noch Spielraum für weitere Worker.
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.05free -mDie vier Werte der ersten Ausgabe sind Anzahl der Worker, durchschnittlicher Speicher (MB), maximaler Speicher (MB) und Summe (MB); die Spalte available in der Ausgabe von free ist der verfügbare Speicher.
Gibt es mehr Worker als CPU-Kerne, steigt die Zahl gleichzeitig bearbeiteter Anfragen durch weitere Worker nicht, es dauert nur jede einzelne Anfrage länger. Solange Besucher 0 Fehler 502 oder 504 bekommen, bringt eine höhere Obergrenze nichts und gibt den Bots und Scannern, die die Worker belegt haben, nur mehr Prozesse.
Bevor du die Obergrenze anhebst, prüfe, ob die Anfragen, die zu den Zeitpunkten der Warnungen die Worker belegt haben, von Besuchern stammen oder nicht. Wie du das in der access.log prüfst, steht im nächsten Abschnitt.
3️⃣ Ursache in der access.log verfolgen: Zeit, IP, Pfad, Statuscode
Wähle einen Zeitpunkt einer Warnung, dann steht die Antwort in der access.log dieser 1–2 Minuten. Die Reihenfolge: Zeilen nach Zeit herausziehen, nach IP zählen, dann Pfade und Statuscodes der häufigsten IPs prüfen.
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.xgrep '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 404Am 9. Oktober um 18:09 Uhr wurden in einer Minute 286 Anfragen protokolliert, davon 115 von einer einzigen IP; die Pfade waren Admin-Adressen wie /wp-admin/profile.php, und alle Statuscodes waren 302.
Der Statuscode zeigt, ob PHP erreicht wurde. Eine 200 für zwischengespeichertes HTML, das nginx direkt ausliefert, sowie 444 und 400 gehen nicht über PHP, während 302, 404 und eine nicht zwischengespeicherte 200 je einen Worker belegen. Ruft jemand ohne Login eine Admin-Adresse auf, antwortet WordPress mit einer 302 auf die Login-Seite – und diese eine 302 ist eine PHP-Ausführung.
Laufen auf einem Server zwei oder mehr Websites, landen die Zeilen beider Websites in der globalen access.log. Zuerst dachte ich, dieser Bot greife die erste Website an, aber der Abgleich mit den Logs der einzelnen Websites zeigte: 22.390 der 22.391 Anfragen gingen an die zweite Website.
4️⃣ Drei Arten von Anfragen füllten die Worker: wp-admin-Aufrufe eines Bots, 404 eines Scanners, Cache-Preload
Mit derselben Reihenfolge für alle Zeitpunkte, an denen eine Warnung protokolliert wurde, ergaben sich drei Arten von Anfragen, die die Worker füllten.
Die erste war ein Bot, der immer wieder Admin-Adressen aufrief. Eine IP schickte in drei Stunden 22.391 Anfragen, davon 9.944 an vier Pfade einschließlich /wp-admin/profile.php; da eine 302 ohne Login eine PHP-Ausführung ist, belegte sie 9.944 Mal einen Worker.
Die zweite war ein Schwachstellen-Scanner, der nicht vorhandene Pfade anfragte. Auch wenn im User-Agent Googlebot steht, ist es nicht der Googlebot: Die IP, die 14.672 Anfragen am Tag schickte, war ein virtueller Server in der Google Cloud, dessen Reverse DNS auf googleusercontent.com endet, während der Googlebot auf googlebot.com endet. Dieselbe IP wechselte zwischen bingbot, Applebot, GPTBot und ClaudeBot.
Anfragen nach Dateien wie .env blockt nginx mit 444 in 0,04 Sekunden, aber bei gewöhnlichen Pfaden, die nicht in den Sperrregeln stehen, etwa /userfiles oder /secrets.yml, erzeugt WordPress eine 404-Seite. Diese 404-Seite kostet 266 KB und 1,4–2,9 Sekunden, genauso viel wie ein normaler Beitrag, und eine IP rief sie 4.069 Mal am Tag auf.
Die dritte war der Server selbst. Speicherst du einen Beitrag oder eine Übersetzung, wird der Cache geleert und der WP Rocket Preload fragt alle Adressen neu an – 8.489 Anfragen am Tag und bis zu 60 pro Minute. Auch die Indexstatus-Prüfung von Rank Math belegte über den Action Scheduler 754 Mal am Tag einen Worker. Beides konzentrierte sich auf die Arbeitszeiten und führte zu keinen Fehlern bei Besuchern, deshalb habe ich es nicht angefasst.
Im Simulator unten siehst du, auf welcher Ebene jede Anfrage endet und ob sie einen PHP-Worker belegt.
| Anfrage | Endet bei | Antwort | Worker |
|---|---|---|---|
| wp-admin eines Bots | WordPress | 302 (zur Login-Seite) | 1 belegt |
| Scanner .env-Dateien | nginx-Sperrregel | 444 (0,04 s) | Nicht belegt |
| Scanner gewöhnlicher Fake-Pfad | WordPress | 404-Seite (266 KB · 1,4–2,9 s) | 1 belegt |
| Cache-Preload | WordPress | 200 · als Cache-Datei gespeichert | 1 belegt |
| Aufruf eines gecachten Beitrags | Cache-Datei | 200 (nginx antwortet direkt) | Nicht belegt |
| Aufruf eines nicht gecachten Beitrags | WordPress | 200 · als Cache-Datei gespeichert | 1 belegt |
5️⃣ nginx Rate Limit: Anfragen ohne Login begrenzen und echte IP wiederherstellen
Ich habe mich für ein Rate Limit statt einer Sperre entschieden. Sperrst du einen Pfad, wechselt der Scanner den Pfad; sperrst du eine IP, kommt am nächsten Tag eine andere. Begrenzt werden nur Anfragen ohne Login, die bis zu PHP durchgehen – auf 30 pro Minute und 60 gleichzeitig pro IP, alles darüber bekommt eine 429.
Für einen Server hinter einem CDN reicht diese Einstellung allein nicht. nginx sieht als IP die Adresse des CDN-Edge, sodass alle Besucher, die über diesen Edge kommen, unter einer IP zusammenfallen; du musst zuerst die Liste der Edge-IPs mit set_real_ip_from eintragen und so die echte IP aus X-Forwarded-For wiederherstellen. Anfragen mit Login-Cookie sind ausgenommen, nach User-Agent wird nichts ausgenommen.
# http-Kontext
real_ip_header X-Forwarded-For;
real_ip_recursive on;
set_real_ip_from 203.0.113.0/24; # CDN-Edge-Liste
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, die an PHP weitergibt
location @apache {
limit_req zone=php_anon burst=60 nodelay;
limit_req_status 429;
proxy_pass http://127.0.0.1:8080;
}Gecachte Seiten beantwortet try_files direkt in nginx, sie kommen also nie bei dieser location an, und admin-ajax.php ist eine eigene location und fällt deshalb nicht unter das Limit.
Teste mit gleichzeitigen Anfragen. Nacheinander gesendete Anfragen kommen nie über 30 pro Minute, weil eine 404 1,5 Sekunden dauert, und greifen daher nicht ins Limit; schickst du 100 Anfragen mit je 20 gleichzeitig, bekommst du 67 Mal 404 und 33 Mal 429 – das Limit greift.
seq 1 100 | xargs -P 20 -I{} curl -s -o /dev/null -w '%{http_code}\n' https://example.com/zz-{} | sort | uniq -cZählt die Ausgabe 429, greift das Limit. Hast du die eigene IP des Servers ausgenommen, hebe die Ausnahme zum Testen kurz auf oder führe den Test aus einem anderen Netz aus.
6️⃣ Vorher und nachher
Das Limit für Admin-Adressen ohne Login ging am Abend des 9. Oktober live, das Limit für Anfragen, die PHP erreichen, am Abend des 11. Die 302 auf Admin-Adressen sanken von 9.996 am 9. auf 11 am 10. und 1 am 11., und die IP, die in drei Stunden 22.391 Anfragen schickte, hat seit dem nächsten Tag 0 Anfragen.
Für den Scanner gibt es direkt nach der Umstellung noch keine Zahlen für einen ganzen Tag; entscheidend wird sein, wie weit die Zahl der von WordPress erzeugten 404-Seiten (2.563 am 9., 2.612 am 10., 1.630 am 11.) bei der Prüfung nächste Woche sinkt. Fehler 502 und 504 bei Besuchern lagen vorher wie nachher bei 0.
Diese Zahlen habe ich direkt aus den Logs eines WordPress-Servers mit Nginx, Apache und php-fpm gezählt, und beurteilt habe ich nicht nach der Zahl der Warnungen, sondern nach Fehlern bei Besuchern und Anfragen, die PHP erreichen.
🔢 FAQ & empfohlene Inhalte
Cache
Website-Cache & serverseitiger Cache
Erklärt Ebene für Ebene, warum gecachte Anfragen nicht über PHP laufen.
Sicherheit
Sicherheitslücken der wp-config.php
So schützt du die Konfigurationsdatei, auf die Scanner es abgesehen haben, mit Rechten und Serverregeln.
Linux
15 Kategorien von Linux-Befehlen
Eine Tabelle, die Befehle in 15 Kategorien einteilt, einschließlich grep und awk, mit denen die Logs ausgewertet wurden.
ℹ️ Affiliate-Hinweis
Die Inhalte dieser Website enthalten Affiliate-Links. Kauft ein Besucher über einen dieser Links ein Produkt oder eine Dienstleistung, erhält die Website vom Verkäufer eine Provision. Der Betrag, den der Käufer zahlt(bei Aktionsrabatten sinkt er ↓), steigt dadurch nicht. Angegebene Preise, Rabatte und Lagerbestände entsprechen dem Stand bei Erstellung und können abweichen; prüfe sie daher vor dem Kauf beim Verkäufer. Produkte werden nach eigenen Kriterien ausgewählt und bewertet, und Provisionen beeinflussen weder die Reihenfolge noch den Inhalt der Bewertungen.