Dieser Beitrag vergleicht die PHP-FPM Modi static, dynamic und ondemand und zeigt, wie sie Prozesse starten, RAM binden und Latenz beeinflussen. Ich erkläre praxisnah, wann welcher Modus überzeugt, liefere sinnvolle Startwerte, nenne typische Stolpersteine und zeige Monitoring-Tricks, damit du deine PHP-Pools sicher einstellst.
Zentrale Punkte
Damit du schnell loslegen kannst, fasse ich die wichtigsten Aussagen kompakt zusammen. Der Fokus liegt auf Prozesssteuerung, RAM-Bedarf, Latenz und Einsatzfeldern. Jede Auswahl hat klare Stärken, aber auch Grenzen. Mit ein paar Kennzahlen triffst du belastbare Entscheidungen. So gehst du fokussiert ans Tuning heran und sparst Zeit.
- Static: Feste Prozesszahl, höchste Konstanz bei gleichbleibender Last.
- Dynamic: Automatisches Skalieren zwischen Minimal- und Maximalwerten.
- Ondemand: Start bei Bedarf, sparsam im Leerlauf, Kaltstart-Latenz.
- RAM-Planung: Pro Prozess 20–50 MB einkalkulieren, OOM vermeiden.
- Monitoring: Status-Page, Logs und htop für fundierte Entscheidungen.
Funktionsweise des Process Managers
Der PHP-FPM Process Manager steuert, wie viele Worker-Prozesse Anfragen bearbeiten und wann sie entstehen oder enden. Jede Worker-Instanz hält Interpreter, Erweiterungen und Teile des Bytecodes im Speicher, was pro Prozess typischerweise einige Megabyte bindet. Die drei Modi verändern Startverhalten, Lebenszyklus und Leerlauf-Verhalten stark. Static hält eine feste Anzahl aktiv, Dynamic balanciert zwischen Unter- und Obergrenzen, Ondemand erzeugt Prozesse erst bei eingehenden Requests. Diese Steuerung wirkt direkt auf RAM-Profil, Latenz beim Aufdrehen und System-Lastspitzen.
Wichtige Parameter bilden das Rückgrat deiner Konfiguration: pm legt den Modus fest, pm.max_children begrenzt gleichzeitige Worker hart. Bei Dynamic kommen pm.start_servers, pm.min_spare_servers und pm.max_spare_servers hinzu, die die Breite des Puffers steuern. Ondemand setzt auf pm.process_idle_timeout, um ruhende Prozesse wieder zu beenden. Mit sinnvollen Werten stellst du sicher, dass Lastspitzen nicht zu Engpässen führen und die Maschine nicht in Speicherdruck gerät.
Ich prüfe vorab den Footprint pro Prozess, die durchschnittliche gleichzeitige Last und die Peak-Verteilung über den Tag. Aus diesen Größen leite ich den Höchstwert für pm.max_children ab, multipliziere mit dem gemessenen Prozess-Speicher und lasse Reserve für Webserver, Datenbank, Cache und Kernel. Diese simple Rechnung verhindert Out-of-Memory-Fehler und sorgt für Stabilität unter Druck. Wer das beherzigt, erspart sich später mühsames Nachsteuern.
Static-Modus: konstante Leistung für gleichmäßige Last
Der Static-Modus hält eine feste Zahl an PHP-Workern dauerhaft aktiv, was Start-Overhead eliminiert. Bei konstanten Traffic-Profilen erzielt dieses Setup sehr geringe Latenzschwankungen und eine gleichmäßige CPU-Last. Die Kehrseite: Im Leerlauf bleibt RAM belegt, obwohl keine Anfragen anliegen. Deshalb wähle ich Static nur auf Hosts mit reichlich Arbeitsspeicher und kalkulierbarem Anfragevolumen. Auf stark genutzten Shops oder API-Backends liefert Static häufig die sauberste Reaktionskurve.
Entscheidend ist ein realistisch gesetztes pm.max_children, das sich am Prozess-Footprint orientiert. Für die erste Abschätzung rechne ich grob 20–50 MB pro PHP-Prozess inklusive Erweiterungen und OPcache. Den finalen Wert verifiziere ich mit Lasttests und dem Systemmonitor. Wer die Berechnung vertiefen möchte, findet praxisnahe Schritte unter pm.max_children optimieren. So stellst du sicher, dass deine feste Poolgröße zur Hardware passt.
[www]
pm = static
pm.max_children = 50
pm.max_requests = 500
Hinweis: Nach Änderungen starte ich PHP-FPM neu, prüfe Logs und beobachte die Auslastung unter realem Traffic. Bleibt noch viel RAM frei, erhöhe ich vorsichtig. Sehe ich steigende Swap-Nutzung oder OOM-Killer-Einträge, reduziere ich sofort. Diese kleine Routine schützt die Verfügbarkeit zuverlässig.
Dynamic-Modus: flexibel bei schwankender Nachfrage
Dynamic startet mit wenigen Prozessen und skaliert die Worker-Zahl je nach Bedarf in die definierte Spannweite. Dadurch sinkt der Leerlauf-Verbrauch bei ruhigen Phasen, während kurze Peaks abgefedert werden. Das Verfahren erzeugt etwas Overhead beim Spawnen, punktet aber mit guter Ressourcen-Effizienz. In gemischten Umgebungen mit Tagesprofilen liefert Dynamic oft den besten Kompromiss. Gerade für viele CMS-Installationen bleibt dieser Modus die erste Wahl.
Ich setze Start-, Minimal- und Maximalwerte so, dass bei typischer Last keine ständigen Spawn-Events auftreten. Häufige Logmeldungen wie „seems busy, spawning children“ deuten auf zu enge Limits hin. Für WordPress-Stacks hilft es, Caching und OPcache sauber einzustellen und dann moderat zu erhöhen. Eine kompakte Anleitung deckt die wichtigsten Hebel ab: optimale WordPress-Einstellungen. So erreichst du kurze Antwortzeiten, ohne die RAM-Reserve zu sprengen.
[www]
pm = dynamic
pm.max_children = 50
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 35
Tipp: Beobachte die Idle-Worker und die durchschnittlichen aktiven Prozesse über den Tag. Liegt der Mittelwert nahe am oberen Ende, erhöhe moderat. Verharren viele Prozesse im Leerlauf, senke die Spanne. Mit wenigen Iterationen triffst du die Sweetspot-Einstellung.
Ondemand-Modus: sparsam im Leerlauf, Start bei Anfrage
Ondemand erzeugt Prozesse erst, wenn eine Anfrage eintrifft, und beendet sie nach einer Idle-Zeit. Damit sinkt der RAM-Bedarf in ruhigen Phasen auf ein Minimum, was viele kleine Sites auf einer Maschine begünstigt. Bei Kaltstarts fällt jedoch zusätzliche Latenz an, weil der Worker erst startet und warm wird. Für Entwicklungsumgebungen, Cron-Only-Apps und selten aufgerufene Seiten ist diese Logik ein Gewinn. Bei Dauerlast würde ich Ondemand nicht einsetzen.
[www]
pm = ondemand
pm.max_children = 50
pm.process_idle_timeout = 10s
pm.max_requests = 500
Die Idle-Zeit steuere ich meist zwischen 10 und 30 Sekunden, je nach Aufrufmuster und Speicherbudget. Eine kürzere Frist spart RAM, erhöht aber die Chance auf Kaltstarts. Eine längere Frist hält Prozesse warm, kostet jedoch Speicher. Ich beobachte daher die Aufruffrequenz, messe die 95. Perzentil-Latenz und passe dann fein an. So bleibt die Antwortzeit kalkulierbar, ohne das System zu belasten.
Vergleichstabelle: Eigenschaften der drei Modi
Die folgende Übersicht stellt typische Eigenschaften gegenüber. Ich nutze sie als Gesprächsgrundlage, bevor ich ins konkrete Sizing gehe. Die Tabelle ersetzt keine Messung unter Real-Last, liefert aber einen strukturierten Startpunkt. Wer Werte anpasst, sollte immer Speicherprofil und Latenzverteilung im Blick behalten. So bleibst du bei Peaks handlungsfähig und vermeidest Engpässe.
| Kriterium | Static | Dynamic | Ondemand |
|---|---|---|---|
| Prozesse | Fixe Anzahl, dauerhaft aktiv | Automatisch zwischen Min/Max | Start nur bei Bedarf |
| RAM-Nutzung | Konstant hoch | Variabel (z. B. 200–600 MB) | Minimal im Leerlauf (z. B. 50–700 MB) |
| Performance | Sehr gleichmäßig | Gut und anpassungsfähig | Gut bei Low-Traffic |
| Ideal für | Konstante High-Traffic-Profile | Variable Nachfrage | Viele ruhende Sites / Shared |
| Overhead | Gering | Mittel (Spawn/Despawn) | Höher bei Kaltstarts |
Die Tabelle hilft, Erwartungen zu kalibrieren und Prioritäten klar zu benennen. Brauchst du höchste Reaktionskonstanz, gewinnt oft Static. Zählt Effizienz bei schwankender Last, arbeitet Dynamic meist angenehmer. Steht Sparsamkeit im Vordergrund, führt kein Weg an Ondemand vorbei. Messwerte entscheiden am Ende, nicht Annahmen.
Ressourcenkalkulation und Sizing
Ich schätze zuerst den Memory-Footprint pro Prozess ab, multipliziere ihn mit der anvisierten Worker-Zahl und addiere 20–30 % Reserve. Zusätzlich rechne ich Platz für Nginx/Apache, Datenbank, Redis/Memcached und den Kernel ein. Diese Summe darf die physische RAM-Kapazität abzüglich Sicherheitsmarge nicht überschreiten. Für OPcache plane ich dedizierten Speicher ein, damit Bytecode nicht verdrängt wird. Mit dieser einfachen Formel halte ich OOM-Risiken gering.
Im nächsten Schritt messe ich gleichzeitige Anfragen per Webserver-Status und APM. Die Peak-Konkurrenz um PHP-Worker bestimmt, wie hoch pm.max_children sein muss. Reicht der RAM nicht aus, erhöhe ich Cache-Hits, senke Query-Zeiten oder verschiebe Arbeit in Queues. Erst wenn diese Hebel greifen, vergrößere ich den Pool. So bleibt die Effizienz hoch und die Maschine reagiert verlässlich.
Monitoring und Fehlersuche
Gute Entscheidungen basieren auf Daten. Ich aktiviere die PHP-FPM-Status-Page und lese aktive sowie idle Prozesse, Queue-Länge und akzeptierte Verbindungen aus. Ergänzend prüfe ich Error-Logs auf Spawn-Warnungen und Zeitüberschreitungen. In htop beobachte ich CPU-Waits, Load und Swap, um Engpässe schneller zu finden. Diese Signale machen Tuning-Schritte nachvollziehbar und vermeiden Blindflug.
<?php
$status = @file_get_contents('http://localhost/status');
$data = json_decode($status, true);
echo "Active: " . $data['active processes'] . "\n";
echo "Idle: " . $data['idle processes'] . "\n";
?>
APM-Tools zeigen Traces und Engstellen auf Funktions- oder Query-Ebene. Finde ich dort Ausreißer, setze ich zuerst bei Caching und I/O an. Danach prüfe ich, ob die Pool-Limits zur tatsächlichen Parallelität passen. Erst wenn Applikations-Engpässe gelöst sind, lohnt mehr Kapazität in FPM. Dieser Ablauf spart Zeit und hält die Architektur schlank.
Häufige Tuning-Fehler vermeiden
Ich sehe oft zu hoch gesetzte max_children-Werte ohne Rücksicht auf den RAM. Das erzeugt unnötigen Swap, lange Garbage-Collection-Phasen und am Ende OOM-Killer. Ebenso schaden zu niedrige Limits, weil sie Warteschlangen aufbauen und Antwortzeiten strecken. Auch fehlender OPcache verschenkt CPU-Zeit und vergrößert den Prozess-Footprint. Mit wenigen Checks vorab bleiben diese Fallen aus dem Weg.
Ein zweiter Klassiker: unpassende Zeitlimits bei Ondemand, die zu vielen Kaltstarts führen. Hier hilft ein kurzer A/B-Versuch mit 10, 20 und 30 Sekunden Idle-Timeout. Bei Dynamic wiederum erzeugen zu kleine Spare-Werte ständiges Spawnen. Logs verraten diese Muster schnell und leiten die nächste Anpassung ein. So bleibt dein Stack reaktionsfreudig.
PHP-FPM im Kontext anderer PHP-Handler
PHP-FPM steht oft im Vergleich zu alten CGI-Varianten oder modernen Alternativen wie LSAPI. Die Wahl des Handlers beeinflusst Prozessverwaltung, Ressourcen-Charakteristik und Fehlerisolierung. Wer die Unterschiede versteht, plant Puffer und Limits realistischer. Für einen schnellen Überblick lohnt ein kurzer PHP-Handler Vergleich. Danach fällt die Entscheidung für FPM-Modi deutlich zielgerichteter aus.
Ich bleibe meist bei FPM, weil es ausgereift ist, sauber loggt und mit Nginx/Apache gut zusammenspielt. Entscheidend sind nicht nur Benchmarks, sondern auch Betriebsaspekte wie Observability und Failover. Stimmen diese Grundlagen, holst du mehr aus Static, Dynamic oder Ondemand heraus. Jede Option verdient Tests unter echter Last. So gewinnst du Vertrauen in deine Einstellungen.
Praxisnahe Entscheidungsstrategie
Ich starte mit Dynamic als Default, messe Lastprofile und beobachte Spitzen. Finde ich sehr konstante Auslastung, wechsle ich zu Static und stelle die feste Poolgröße ein. Treffe ich auf selten genutzte Sites, wähle ich Ondemand mit angemessenem Idle-Timeout. Parallel optimiere ich OPcache, Object-Cache und Datenbank-Queries, damit FPM weniger Druck abbekommt. Danach feine ich Limits so aus, dass Warteschlangen gar nicht erst entstehen.
Diese Reihenfolge senkt Risiko und Aufwand. Erst messen, dann Regeln anpassen, schließlich Hardware berücksichtigen. Jede Änderung dokumentiere ich kurz mit Zeitpunkt, Werten und Ziel. Das erleichtert spätere Korrekturen und sorgt für saubere Transparenz. So bleibt der Stack beherrschbar, auch wenn Traffic-Muster sich ändern.
Von Kennzahlen zu belastbaren Werten: so rechne ich
Ich übersetze Lastprofile in konkrete Poolgrößen mit einer einfachen Daumenregel: Wie viele Requests treffen pro Sekunde ein und wie lange dauert die Bearbeitung im Mittel bzw. im 95. Perzentil? Als Orientierung nutze ich Little’s Law in einfacher Form: gleichzeitige Bearbeitungen ≈ Durchsatz × mittlere Bearbeitungszeit. Beispiel: 120 Requests/s bei 80 ms im Mittel ergeben rund 9,6 gleichzeitige Ausführungen. Ich gebe 30–50 % Puffer für Spitzen drauf und prüfe, ob die resultierenden pm.max_children in mein RAM-Budget passen. Für harte Peaks beziehe ich zusätzlich das 95. Perzentil ein, um Warteschlangen zu vermeiden.
Wichtig ist, den Charakter der Workloads zu berücksichtigen: Bei I/O-lastigen Apps (viele Remote-Calls, DB-Zugriffe) bringen etwas mehr Worker oft Vorteile, weil Wartezeiten überlappt werden. Bei CPU-lastigem Code limitiere ich stärker, damit sich die Prozesse nicht gegenseitig ausbremsen und die Run-Queue nicht explodiert.
pm.max_requests: sauberes Recycling gegen Fragmentierung
Lang laufende PHP-Prozesse können durch Fragmentierung oder Speicher-Leaks wachsen. Mit pm.max_requests legst du fest, nach wie vielen abgearbeiteten Anfragen ein Worker beendet und frisch gestartet wird. Das hält den Footprint stabil. Ich beginne meist bei 300–1000, je nach Erweiterungen und Code-Basis. Beobachte die RSS-/PSS-Werte der Prozesse: Wenn sie deutlich wachsen, reduziere den Wert. Da der OPcache geteilt ist, bleibt Bytecode beim Worker-Recycling erhalten; die meisten Apps spüren das Recycling deshalb kaum.
[www]
; gezieltes Recycling ohne zu häufige Neustarts
pm.max_requests = 800
Wer regelmäßig Deployments ausrollt, profitiert von einem Reload des Pools. Ich nutze bevorzugt einen graceful reload über den Dienst-Manager (z. B. „systemctl reload php-fpm“), damit laufende Requests sauber enden und neue Worker mit aktualisierter Config starten.
Slowlog und Timeouts: Engstellen gezielt sichtbar machen
Die meisten Latenzspitzen stecken in wenigen langsamen Requests. Ich aktiviere daher den Slowlog mit einem moderaten Schwellwert (z. B. 2–5 s) und schaue mir Stacktraces an. So finde ich problematische Funktionen, externe Calls oder teure Queries.
[www]
request_slowlog_timeout = 3s
slowlog = /var/log/php-fpm/slowlog-www.log
Passend dazu gleiche ich die Timeouts des Webservers ab. Ein zu kurzes Upstream-Timeout (Nginx/Apache) gegenüber PHPs max_execution_time führt zu 502/504-Fehlern, obwohl FPM weiterarbeitet. Ich halte die Kette konsistent: Connect-, Read- und Send-Timeouts des Webservers knapp oberhalb der typischen PHP-Request-Dauer, aber unterhalb harter Obergrenzen.
Warteschlange, Backlog und Statuswerte richtig deuten
Im FPM-Status beachte ich besonders „listen queue“ und „max listen queue“. Wachsen diese Werte regelmäßig, ist der Pool zu klein oder blockiert. Kurzzeitige Peaks sind normal, aber dauerhafte Staus deuten auf Unterdimensionierung hin. In stark burstigen Umgebungen erhöhe ich den Socket-Backlog moderat, beobachte die Queue und stelle sicher, dass Kernel-Limits (z. B. somaxconn) nicht der Flaschenhals sind.
Zeigt das Monitoring „seems busy, spawning children“ sehr häufig, sind die Reserve-Parameter (Dynamic) zu eng. Bei Ondemand ist ein wiederkehrend hoher Anteil an Kaltstarts ein Hinweis, das Idle-Timeout zu verlängern oder tagsüber einen Minimalpuffer zu halten.
Mehrere Pools: Fairness, Isolation und Quoten
Auf Multi-Tenant- oder Shared-Hosts trenne ich Anwendungen in eigene Pools mit individuellen Limits. So verhindere ich, dass ein speicherhungriges Projekt andere verdrängt. Für kritische Services (z. B. Login-/API-Endpunkte) plane ich dedizierte Pools mit fester Minimal-Reserve. Eine klare Benennung („www-shop“, „www-api“, „www-cron“) und getrennte Logs erleichtern Analyse und Fehlersuche.
Achte darauf, dass die Summe aller pm.max_children über alle Pools zur Maschine passt. Zusätzlich beziehe ich Downstream-Limits ein: DB-max_connections, Redis/Memcached-Threading und externe API-Raten. Ein PHP-Pool, der mehr gleichzeitige Queries feuert als die Datenbank verkraftet, erkauft sich nur längere Warteschlangen.
OPcache-Warmup, Preload und Kaltstarts zähmen
Um Ondemand-Kaltstarts zu mildern, halte ich OPcache stabil (ausreichend memory_consumption und interned_strings_buffer) und setze, falls sinnvoll, auf Preload zentraler Klassen/Frameworks. Damit steht Bytecode nach dem ersten Hit bereit, und Wiederholungen bleiben warm. Zusätzlich helfen ein größerer realpath-Cache und ein strukturierter Autoloader, um Dateisystem-Lookups zu reduzieren. In summe verkürzt das die Anlaufzeit frisch gestarteter Worker deutlich.
Webserver-Interaktion: Nginx/Apache sauber ankoppeln
Ich stelle sicher, dass Webserver- und FPM-Einstellungen zusammenpassen: Puffer und Timeouts müssen symmetrisch sein, Keep-Alive darf FPM nicht mit Zombie-Verbindungen blockieren, und der Upstream-Socket (Unix oder TCP) ist konsistent konfiguriert. Viele 502/504-Fehler gehen auf falsch gesetzte Read-Timeouts oder erschöpfte Backlogs zurück. Wer FPM per TCP anspricht, sollte die Latenz des Netz-Stacks und das Risiko halb-offener Verbindungen im Blick behalten; für lokale Deployments bevorzuge ich meist Unix-Sockets.
Container/VM-Besonderheiten
In Containern gelten die cgroup-Limits, nicht zwingend die Host-Werte. Ich dimensioniere Pools explizit für das Container-RAM und teste mit künstlichen Lastspitzen, ob OOM-Killer greifen könnten. Ein zu knapp bemessenes Limit führt zu harten Abbrüchen. Ebenso ist Swapping in Containern oft unerwünscht – also lieber etwas konservativer bei pm.max_children planen und Applikations-Caching priorisieren.
CPU- und I/O-Charakter erkennen
Ich beurteile mit htop/iostat, ob Workloads CPU– oder I/O-gebunden sind. Hohe CPU-Auslastung bei niedrigen I/O-Waits deutet auf Rechenlast hin – hier limitiere ich die Worker näher an der Kernzahl. Hohe I/O-Waits rechtfertigen mehr Worker, weil Wartezeiten auf Datenbank, Netzwerk oder Filesystem überlappt werden. Die Grenze erkennst du daran, dass die Latenz trotz zusätzlicher Worker nicht mehr sinkt, aber die Load deutlich steigt.
Typische 502/504-Muster schnell entschlüsseln
- 504 Gateway Timeout: Webserver-Timeout kleiner als PHP-Ausführungszeit oder blockierter Pool (Queue voll).
- 502 Bad Gateway: FPM nicht erreichbar (Socket/Port), Crash/Restart während der Anfrage oder zu kleine Puffer.
- Spikes kurz nach Deploy: OPcache kalt, Autoloader/Composer-Optimierungen prüfen, Warmup einplanen.
Ich korreliere Webserver-Errorlog, FPM-Errorlog und Status-Seite im gleichen Zeitfenster. Das zeigt, ob das Problem vor, in oder nach FPM liegt.
Messhandwerk: Speicherkosten korrekt erfassen
Für die RAM-Planung schaue ich nicht nur auf RSS, sondern auf den PSS (Proportional Set Size), weil er geteilte Seiten (z. B. OPcache) fair auf Prozesse verteilt. Werkzeuge wie smem oder pmap helfen, realistische prozessbezogene Werte zu ermitteln. In der Praxis reichen aber oft Stichproben unter Last: mehrere Prozesse markieren, Mittelwert bilden, mit Reserve multiplizieren – das trifft die Realität besser als Theoriewerte aus Foren.
Mini-Checkliste für schnelle Iterationen
- Lastprofil erfassen (RPS, 50/95/99-Perzentile, Parallelität).
- Prozess-Footprint messen (PSS, nicht nur RSS) und pm.max_children mit Reserve wählen.
- Modus passend zum Muster wählen: Static (konstant), Dynamic (wechselnd), Ondemand (viel Leerlauf).
- pm.max_requests setzen, Wachstum der Worker beobachten, bei Bedarf nachregeln.
- OPcache dimensionieren und Warmup/Preload prüfen, um Kaltstarts zu dämpfen.
- Status-Page und Slowlog aktivieren, Queue- und Spawn-Meldungen auswerten.
- Webserver-Timeouts und Puffer gegen FPM- und App-Zeiten abgleichen.
- Limits mit Downstream-Systemen harmonisieren (DB, Caches, externe APIs).
- Änderungen dokumentieren, nach Deployments gezielt messen und iterieren.
Kompakte Zusammenfassung
Static liefert die gleichmäßigste Reaktionszeit und passt zu konstantem Traffic mit reichlich RAM. Dynamic balanciert Flexibilität und Effizienz und punktet bei wechselnden Mustern. Ondemand spart Speicher bei Leerlauf und eignet sich für viele ruhende Sites, erkauft sich aber Kaltstart-Latenz. Mit sauberer Ressourcenrechnung, Monitoring und kleinen Iterationen triffst du belastbare Entscheidungen. Halte Prozesse so klein wie nötig, setze OPcache ein und wähle den Modus, der zu deinem echten Profil passt.
Mit diesen Leitplanken holst du stabile Leistung bei vertretbarem Verbrauch heraus. Konfiguration bleibt kein Ratespiel, wenn Zahlen auf dem Tisch liegen. Kleine Schritte bringen oft die größten Effekte. Miss, justiere und dokumentiere. So bleiben deine PHP-FPM-Pools schnell, sparsam und vorhersehbar.


