...

WordPress PHP-FPM Children: Falsche Werte blockieren Seiten

PHP-FPM Children entscheiden in WordPress, ob Anfragen flüssig laufen oder in der Warteschlange hängen bleiben. Ich zeige, wie falsche pm.max_children-Werte Seiten blockieren, RAM auffressen und wie ich saubere Werte berechne.

Zentrale Punkte

Bevor ich tiefer einsteige, fasse ich die Kernaussagen kurz zusammen:

  • pm.max_children bestimmt, wie viele gleichzeitige PHP-Requests laufen.
  • Zu wenig Children erzeugt Warteschlangen, 502/504 und hohe TTFB.
  • Zu viel führt zu RAM-Engpässen, Swap und OOM-Kills.
  • Formel: verfügbarer PHP-RAM / Prozessgröße × 0,7–0,8.
  • Iteratives Tuning mit Monitoring liefert langfristig die beste Performance.

Warum falsche PHP-FPM Children Seiten blockieren

Jede dynamische WordPress-Anfrage benötigt einen eigenen Worker, und genau diese Prozesse steuert der Pool über pm.max_children. Setze ich den Wert zu niedrig, stauen sich Anfragen in einer Warteschlange und die TTFB steigt spürbar. Lege ich den Wert zu hoch fest, belegt jeder Child-Prozess zusätzlichen RAM und der Server wechselt in den Swap. Im Swap verlangsamt sich alles, bis Apache oder Nginx 502/504 melden oder der OOM-Killer Prozesse beendet. Gesunder Durchsatz entsteht erst, wenn die Anzahl der Children zum realen RAM-Budget und zur Last des Projekts passt.

Die Formel für pm.max_children in der Praxis

Ich starte mit der einfachen Formel: verfügbarer RAM für PHP geteilt durch die durchschnittliche Größe eines Child-Prozesses, multipliziert mit einem Sicherheitsfaktor von 0,7 bis 0,8. Den RAM pro Prozess ermittle ich mit ps und der RSS-Spalte; für typische WordPress-Stacks liegen 50–250 MB oft richtig. Auf einem 4‑GB‑Server reserviere ich Speicher für Linux, Datenbank und Cache-Services, sodass rund 1,5–2 GB für PHP bleiben. Liegt der Prozessschnitt zum Beispiel bei 100 MB, ergibt 2.000 / 100 × 0,75 = 15 Children. Diese Zahl dient als Startpunkt, den ich nach Lastprofil, Caching und Plugin-Mix verfeinere.

Startwerte für typische WordPress-Setups

Für kleine Blogs mit 2 GB RAM bewähren sich 8 Children, pm = dynamic und ein pm.max_requests von etwa 800. Bei mittleren Projekten mit 4 GB RAM setze ich 12 Children, start_servers 4, min_spare_servers 4. Große Shops ab 8 GB RAM profitieren von 21–40 Children; bei dauerhaft hoher Last kann pm = static für konstanten Durchsatz sorgen. Ich prüfe anschließend das Verhältnis aus CPU-Auslastung, RAM-Nutzung und Antwortzeiten, um fein nachzujustieren. Wer tiefer einsteigen will, findet Hintergründe unter optimale PHP-FPM-Einstellungen.

Prozesse messen: So bestimme ich den RAM-Bedarf

Ich ermittle zuerst die Live-Größe der Prozesse, bevor ich Werte festlege, denn Glaskugeln helfen hier nicht und kosten Leistung. Der Befehl ps -ylC php-fpm –sort:rss liefert die RSS-Größen, die ich über einige Minuten beobachte. Bei Updates oder Cronjobs wachsen Prozesse oft an, weshalb ich Spikes in die Rechnung einbeziehe. Zusätzlich prüfe ich mit htop und free -h die RAM-Reserven und den Anteil an Swap. Mit diesen Daten lege ich einen belastbaren Durchschnitt fest und wähle den Sicherheitsfaktor konservativ.

Wichtige Parameter im Überblick

Neben pm.max_children bestimmen weitere Pool-Optionen, wie sauber WordPress Anfragen abarbeitet und wie gut sich Speicher wieder freigibt, was spürbar die Stabilität erhöht. pm regelt den Modus: dynamic passt die Anzahl der Prozesse an die Last an, static hält eine feste Zahl. pm.max_requests verhindert Memory-Bloat, indem es Prozesse nach X Requests neu startet. request_terminate_timeout schützt gegen Hänger durch fehlerhafte oder langsame Skripte. Mit diesem Set decke ich 90 Prozent der echten Praxisfälle ab.

Parameter Funktion WordPress-Empfehlung
pm Modus für Prozesssteuerung dynamic für variable Last; static bei dauerhaft hohem Traffic
pm.max_children Maximale Zahl gleichzeitiger Worker Verfügbarer PHP-RAM / Prozessgröße × 0,75
pm.max_requests Recycling von Prozessen 300–1.000; eher niedriger bei WooCommerce
request_terminate_timeout Abbruch langlaufender Requests 60–120 Sekunden gegen Hänger

Dynamic, ondemand oder static – welcher Modus passt?

Ich wähle den Modus passend zum Lastprofil: dynamic ist mein Default, weil er die Anzahl aktiver Prozesse flexibel anpasst und so RAM spart, wenn wenig los ist. static setze ich ein, wenn die Last konstant ist und ich harte Zusagen bei Latenz und Durchsatz brauche – etwa während Kampagnen oder Sales. ondemand eignet sich für Server mit langen Leerlaufphasen: Prozesse werden erst bei Bedarf erzeugt und nach Inaktivität wieder beendet. Der Trade-off sind Kaltstarts; die erste Anfrage pro neuem Prozess fühlt sich langsamer an. Für ondemand stelle ich pm.process_idle_timeout sauber ein (z. B. 10–20s), bei dynamic halte ich start_servers, min_spare_servers und max_spare_servers eng, damit der Pool zügig skaliert, aber nicht „aufbläht“.

Konfigurationsbeispiel für deinen Pool

Auf Debian/Ubuntu liegt die Pool-Datei üblicherweise unter /etc/php/8.x/fpm/pool.d/www.conf, was mir eine klare Struktur für Anpassungen gibt. Ich setze pm auf dynamic, verankere einen realistischen Wert für pm.max_children und halte Spare-Server eng. Das Recycling stelle ich auf 500, um Leaks und RAM-Anstiege früh zu kappen. Nach jeder Änderung teste ich Last und stopfe Engpässe, bevor ich die Werte weiter erhöhe. Für Hintergründe zu Grenzwerten hilft der Einblick unter pm.max_children optimieren.

pm = dynamic
pm.max_children = 15
pm.start_servers = 4
pm.min_spare_servers = 4
pm.max_spare_servers = 8
pm.max_requests = 500
request_terminate_timeout = 90s

Mehrere Pools, Sockets und saubere Isolation

Bei mehreren Projekten oder klar getrennten Rollen (Frontend vs. Admin/REST) richte ich separate Pools mit eigenem User und eigenem Socket ein. So begrenzt jeder Pool seine Children selbst und ein Ausreißer blockiert nicht den Rest. Auf einem Host bevorzuge ich Unix-Sockets gegenüber TCP (listen = /run/php/site.sock) – geringere Latenz, weniger Overhead. Zwischen Nginx/Apache und PHP-FPM auf unterschiedlichen Hosts/Containern nutze ich TCP. Ich setze listen.owner, listen.group und listen.mode konsistent und hebe bei Bedarf listen.backlog an, damit kurze Lastspitzen nicht in Verbindungsfehler laufen. Mit einem dedizierten Admin-Pool kann ich ein engeres request_terminate_timeout fahren und pm.max_requests niedriger ansetzen, ohne den Caching-starken Frontend-Pool zu bremsen.

Symptome erkennen und richtig reagieren

Steht im Error-Log regelmäßig „server reached pm.max_children“, limitiert der Pool die Parallelität und ich erhöhe moderat. Tauchen 502/504 bei gleichzeitig hoher Swap-Nutzung auf, setze ich pm.max_children zurück und senke pm.max_requests. Steigt die CPU bei niedriger RAM-Auslastung, dann blockieren meist Queries oder PHP-Logik; ich optimiere Datenbank und Caching. Bleiben Anfragen hängen, hilft ein strengeres request_terminate_timeout und Log-Analyse mit Timestamps. Auffällige Peaks überprüfe ich gegen Cronjobs, Such-Indizes und Admin-Aktionen.

FPM-Status und Slowlog: präzise Diagnose

Ich aktiviere den Status pro Pool (pm.status_path) und lese Kennzahlen wie active processes, max children reached, listen queue und max listen queue aus. Eine dauerhaft wachsende Listen-Queue zeigt klar: zu wenig Children oder blockierende Backends. Zusätzlich setze ich request_slowlog_timeout (z. B. 3–5s) und einen slowlog-Pfad. So sehe ich Stacktraces von Requests, die trödeln – häufig sind es externe HTTP-Calls, komplexe WooCommerce-Queries oder Bildmanipulationen. Mit catch_workers_output landen Warnungen der Worker gesammelt in den Logs. Auf Basis dieser Daten entscheide ich, ob mehr Parallelität hilft oder ob ich Engstellen im Code/DB lösen muss.

Monitoring: 3–5 Tage saubere Auswertung

Nach dem Tuning beobachte ich Lastspitzen über mehrere Tage, denn kurzfristige Schwankungen täuschen. Ich logge RAM, Swap, 502/504, TTFB und die Anzahl aktiver Prozesse im FPM-Status. Unter 80 Prozent RAM-Auslastung ohne Swap und ohne Warteschlangen liege ich richtig. Treten Engstellen bei Aktionen wie Checkout, Suche oder Imports auf, drehe ich gezielt an pm.max_children und pm.max_requests. Jeder Schritt erfolgt in kleinen Anpassungen und mit einer erneuten Messung.

Speicherrechnung im Detail: RSS, PSS und Shared Memory

Die Prozessgröße ist tückisch: RSS (Resident Set Size) enthält auch geteilte Segmente wie OPcache und Bibliotheken. Deshalb überschätze ich schnell den RAM-Verbrauch, wenn ich einfach „RSS × Children“ rechne. Besser ist die PSS-Sicht (Proportional Set Size), die Shared Memory fair auf Prozesse verteilt – Tools wie smem helfen hier. In die Kalkulation gehören OPcache (z. B. 256 MB + strings), APCu (z. B. 64–128 MB) und der Master-Prozess. Das PHP memory_limit ist kein Durchschnitt, sondern die Obergrenze je Request; einzelne Peaks dürfen auftreten, aber der Mittelwert zählt. Ich plane einen Puffer ein, damit Spikes, Deployments und Cronjobs nicht sofort Swap auslösen, und lasse pm.max_requests bewusst wirken, um Memory-Bloat zu begrenzen.

WordPress-spezifische Last senken

Ich reduziere PHP-Last zuerst, bevor ich Children weiter erhöhe, denn eine schnellere Cache-Trefferquote spart echten RAM. Full-Page-Caches drücken die PHP-Requests drastisch, was Kapazität für Checkout, Suche und Admin schafft. OPcache mit memory_consumption um 256 MB beschleunigt den Bytecode und entlastet den Pool. Das PHP memory_limit halte ich praxisnah bei 256M, damit einzelne Plugins nicht den Server ausbremsen. Mehr Einblick zu Engpässen liefert der Ratgeber PHP-Worker als Flaschenhals.

Datenbank- und Cache-Backends im Gleichgewicht

Jeder PHP-Worker erzeugt potenziell eine Datenbankverbindung. Erhöhe ich pm.max_children, steigt auch die gleichzeitige DB-Last. Ich prüfe deshalb MySQL/MariaDB: max_connections, Puffer (innodb_buffer_pool_size), und den Query-Planer. Redis/Memcached müssen parallel mithalten – maxclients, Speicherlimit und Latenzen im Blick behalten. Eine WordPress-Instanz mit 20 aktiven Children kann die DB mühelos sättigen, wenn mehrere teure Queries parallel laufen. Deshalb tune ich die DB (Indexe, langsame Queries) und setze auf persistente Objekt-Caches, bevor ich weitere Children freigebe. So steigere ich Durchsatz, ohne das Backend zu überfahren.

WooCommerce, Cron und Admin: Sonderfälle

Shops erzeugen mehr gleichzeitige dynamische Requests, weshalb ich für Checkouts etwas Luft bei pm.max_children lasse. Gleichzeitig senke ich pm.max_requests eher, um Memory-Bloat laufend zu kappen. Bei Importen und Cronjobs plane ich zusätzliches Budget ein oder führe Tasks außerhalb der Hauptverkehrszeit aus. Der Admin-Bereich spiked oft kurzfristig; Caching schützt hier weniger, also zählt effiziente Pool-Steuerung. Bei Anzeichen für Warteschlangen erhöhe ich in kleinen Schritten und beobachte die Metriken direkt danach.

Container, vCPU-Quoten und OOM-Fallen

In Containern und VMs gilt der Blick auf das effektive RAM-Limit (cgroups), nicht auf den Host. Ich berechne pm.max_children daher aus dem zugewiesenen Limit und nicht aus „free -h“. Container-OOMs sind gnadenlos – der Kernel beendet Prozesse hart. Auch CPU-Quoten zählen: Mehr Children helfen nicht, wenn 1–2 vCPUs die Rechenzeit limitieren. Als Faustregel skaliere ich bei IO-lastigen WordPress-Workloads auf etwa 2–4× vCPU-Anzahl; darüber steigen Kontextwechsel, aber nicht der reale Durchsatz. In orchestrierten Umgebungen rolle ich Änderungen konservativ aus, beobachte Pod-Restarts und halte Readiness/Liveness-Probes so, dass kurze Warmup-Phasen von FPM nicht als Ausfall zählen.

Fehlerquellen, die oft übersehen werden

Viele Probleme stammen nicht vom Pool, sondern von Plugins, die Anfragen vervielfachen oder lange Prozesse erzeugen. Indexierte Suchen, kaputte Crawler-Regeln und überdrehte Heartbeat-Intervalle treiben die Last hoch. Ich prüfe deshalb immer zuerst Logs, Query Monitor und Caching-Header. Tritt die Last nur bei bestimmten URLs auf, deute ich das als Hinweis auf Plugin- oder Template-Engstellen. Erst wenn diese Baustellen geklärt sind, skaliere ich Children weiter.

Sitzungen, Admin-AJAX und Locks verstehen

WordPress/Plugins arbeiten teils mit Sessions. Dateibasierte Session-Locks können Anfragen seriell machen – ein einziger langsamer Request blockiert den Rest derselben Session-ID. Ich halte Session-Nutzung schlank und prüfe, ob Admin-AJAX-Bursts (wp-admin/admin-ajax.php) unnötig häufig feuern. Der Heartbeat sollte sinnvoll gedrosselt sein, sonst erzeugt er Last ohne Mehrwert. Treten Locks oder lange Dateizugriffe auf, bringt mehr Parallelität keine Abhilfe; hier helfen Caching, schnellere Storage-I/O oder ein anderer Session-Handler. In Logs erkenne ich solche Muster an vielen ähnlichen, gleichzeitig startenden Requests mit ungewöhnlich langen Laufzeiten.

Nginx, Apache und FastCGI-Timeouts im Blick

Auch der Webserver setzt Grenzen, die ich mit den FPM-Werten abstimmen muss, sonst verpufft Tuning. Bei Nginx achte ich auf fastcgi_read_timeout und auf genügend Worker-Prozesse. Unter Apache prüfe ich mpm_event, Keepalive-Settings und Proxy-Timeouts. Stimmen diese Limits nicht, melden Nutzer Timeouts, obwohl FPM noch Kapazität hat. Einheitliche Zeitbudgets halten den Pfad vom Client bis PHP konsistent.

Rollout-Strategie, Tests und Betrieb

Änderungen an pm.max_children rolle ich schrittweise aus und teste sie unter realer Last. Ein Reload von FPM (graceful) übernimmt Konfigurationen ohne Verbindungsabbruch. Vor größeren Sprüngen simuliere ich Spitzen (z. B. Sale-Start) und beobachte dabei listen queue, CPU, RAM, 95.–99. Perzentil der Latenz und Fehlerraten. Ich dokumentiere die getroffenen Annahmen, damit spätere Teammitglieder verstehen, warum ein Wert so gewählt ist. Alarme setze ich auf: Swap > 0, „max children reached“ im Status, steigende 502/504, und DB-Latenz. So bleibt die Plattform auch Monate später stabil, wenn Traffic- und Plugin-Mix sich ändern.

Kurz zusammengefasst

Falsch gesetzte PHP-FPM-Children bremsen WordPress aus, entweder in Warteschlangen oder im RAM-Limit. Ich ermittle die Prozessgröße, reserviere Speicher für Systemdienste und setze pm.max_children mit Puffer. Danach kontrolliere ich pm.max_requests, request_terminate_timeout und den Modus pm = dynamic oder static nach Lastbild. Caching, OPcache und saubere Plugins senken die Zahl der PHP-Requests spürbar. Wer diese Schritte konsequent umsetzt, hält Seiten reaktionsschnell und den Server zuverlässig.

Aktuelle Artikel