PHP Request Queueing begrenzt, wie viele Anfragen dein Server gleichzeitig verarbeitet, und entscheidet damit über Antwortzeit, Fehlerraten und Nutzererlebnis. Ich zeige dir, wie ich Verarbeitungslimits setze, Engpässe eliminiere und durch abgestimmte Parameter eine konstante Auslieferung erreiche.
Zentrale Punkte
Damit du sofort loslegen kannst, fasse ich die wichtigsten Stellschrauben für PHP-FPM zusammen.
- pm.max_children: Obergrenze für gleichzeitige PHP-Prozesse, passend zum RAM kalkulieren.
- listen.backlog: Kurzfristige Pufferung von Verbindungsversuchen bei Lastspitzen maximieren.
- pm.max_requests: Prozesse regelmäßig recyceln, um Memory-Leaks und Aufblähung zu vermeiden.
- Timeouts: request_terminate_timeout, max_execution_time und Webserver-Timeouts konsistent setzen.
- Metriken: max children reached, listen queue und Slowlogs kontinuierlich prüfen.
Ich setze auf klare Kennzahlen und messbare Effekte, damit jede Anpassung an Limits nachvollziehbar bleibt. Pro Änderung beobachte ich Logs und Antwortzeiten, bevor ich den nächsten Schritt plane und Werte schrittweise erhöhe oder senke. So verhindere ich Nebeneffekte wie Memory-Swapping, das jede Queue dramatisch verlängert. Mit diesem Vorgehen bringe ich Lastspitzen in geordnete Bahnen und halte die Antwortzeiten stabil. Ziel ist eine ausgewogene Auslastung, die Ressourcen effizient nutzt, ohne den Host zu überfordern.
Wie PHP Request Queueing in PHP-FPM arbeitet
Jede eingehende HTTP-Anfrage benötigt einen eigenen Worker, und ein Worker bedient immer nur eine Anfrage zur selben Zeit. Sind alle Prozesse belegt, landen weitere Aufrufe in der Queue und warten, bis ein Prozess frei wird. Wächst diese Warteschlange, verlängern sich die Antwortzeiten, und Fehler wie 502/504 treten häufiger auf. Ich achte deshalb auf ein sinnvolles Verhältnis aus Prozessanzahl und verfügbarem Speicher, statt blind auf maximale Parallelität zu setzen. So erreiche ich eine konstante Durchsatzrate, ohne dass RAM oder CPU wegbrechen.
Prozess-Manager-Modi sauber wählen
Neben den Grenzwerten entscheidet der pm-Modus über Reaktionsfähigkeit und Ressourcenverbrauch:
- pm = dynamic: Ich definiere start_servers, min_spare_servers und max_spare_servers. Dieser Modus ist mein Standard für variable Last, weil er schnell auf Anstiege reagiert und warme Prozesse bereithält.
- pm = ondemand: Prozesse entstehen erst bei Bedarf und werden nach process_idle_timeout beendet. Das spart RAM bei seltenen Zugriffen (Admin, Staging, Cron-Endpoints), kann aber bei plötzlichen Spitzen Kaltstarts und höhere Latenz erzeugen. Ich setze ihn deshalb gezielt und mit großzügigem Backlog ein.
- pm = static: Eine feste Zahl an Prozessen. Ideal, wenn ich eine harte Obergrenze und besonders vorhersagbare Latenzen brauche (z. B. L7-Proxy vor wenigen, aber kritischen Endpunkten). Der RAM-Bedarf ist klar kalkulierbar, doch ungenutzte Prozesse binden Speicher.
Ich entscheide pro Pool, welcher Modus zum Profil passt. Für Frontends mit wechselnder Last setze ich meist dynamic ein, für Utility-Pools ondemand und für dedizierte, latenzkritische Services ggf. static.
pm.max_children richtig bestimmen
Den wichtigsten Hebel bildet pm.max_children, denn dieser Wert definiert, wie viele Anfragen gleichzeitig laufen dürfen. Ich kalkuliere die Startgröße mit der Faustformel: (frei verfügbarer RAM – 2 GB Reserve) geteilt durch durchschnittlichen Speicher pro PHP-Prozess. Als grobe Annahme setze ich 40–80 MB pro Prozess an und fahre auf einem 32-GB-Host zunächst mit 200–300 Prozessen los. Unter Live-Last erhöhe oder senke ich stufenweise und prüfe, ob die Wartezeit der Queue fällt und die Fehlerquote sinkt. Wer tiefer einsteigen will, findet Hintergründe zu Start- und Grenzwerten unter pm.max_children optimieren.
Start-, Spare- und Backlog-Werte abstimmen
Ich setze pm.start_servers auf etwa 15–30 Prozent von pm.max_children, damit zu Beginn genügend Prozesse bereitstehen und Kaltstarts ausbleiben. Mit pm.min_spare_servers und pm.max_spare_servers lege ich ein vernünftiges Fenster für freie Prozesse fest, damit neue Requests nicht warten und gleichzeitig kein unnötiger Leerlauf Speicher bindet. Besonders wichtig ist listen.backlog: Dieser Kernel-Puffer hält kurzzeitig zusätzliche Verbindungsversuche, wenn alle Worker beschäftigt sind. Bei Lastspitzen setze ich hohe Werte (z. B. 65535), damit die Warteschlange nicht vor dem FPM-Pool abreißt. Tiefergehende Hintergründe zum Zusammenspiel aus Webserver, Upstream und Puffern gibt der Überblick zu Webserver-Queueing.
Request-Laufzeiten begrenzen und Prozesse recyceln
Ich verhindere schleichende Speicheraufschwünge mit pm.max_requests, das jeden Prozess nach X Requests neu startet. Unauffällige Anwendungen fahren oft gut mit 500–800, bei Verdacht auf Memory-Leaks reduziere ich auf 100–200 und beobachte den Effekt. Zusätzlich kapselt request_terminate_timeout Ausreißer, indem extrem lang laufende Anfragen nach einer festen Zeit enden. Wichtig ist die Konsistenz: Ich halte PHPs max_execution_time und die Webserver-Timeouts im gleichen Korridor, damit nicht eine Schicht früher abbricht als die andere. Dieses Zusammenspiel hält die Worker frei und schützt den Pool vor Stau.
Warteschlangen sichtbar machen: Logs und Metriken
Ich lese regelmäßig die FPM-Logs und achte auf max children reached, denn dieser Eintrag zeigt an, dass der Obergrenze der Prozesse erreicht wurde. Parallel beobachte ich die listen queue, die stiegenden Rückstau im Eingangspuffer erkennbart macht. In Kombination mit request_slowlog_timeout erhalte ich Stack-Traces zu langsamen Stellen im Code und isoliere Datenbank- oder API-Bremsen. Ich korreliere upstream_response_time aus den Webserver-Logs mit request_time und Statuscodes, um die Quelle langer Antwortzeiten einzugrenzen. So erkenne ich, ob der Engpass in PHP-FPM, der Datenbank oder dem Upstream-Netzwerk steckt.
Workload-Profile: CPU-gebunden vs. IO-gebunden
Bei CPU-lastigen Prozessen skaliere ich die Parallelität vorsichtig und orientiere mich eng an der vCPU-Zahl, weil zusätzliche Prozesse kaum Durchsatz bringen. Handelt es sich vorwiegend um IO-Last mit Datenbankzugriffen oder externen APIs, kann ich mehr Prozesse zulassen, solange das RAM-Budget reicht. E-Commerce-Checkouts profitieren von längeren Timeouts (z. B. 300 s), um Zahlungswege ohne Abbrüche abzuschließen. Flash-Sales fange ich ab, indem ich listen.backlog hoch setze und das Spare-Fenster vergrößere. Hinweise zur Balance zwischen Prozessanzahl und Host-Leistung bündelt der Leitfaden zu PHP-Workers als Flaschenhals.
Beispielrechnungen und Dimensionierung
Ich kalkuliere zunächst den Speicher je Prozess und leite daraus eine sinnvolle Obergrenze ab. Danach teste ich unter realer Last und beobachte, ob die Queue abnimmt und der Durchsatz steigt. Konservative Startwerte senken das Risiko von Swapping und halten die Reaktionszeit gleichmäßig. Anschließend verfeinere ich in kleinen Schritten, um Nebenwirkungen sicher zu bemerken. Die folgende Tabelle bietet Orientierung zu Startwerten und Effekten auf die Queue.
| Parameter | Wirkung | Startwert (Beispiel) | Hinweis |
|---|---|---|---|
| pm.max_children | Max. gleichzeitige Prozesse | 200–300 (bei 32 GB) | Mit RAM-Budget und Prozessgröße abgleichen |
| pm.start_servers | Initiale Worker-Anzahl | 15–30 % von max_children | Kaltstarts vermeiden, aber Leerlauf gering halten |
| pm.min_spare_servers | Freie Worker Minimum | z. B. 20 | Direkte Aufnahme neuer Requests |
| pm.max_spare_servers | Freie Worker Maximum | z. B. 40 | RAM-Verbrauch von Leerlaufprozessen begrenzen |
| listen.backlog | Kernel-Puffer für Verbindungsversuche | 65535 | Spitzenlast abfedern und Verbindungsabbrüche senken |
| pm.max_requests | Recycling Intervall | 500–800, bei Leaks 100–200 | Speicheraufblähung und Hänger minimieren |
| request_terminate_timeout | Hartes Request-Limit | 300–600 s | Konsistent mit PHP- und Webserver-Timeouts |
Praxis-Templates für PHP-FPM-Pools
Für einen Shop mit vielen Lesezugriffen setze ich moderate Prozesszahlen und erhöhe das Spare-Fenster, damit Anfragen nicht anstehen. Bei Content-Seiten mit Caching reichen oft deutlich weniger Worker, solange NGINX oder Apache statische Inhalte effizient liefern. Multi-Pool-Setups trenne ich nach Anwendungsteilen, die unterschiedliche Speicherprofile besitzen, damit kein schwerer Pool die anderen verdrängt. Für Cron- oder Queue-Worker definiere ich separate Pools mit eigenem Timeout-Regelwerk. So halte ich den interaktiven Traffic frei und bremse keine Nutzeraktionen aus.
Webserver-Timeouts, Upstream und Sockets
Ich halte FastCGI- und Proxy-Timeouts von Nginx oder Apache im gleichen Fenster wie die FPM-Timeouts, damit kein Layer zu früh abbricht. Unix-Sockets ziehe ich TCP vor, sofern beide Dienste auf demselben Host laufen, weil die Latenz minimal bleibt. Bei verteilten Setups nutze ich TCP mit stabilen Keepalive-Werten und einem ausreichend großen Verbindungs-Pool. Für hohe Parallelität stimmen nginx worker_connections und die FPM-Backlog-Werte aufeinander ab. Damit bleiben Weiterleitungen zügig, und ich verhindere Leerlauf durch zu enge Upstream-Limits.
Caching, OPCache und Datenbank als Stellhebel
Viele Server-Probleme löse ich, indem ich teure Operationen reduziere und die Antwortzeit senke. Ich schalte OPCache ein, erhöhe das Memory-Limit des Caches sinnvoll und sorge für eine hohe Cache-Hitrate. Bei wiederkehrenden Ergebnissen setze ich Anwendungs-Caching ein, damit PHP-Prozesse schneller fertig werden. Auf Datenbankseite optimiere ich langsame Queries und aktiviere Query-Caches, die zum eingesetzten System passen. Jede gesparte Millisekunde entlastet die Queue und erhöht den Durchsatz pro Worker.
Notfall-Mechanismen und Neustarts absichern
Ich aktiviere emergency_restart_threshold und emergency_restart_interval, damit der FPM-Master neu startet, wenn zu viele Kinder kurz hintereinander crashen. Dieser kontrollierte Neustart verhindert Kettenreaktionen und hält den Dienst verfügbar. Parallel setze ich klare Limits für Speicher und Prozessanzahl, um Eskalationen abzufangen. Health-Checks auf der Upstream-Seite nehmen fehlerhafte Backends automatisch aus dem Pool und verringern Fehlerraten. So bleibt die Verfügbarkeit erhalten, während ich die eigentliche Ursache untersuche.
Betriebssystem- und Systemd-Limits feinjustieren
Damit listen.backlog tatsächlich greift, gleiche ich die Kernel-Limits ab. Der OS-Wert net.core.somaxconn muss mindestens so hoch liegen wie der eingestellte Backlog, sonst kappt das System die Warteschlange. Ich prüfe außerdem die Zahl erlaubter Datei-Deskriptoren: Im FPM-Pool kann ich rlimit_files setzen, auf Service-Ebene sichere ich LimitNOFILE (systemd) und auf Kernel-Ebene fs.file-max. Der Webserver braucht ähnliche Reserven, damit er nicht früher an seine Grenzen stößt.
Für stabilere Latenzen reduziere ich vm.swappiness, damit der Kernel aktiv genutzte Speicherseiten nicht frühzeitig verdrängt. In latenzkritischen Setups deaktiviere ich Transparent Huge Pages, um lange Page-Faults zu vermeiden. Läuft FPM über TCP, gleiche ich zusätzlich net.ipv4.tcp_max_syn_backlog und Reuse-/Keepalive-Parameter ab. Solche OS-Details wirken unscheinbar, doch sie entscheiden, ob Queues glatt ablaufen oder ob Verbindungen schon vor FPM zurückgewiesen werden.
Speicher je Prozess belastbar messen
Statt pauschal zu schätzen, messe ich den Realverbrauch pro Worker unter echter Last. Ich nutze Werkzeuge wie ps, smem oder pmap, filtere auf php-fpm-Kinder und bilde den Durchschnitt der RSS-Werte, während Requests laufen. Wichtig ist, die gemeinsame, geteilte OPCache-Nutzung zu berücksichtigen: Shared Memory zählt man nicht mehrfach. Aus dem gemittelten Wert leite ich pm.max_children ab und plane zusätzlich eine Reserve ein, damit die Maschine auch unter Peaks nicht ins Swapping kippt.
Ich wiederhole diese Messung nach Funktions- oder Release-Änderungen. Neue Features, mehr Abhängigkeiten oder Änderungen an Frameworks können den Footprint pro Prozess deutlich anheben. So bleibt die Prozessanzahl realistisch und die Queue kurz.
PHP-FPM-Status, Ping und lebende Metriken
Für eine schnelle Lageeinschätzung aktiviere ich pm.status_path und einen Ping-Endpunkt (ping.path/ping.response). Darüber sehe ich Kennzahlen wie accepted conn, listen queue len, idle/busy processes, max children reached und deren Verlauf. Ich lese diese Werte periodisch aus und lege Schwellwerte fest: Steigt listen queue dauerhaft, erhöhe ich entweder Prozesse oder beseitige die Ursache langsamer Requests. Springt max children reached an, während idle low bleibt, ist der Pool zu klein oder blockiert durch lange Läufer.
Zusätzlich separate ich Pools mit unterschiedlichen Profilen, damit Spikes in einem Bereich (z. B. API-Importe) den interaktiven Traffic nicht in die Knie zwingen. Für Diagnosefälle erhöhe ich temporär den log_level und lasse den slowlog mehr Samples erfassen, reduziere ihn danach aber wieder, um I/O-Last gering zu halten.
Uploads, Buffering und große Request-Bodies
Große Uploads können Worker unnötig binden, wenn PHP den Request-Body erst lesen muss. Ich sorge dafür, dass der Webserver puffert (z. B. fastcgi_request_buffering bei NGINX), sodass FPM erst startet, wenn der Body vollständig vorliegt. So blockiert kein Worker während des Uploads. Mit client_max_body_size, post_max_size und max_input_time steuere ich, wie groß und wie lange Anfragen sein dürfen, ohne Endpunkte zu gefährden. Liegen Dateien zwischen, weise ich ausreichend schnellen Temp-Speicher (SSD) zu, um Pufferstaus zu vermeiden.
Für Endpunkte mit sehr großen Bodies (z. B. Exporte/Imports) definiere ich dedizierte Pools mit eigenen Timeouts und geringerer Parallelität. Damit bleiben die Standard-Worker frei und die Queue der wichtigen Nutzeraktionen kurz.
Datenbank-Verbindungen und Pool-Grenzen
Die beste FPM-Einstellung nützt nichts, wenn die Datenbank früher limitiert. Ich richte die maximale Zahl gleichzeitiger PHP-Prozesse an der real verfügbaren DB-Kapazität aus. Bei persistenten Verbindungen oder Connection-Pools sorge ich dafür, dass die Summe aller Pools unter max_connections bleibt. Entstehen viele kurze Queries, hilft es, die PHP-Parallelität moderat zu begrenzen, damit die DB nicht thrashend zwischen tausenden Sessions wechselt.
Langsame Transaktionen verursachen schnell einen Stau bis in die FPM-Queue. Ich analysiere deshalb Lock-Wartezeiten, Indexnutzung und Query-Pläne. Jede Reduktion der DB-Laufzeit kürzt unmittelbar die PHP-Belegdauer und baut Queue-Längen ab.
Releases und Rollouts ohne Spike
Beim Ausrollen neuer Versionen vermeide ich kalte Caches und Prozess-Stürme. Ich nutze reload statt harter Restarts, damit bestehende Worker Requests sauber beenden (process_control_timeout beachten). Den OPCache wärme ich frühzeitig auf, indem ich kritische Pfade vor dem Umschalten einmal abfahre oder mit Preloading arbeite. So verhindere ich, dass viele Worker zeitgleich Klassendateien parsen und die Antwortzeit sprunghaft steigt.
Bei Blue/Green- oder Canary-Strategien lasse ich die Last schrittweise ansteigen und beobachte die Statusseiten. Erst wenn Queue, Error-Rate und Latenzen stabil bleiben, erhöhe ich den Traffic-Anteil. Dieses kontrollierte Vorgehen schützt vor Lastspitzen während des Deployments.
Container- und VM-Besonderheiten
In Containern ist die wahrgenommene Gesamtspeichermenge oft niedriger als der Host meldet. Ich richte pm.max_children strikt am cgroup-Limit aus und plane eine Reserve gegen den OOM-Killer ein. Memory-Limits in PHP (memory_limit) und der Footprint pro Prozess müssen zusammenpassen, sonst genügt ein einziger Ausreißer, um den Container zu beenden.
Fehlt Swap im Container, sind harte Abbrüche wahrscheinlicher. Deshalb halte ich die Prozesse konservativ, aktiviere Recycling, und überwache die RSS-Spitzen in Produktionslast. Mehrere schlanke Pools sind hier oft robuster als ein großer, monolithischer Pool.
Steuerbare Degradierung und Backpressure
Wächst die Queue schneller, als sie abgebaut werden kann, setze ich auf kontrollierte Degradierung: Ich liefere bei Überlast bewusst 503 mit Retry-After für nichtkritische Endpunkte, reduziere teure Features (z. B. Live-Suchen) und limitiere parallele Zugriffe auf Hotspots. So bleibt das System ansprechbar, während ich die Ursache behebe, statt dass alle Nutzer in Timeouts laufen.
Kurz zusammengefasst
Ich bringe PHP Request Queueing unter Kontrolle, indem ich die Anzahl gleichzeitiger Prozesse klug zum RAM-Budget und zur Art der Last abstimme. Hohe Backlog-Werte puffern Spitzen, Timeouts auf allen Ebenen greifen sauber ineinander, und Recycling entfernt schleichende Speicherprobleme. Logs und Metriken zeigen mir, ob die Queue wächst, wo Requests hängen und wann ich nachschärfen sollte. Mit behutsamen Anpassungen und gezieltem Caching senke ich die Verarbeitungszeit je Request und steigere den Durchsatz. So liefern Server konsistent und vermeiden teure Timeouts im Alltag.


