php-fpm max_children-Warnung: Vor dem Aufstocken der Worker die Ursache in den Logs finden

Testumgebung RHEL-FamilieAlmaLinux 9.8root-KontoGeprüft am 2026-10-12

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'
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

Die 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.05
free -m

Die 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.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

Am 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.

🧭 Request-Pfad-Simulatorwp-admin Admin-SeiteErklär-Simulator
🗂️ Anfragen, die die Worker füllten
wp-admin-Anfragen eines Bots
Ruft jemand ohne Login eine Admin-Adresse auf, antwortet WordPress mit einer 302 auf die Login-Seite. Diese eine 302 ist eine PHP-Ausführung.
Scanner: .env-Dateien
Anfragen nach Dateien wie .env beendet die nginx-Sperrregel mit 444. 0,04 Sekunden, PHP wird nicht berührt.
Scanner: gewöhnliche Fake-Pfade
Bei gewöhnlichen Pfaden, die nicht in den Sperrregeln stehen, etwa /userfiles oder /secrets.yml, erzeugt WordPress eine 404-Seite. 266 KB und 1,4–2,9 Sekunden – genauso teuer wie ein normaler Beitrag.
Cache-Preload
Speicherst du einen Beitrag oder eine Übersetzung, wird der Cache geleert und der WP Rocket Preload fragt alle Adressen neu an. Bis zu 60 pro Minute, jede Anfrage belegt einen Worker.
Aufruf eines gecachten Beitrags
Gibt es eine Cache-Datei, antwortet nginx direkt, ohne PHP.
Aufruf eines nicht gecachten Beitrags
Gibt es keine Cache-Datei, erzeugt PHP die Seite und speichert das HTML als Cache-Datei.
Gesamter Ablauf
Eine Anfrage läuft von oben nach unten und stoppt auf der Ebene, auf der die Antwort endet. Einen PHP-Worker belegen nur Anfragen, die bis zu WordPress durchgehen.
AnfrageEndet beiAntwortWorker
wp-admin eines BotsWordPress302 (zur Login-Seite)1 belegt
Scanner .env-Dateiennginx-Sperrregel444 (0,04 s)Nicht belegt
Scanner gewöhnlicher Fake-PfadWordPress404-Seite (266 KB · 1,4–2,9 s)1 belegt
Cache-PreloadWordPress200 · als Cache-Datei gespeichert1 belegt
Aufruf eines gecachten BeitragsCache-Datei200 (nginx antwortet direkt)Nicht belegt
Aufruf eines nicht gecachten BeitragsWordPress200 · als Cache-Datei gespeichert1 belegt
Ein Erklär-Simulator, der nur in deinem Browser läuft. Antworten und Zeiten stammen aus Logs und Messungen eines Servers mit Nginx + Apache im Oktober 2026.

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 -c

Zä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.

📊Vor und nach dem Rate LimitPro Tag · Log-Auswertung
VorherNachher
302-Antworten auf Admin-Adressen
302, die WordPress auf wp-admin-Anfragen ohne Login geschickt hat. Das Limit für Admin-Adressen ohne Login ging am Abend des 9. Oktober live.
02,4994,9987,4979,996
10/99,996Vorher
10/1011Nachher
10/111Nachher
Anfragen der Bot-IP
Tägliche Anfragen der einen IP, die in drei Stunden 22.391 schickte.
05,59811,19616,79322,391
10/922,391Vorher
10/100Keine Anfragen
10/110Keine Anfragen
Von WordPress erzeugte 404-Seiten
Anfragen des Scanners an gewöhnliche Fake-Pfade. Das Limit für Anfragen, die PHP erreichen, ging am Abend des 11. Oktober live – beurteilt wird mit den Zahlen der Prüfung nächste Woche.
06531,3061,9592,612
10/92,563Vorher
10/102,612Vorher
10/111,630Vorher · ab Abend des 11.
Die Zahlen sind tägliche Auswertungen der nginx access.log vom 9. bis 11. Oktober 2026.

🔢 FAQ & empfohlene Inhalte

dynamic startet und beendet Worker je nach Anfragemenge und protokolliert die Warnung seems busy, wenn kein Worker mehr frei ist. static hält immer max_children Worker vor – ohne Startverzögerung und ohne Warnung, belegt dafür aber dauerhaft Speicher. Der Server in diesem Beitrag blieb bei dynamic mit 26; hast du Speicher übrig und ist die Anfragemenge den ganzen Tag gleichmäßig, passt static.

Schalte es ein, wenn zu den Zeitpunkten der Warnungen bestimmte Anfragen lange zu laufen scheinen. Trägst du in der Pool-Konfiguration einen [slowlog]-Pfad und [request_slowlog_timeout] ein, wird die Ausführungsstelle jeder Anfrage protokolliert, die diese Zeit überschreitet. Hier war die Zahl der Anfragen die Ursache, deshalb reichte die Auswertung der access.log, und slowlog kam nicht zum Einsatz.

Das Limit gilt nur für Anfragen ohne Login, die am Cache vorbei bis zu PHP gehen, und nur der Überschuss bekommt eine 429, wenn eine IP 30 pro Minute oder 60 gleichzeitig überschreitet. In der Auswertung vom 11. Oktober lag nur eine IP über dieser Grenze, ein Scanner, und der Überschuss-Zustand endet innerhalb einer Minute, sodass die nächste Anfrage wieder normal beantwortet wird.

Ich habe es nicht getan. Der Preload konzentrierte sich auf die Zeiten, in denen Beiträge und Übersetzungen gespeichert wurden, und führte nie zu Fehlern bei Besuchern. Maßstab ist nicht die Zahl der Warnungen, sondern ob Besucher Fehler 502 oder 504 bekommen; liegt diese Zahl bei 0, bleibt der Preload, wie er ist.

ℹ️ 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.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Beweisen Sie, dass Sie ein Mensch sind: 10   +   5   =