pm.max_children für PHP-FPM richtig berechnen statt raten
Die Meldung server reached pm.max_children heißt nicht, dass Sie den Wert verdoppeln sollen. Messen, rechnen, Betriebsart wählen und danach belegen, dass der Wert wirklich passt.
Im Protokoll von PHP-FPM steht irgendwann diese Zeile, und sie liefert den Rat gleich mit:
WARNING: [pool www] server reached pm.max_children setting (5), consider raising it
Der Rat ist nicht falsch, nur unvollständig. pm.max_children ist die einzige Stellschraube in PHP-FPM, bei der ein zu hoher Wert gefährlicher ist als ein zu niedriger. Zu niedrig kostet Wartezeit und im Extremfall einen 502. Zu hoch kostet den Arbeitsspeicher des gesamten Servers, und dann sucht sich der Kernel selbst aus, welchen Prozess er beendet. Erfahrungsgemäß ist das nicht PHP, sondern die Datenbank.
Diese Anleitung zeigt den Weg vom Raten zum Rechnen: den Speicherbedarf eines Arbeitsprozesses messen, das Budget bestimmen, die Betriebsart wählen und anschließend belegen, dass der Wert stimmt.
Alle Angaben beziehen sich auf Debian 13 (trixie), Debian 12 (bookworm), Ubuntu 24.04 LTS und Ubuntu 22.04 LTS. Die Befehle sind für den Betrieb als root geschrieben, als normaler Benutzer stellen Sie sudo voran. Ersetzen Sie überall die PHP-Versionsnummer durch die Ihres Systems.
| System | PHP | Dienst | Pool-Datei | FPM-Protokoll |
| Debian 13 (trixie) | 8.4 | php8.4-fpm | /etc/php/8.4/fpm/pool.d/www.conf | /var/log/php8.4-fpm.log |
| Debian 12 (bookworm) | 8.2 | php8.2-fpm | /etc/php/8.2/fpm/pool.d/www.conf | /var/log/php8.2-fpm.log |
| Ubuntu 24.04 LTS | 8.3 | php8.3-fpm | /etc/php/8.3/fpm/pool.d/www.conf | /var/log/php8.3-fpm.log |
| Ubuntu 22.04 LTS | 8.1 | php8.1-fpm | /etc/php/8.1/fpm/pool.d/www.conf | /var/log/php8.1-fpm.log |
Welche Versionen installiert sind, zeigt ein Blick ins Verzeichnis. Auf Servern nach einem Distributionsupgrade sind es oft zwei:
ls /etc/php/
ls /etc/php/*/fpm/pool.d/
Was pm.max_children wirklich begrenzt
Der Wert begrenzt nicht Besucher und nicht Verbindungen, sondern die Anzahl der PHP-Anfragen, die im selben Augenblick ausgeführt werden. Eine Anfrage von 80 Millisekunden belegt einen Arbeitsprozess für 80 Millisekunden und gibt ihn dann frei. Daraus folgt fast alles Weitere:
- Viel Verkehr braucht wenige Prozesse, solange die Skripte schnell sind. 40 Anfragen pro Sekunde mit je 80 Millisekunden ergeben gut drei gleichzeitige Anfragen, nicht 40.
- Eine langsame Stelle kippt die Rechnung. Ein Aufruf an eine fremde Programmierschnittstelle ohne eigene Frist hält einen Prozess fünf Sekunden besetzt, obwohl dieser nichts tut. Fünf solcher Aufrufe pro Sekunde binden dauerhaft 25 Prozesse.
- Wartende Verbindungen belegen keinen Prozess. Eine Keep-Alive-Verbindung zu nginx kostet einen Dateideskriptor, aber keinen Arbeitsprozess.
Sind alle Prozesse belegt, warten neue Anfragen in der Annahmewarteschlange des Sockets. Deren Größe steht in der Pool-Datei unter listen.backlog, ab Werk 511, und der Kernel begrenzt sie zusätzlich über net.core.somaxconn, das auf allen vier Systemen bei 4096 liegt:
sysctl net.core.somaxconn
Solange die Warteschlange reicht, merkt der Besucher nur längere Ladezeiten. Ist auch sie voll, weist der Kernel die Verbindung ab, nginx protokolliert 11: Resource temporarily unavailable, und beim Besucher kommt ein 502 an. Zur Abgrenzung von den übrigen Ursachen: nginx 502 Bad Gateway beheben.
Ein Detail, das viel Verwirrung stiftet: Die Warnung erscheint einmal je Sättigungsphase, nicht je betroffener Anfrage. FPM setzt intern eine Merkmarke und löscht sie erst, wenn wieder ein Prozess frei ist. Eine einzelne Zeile kann für eine ganze Stoßzeit stehen. Wer Zeilen zählt, unterschätzt das Problem. Der ehrliche Messwert ist der Zähler max children reached auf der Statusseite, die weiter unten aufgerufen wird.
Der Rückweg, bevor Sie etwas ändern
Zwei Dinge können hier schiefgehen, und beide treffen den laufenden Betrieb.
Erstens: FPM startet nicht mehr. Ein systemctl reload schickt dem Hauptprozess das Signal USR2, woraufhin dieser sich mit der neuen Konfiguration selbst neu startet. Ist sie ungültig, bricht die Initialisierung ab und der Hauptprozess beendet sich. Aus einer laufenden Instanz wird also eine tote. Deshalb ausnahmslos erst prüfen, dann laden:
php-fpm8.2 -t
Bei Erfolg endet die Ausgabe mit test is successful.
Zweitens: Der Wert ist zu hoch. Dann ist der Server nicht sofort kaputt, sondern erst bei der nächsten Lastspitze, und zwar so gründlich, dass auch eine SSH-Anmeldung hängt oder gar nicht zustande kommt. Dafür brauchen Sie einen zweiten Weg auf den Server.
Bei den KVM-Rootservern und Dedicated Servern von KernelHost ist das die VNC-Konsole im Kundenbereich. Sie hängt an der Virtualisierungsschicht beziehungsweise am Anschluss selbst und ist auch dann erreichbar, wenn der SSH-Dienst wegen Speichermangels nicht mehr antwortet. Melden Sie sich vorher einmal darüber an. Ein Rettungsweg, den man im Notfall zum ersten Mal ausprobiert, ist keiner.
Legen Sie außerdem eine Kopie der Pool-Datei an, mit Zeitstempel, damit ein zweiter Versuch die erste Kopie nicht überschreibt:
mkdir -p /root/backups
cp -a /etc/php/8.2/fpm/pool.d/www.conf /root/backups/www.conf.$(date +%F-%H%M)
ls -l /root/backups/
Der Weg zurück ist dann drei Zeilen lang:
cp -a /root/backups/www.conf.2026-09-03-1015 /etc/php/8.2/fpm/pool.d/www.conf
php-fpm8.2 -t
systemctl reload php8.2-fpm
Ein Geländer für den Fall, dass Sie sich verrechnen
Ein Speicherlimit auf der systemd-Einheit sorgt dafür, dass eine Fehleinschätzung einen PHP-Arbeitsprozess trifft und nicht die Datenbank: Der Kernel beendet dann innerhalb der Kontrollgruppe von FPM, statt sich systemweit den größten Prozess zu suchen.
mkdir -p /etc/systemd/system/php8.2-fpm.service.d
cat > /etc/systemd/system/php8.2-fpm.service.d/speicher.conf <<'EOF'
[Service]
MemoryHigh=1500M
MemoryMax=2G
EOF
systemctl daemon-reload
systemctl restart php8.2-fpm
Prüfen, ob es angekommen ist:
systemctl show php8.2-fpm -p MemoryHigh -p MemoryMax
MemoryHigh bremst und räumt auf, MemoryMax ist die harte Grenze. Alle vier Systeme nutzen die vereinheitlichte Kontrollgruppen-Hierarchie, die Werte wirken also unmittelbar. Das ist ein Geländer, kein Ersatz für die Rechnung: Wird die Grenze erreicht, sieht der betroffene Besucher weiterhin einen Fehler, nur eben nicht der ganze Server. Zum sauberen Ablegen solcher Ergänzungsdateien: systemd-Service erstellen.
Und die dritte Regel, die keinen Befehl braucht: eine Änderung nach der anderen. Wer die Betriebsart, pm.max_children und die Spare-Werte gleichzeitig anfasst, weiß hinterher nicht, was gewirkt hat.
Bestandsaufnahme: welcher Pool gilt
Jeder Pool hat sein eigenes pm.max_children, und für den Speicher zählt die Summe über alle Pools:
grep -n "^pm" /etc/php/*/fpm/pool.d/*.conf
Im Auslieferungszustand steht auf allen vier Systemen dasselbe:
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
Das sind keine Empfehlungen, sondern Platzhalter, mit denen PHP auch auf einer sehr kleinen Testmaschine startet. Dazu kommt eine globale Obergrenze über alle Pools hinweg:
grep -n "^process.max" /etc/php/*/fpm/php-fpm.conf
Bleibt die Ausgabe leer, ist die Zeile auskommentiert und es gilt der Standardwert 0, also keine globale Begrenzung. Auf Servern mit vielen Pools verhindert genau diese Zeile, dass die Summe den Speicher sprengt, sobald mehrere Pools gleichzeitig unter Last stehen.
Maßgeblich ist am Ende nicht die Datei, sondern das, was FPM daraus macht. Der Schalter -tt gibt die Konfiguration vollständig aufgelöst aus, samt aller Vorgabewerte, die in keiner Datei stehen:
php-fpm8.2 -tt 2>&1 | grep -E "^\[|pm |pm\.|listen ="
Diese Ausgabe ist später auch der Nachweis, dass eine Änderung angekommen ist.
Den Speicherbedarf eines Arbeitsprozesses messen
Hier werden die meisten Anleitungen ungenau. Drei Zahlen werden regelmäßig verwechselt:
memory_limit, ab Werk 128M im FPM-Betrieb: eine Obergrenze je Anfrage, kein Verbrauch. Ein Skript, das 12 MB braucht, braucht auch mit 512M nur 12 MB.- RSS: alles, was gerade im Arbeitsspeicher liegt, einschließlich des gemeinsam genutzten Opcache und der geteilten Bibliotheken. Wer RSS über 20 Prozesse aufaddiert, zählt denselben Opcache zwanzigmal.
- PSS: gemeinsam genutzte Seiten werden durch die Zahl ihrer Nutzer geteilt. Die einzige der drei Zahlen, die sich sinnvoll aufsummieren lässt.
Für die Rechnung brauchen Sie PSS. Der Kernel liefert ihn fertig zusammengefasst in /proc/<pid>/smaps_rollup, lesbar als root:
pgrep -f "php-fpm: pool www" | while read -r p; do
awk '/^Pss:/ {print $2}' "/proc/$p/smaps_rollup"
done | awk '{s+=$1; n++} END {
if (!n) { print "keine Arbeitsprozesse gefunden"; exit }
printf "Prozesse: %d Summe: %.0f MiB Mittel: %.1f MiB\n", n, s/1024, s/1024/n
}'
Zum Vergleich derselbe Pool über RSS:
ps -eo pid,rss,args --sort=-rss | grep '[p]hp-fpm' | head
Die RSS-Summe liegt je nach Opcache-Größe deutlich höher. Wer damit rechnet, bekommt ein zu kleines pm.max_children und kauft sich Wartezeit, die es nicht bräuchte.
Zwei Bedingungen entscheiden, ob die Messung etwas taugt. Messen Sie unter echter Last, nicht nach einem Neustart: Ein frisch abgespaltener Prozess ist billig, weil er die Speicherseiten des Hauptprozesses zunächst nur mitbenutzt, und wird erst mit den ersten Anfragen teuer. Und nehmen Sie nicht den Mittelwert allein: Liegt das Mittel bei 60 MiB und der größte Prozess bei 190 MiB, rechnen Sie nicht mit 60.
Die zweite Quelle: das Zugriffsprotokoll von FPM
FPM protokolliert auf Wunsch je Anfrage Spitzenverbrauch und Laufzeit. Beide Zeilen stehen auskommentiert in der Pool-Datei:
access.log = /var/log/php8.2-fpm.access.log
access.format = "%R - %u %t \"%m %r%Q%q\" %s %f %{mili}d %{kilo}M %C%%"
Die Schreibweise %{mili}d ist so korrekt, sie stammt unverändert aus der mitgelieferten Datei und liefert die Laufzeit in Millisekunden, %{kilo}M den Spitzenverbrauch in Kilobyte. Danach prüfen, laden und nachsehen, ob die Datei entsteht:
php-fpm8.2 -t
systemctl reload php8.2-fpm
ls -l /var/log/php8.2-fpm.access.log
Lassen Sie das Protokoll einen vollen Tag mitlaufen, damit die Stoßzeiten enthalten sind. Danach werten Sie den Spitzenverbrauch als Quantile aus:
awk '{print $(NF-1)+0}' /var/log/php8.2-fpm.access.log | sort -n | awk '{v[NR]=$1} END {
if (!NR) exit
p=int(NR*0.95); if (p<1) p=1
printf "Anfragen: %d Median: %d kB p95: %d kB Maximum: %d kB\n", NR, v[int((NR+1)/2)], v[p], v[NR]
}'
Dieselbe Auswertung für die Laufzeit, die Sie gleich für die zweite Rechnung brauchen, holt die Spalte davor:
awk '{print $(NF-2)+0}' /var/log/php8.2-fpm.access.log | sort -n | awk '{v[NR]=$1} END {
if (!NR) exit
p=int(NR*0.95); if (p<1) p=1
printf "Median: %.0f ms p95: %.0f ms Maximum: %.0f ms\n", v[int((NR+1)/2)], v[p], v[NR]
}'
Zwei Einschränkungen. Erstens hängen die Feldpositionen an der oben gezeigten Formatzeile, weil diese mit Laufzeit, Speicher und CPU-Anteil endet. Wer access.format ändert, muss sie anpassen. Zweitens ist %{kilo}M die Speicherbuchhaltung von PHP selbst und damit eine Untergrenze: Programmcode, Erweiterungen und der Speicher, den die C-Bibliothek nach einer Anfrage nicht sofort zurückgibt, fehlen darin. Für die Formel gilt deshalb PSS. Das Zugriffsprotokoll dient dazu, die Ausreißer-Skripte zu finden, die einen Prozess dauerhaft groß machen.
Schalten Sie das Protokoll danach wieder ab oder richten Sie eine Rotation dafür ein. Die mitgelieferte Regel erfasst nur das Fehlerprotokoll, und eine volle Platte erzeugt Fehlerbilder, die mit PHP-FPM nichts mehr zu tun haben: Festplatte voll unter Linux aufräumen.
Die Rechnung
Es gibt zwei Zahlen: eine Obergrenze aus dem Arbeitsspeicher und einen Bedarf aus der Last. Der richtige Wert ist der kleinere von beiden.
Obergrenze aus dem Arbeitsspeicher
pm.max_children = Budget für PHP / PSS je Arbeitsprozess
Das Budget ist nicht der gesamte Arbeitsspeicher:
free -m
Die Spalte available berücksichtigt bereits den freiräumbaren Zwischenspeicher, enthält aber nicht den Speicher, den Ihre Arbeitsprozesse gerade halten. Das Budget ist also available plus die gemessene PSS-Summe, abzüglich einer Reserve. Für die Reserve haben sich rund 20 Prozent des Gesamtspeichers bewährt, mindestens aber 512 MB. Sie fängt ab, was in keiner Messung auftaucht: einen wachsenden Pufferpool der Datenbank, ein Backup, ein Paketupgrade, eine Importmaske zur Unzeit.
| Gesamt-RAM | Dauerhaft belegt | Reserve | Budget für PHP | PSS je Prozess | pm.max_children |
| 4 GB | 1,5 GB | 0,8 GB | 1,7 GB | 60 MB | 29 |
| 8 GB | 3,0 GB | 1,6 GB | 3,4 GB | 80 MB | 43 |
| 16 GB | 6,0 GB | 3,2 GB | 6,8 GB | 110 MB | 63 |
Runden Sie immer ab. Ein Prozess mehr bringt nichts, ein Prozess zu viel kann alles kosten.
Bedarf aus der Last
benötigte Prozesse = Anfragen pro Sekunde × mittlere Laufzeit in Sekunden
Die Anfragen pro Sekunde liefert die Statusseite von FPM: accepted conn geteilt durch start since ergibt den Durchschnitt seit dem letzten Start. Die Laufzeit kommt aus der Auswertung oben, und zwar der p95-Wert und nicht der Median, sonst planen Sie für den ruhigen Nachmittag.
Beispiel: 40 Anfragen pro Sekunde in der Spitze, p95-Laufzeit 300 Millisekunden, macht 12 gleichzeitige Anfragen, mit Zuschlag für kurze Ausschläge etwa 20 bis 25. Liegt die Obergrenze bei 43, tragen Sie den Bedarfswert ein und lassen den übrigen Speicher dort, wo er mehr nützt, nämlich im Zwischenspeicher der Datenbank.
Liegt der Bedarf über der Obergrenze, tragen Sie ihn trotzdem nicht ein. Dann ist entweder der Arbeitsspeicher zu klein, oder die Skripte sind zu langsam, oder es laufen Anfragen durch PHP, die als statische Datei oder aus einem Zwischenspeicher kommen könnten. Alle drei Ursachen lassen sich beheben, ein zu hoher Wert nicht: Er verschiebt nur den Zeitpunkt des Ausfalls.
dynamic, ondemand oder static
Die Betriebsart legt fest, wann Prozesse entstehen und verschwinden. Die Obergrenze pm.max_children gilt in allen drei Fällen.
| Betriebsart | Prozesse beim Start | Verhalten | Speicher | Passt zu |
| dynamic | pm.start_servers | hält zwischen min_spare und max_spare Prozesse im Leerlauf bereit, spaltet bei Bedarf bis max_children ab | schwankt mit der Last | dem Normalfall: ein bis wenige Pools, wechselnde Last |
| ondemand | keine | startet einen Prozess erst bei einer Anfrage, beendet ihn nach pm.process_idle_timeout wieder | im Leerlauf am niedrigsten | vielen Pools auf einem Server, Auftritten mit wenig Verkehr |
| static | pm.max_children | genau diese Anzahl, dauerhaft, ohne Abspalten und Beenden im Betrieb | konstant auf dem Maximum | einem Pool auf dafür vorgesehener Hardware, gleichmäßiger Last |
dynamic ist die Voreinstellung und für den Normalfall richtig. Sie kostet etwas Rechenzeit fürs Abspalten und verlangt, dass die vier Werte zueinander passen.
ondemand spart im Leerlauf spürbar Speicher, wenn ein Dutzend Pools für selten aufgerufene Auftritte auf einer Maschine liegen. Der Preis ist die erste Anfrage nach einer Ruhephase, die auf das Abspalten warten muss. pm.process_idle_timeout steuert, wie lange ein untätiger Prozess überlebt (ab Werk zehn Sekunden), und wirkt ausschließlich in dieser Betriebsart. Beachten Sie außerdem: Die Sättigungswarnung nennt hier max_children ohne das Präfix pm.. Wer nach dem gewohnten Wortlaut sucht, findet nichts.
static ist ehrlich: Was Sie eintragen, wird sofort und dauerhaft belegt, dafür gibt es unter Last keine Überraschung mehr. Sinnvoll ist das, wenn PHP der Hauptverbraucher der Maschine ist. Teilt sich PHP den Arbeitsspeicher mit einer Datenbank, nimmt static dieser die Möglichkeit, zeitweise mehr Zwischenspeicher zu halten.
Unabhängig von der Betriebsart lohnt ein Blick auf pm.max_requests, ab Werk 0 und damit unbegrenzt. Ein Wert wie 500 ersetzt jeden Arbeitsprozess nach 500 Anfragen. Gegen langsam wachsenden Speicherverbrauch durch eine unsaubere Erweiterung ist das wirksam und billig, denn der Opcache bleibt gemeinsam genutzt und wird nicht neu aufgebaut. Setzen Sie den Wert aber nicht auf 20, sonst verbringt FPM seine Zeit mit Abspalten.
start_servers und die Spare-Werte
Diese drei Werte wirken nur bei pm = dynamic und entscheiden darüber, wie schnell FPM auf eine Lastspitze reagiert:
pm.min_spare_servers: so viele untätige Prozesse hält FPM mindestens bereit, der Puffer für Ausschläge. Zu niedrig heißt, jede Spitze wartet erst auf das Abspalten.pm.max_spare_servers: so viele untätige Prozesse duldet FPM höchstens. Ohne diese Grenze behielte FPM nach einer Spitze alle Prozesse und damit deren Speicher.pm.start_servers: so viele Prozesse existieren unmittelbar nach dem Start.
FPM prüft beim Start vier Bedingungen und verweigert den Dienst, wenn eine davon verletzt ist: Beide Spare-Werte müssen größer als null sein, keiner von beiden darf größer als pm.max_children sein, max_spare darf nicht kleiner als min_spare sein, und start_servers muss dazwischen liegen. Fehlt pm.start_servers ganz, rechnet FPM selbst und protokolliert:
NOTICE: [pool www] pm.start_servers is not set. It's been set to 3.
Die Formel dafür ist min_spare + (max_spare - min_spare) / 2, also die Mitte zwischen beiden Spare-Werten. Als Ausgangspunkt funktioniert in der Praxis:
pm.max_children = 40
pm.start_servers = 10
pm.min_spare_servers = 6
pm.max_spare_servers = 16
Leicht übersehen: Auch untätige Prozesse belegen Speicher. Das Budget muss pm.max_children tragen, der Alltagsverbrauch entspricht dagegen ungefähr max_spare zuzüglich der aktiven Prozesse. Ein hoher max_spare hält den Speicher auch nachts um drei belegt.
Der Zusammenhang mit dem Swap
Es ist verlockend, den Auslagerungsspeicher in die Rechnung aufzunehmen. Tun Sie es nicht. Ein Arbeitsprozess, dessen Daten auf der Platte liegen, antwortet um Größenordnungen langsamer und bleibt dadurch länger belegt. Die Zahl gleichzeitiger Anfragen steigt, FPM spaltet weitere Prozesse ab, und diese verdrängen weiteren Speicher. Diese Rückkopplung ist der Grund, warum ein überbuchter Server nicht allmählich schlechter wird, sondern innerhalb weniger Minuten kippt.
Was Swap trotzdem leistet, ist ein Puffer für den Fehlerfall. Ohne ihn endet eine Überbuchung abrupt damit, dass der Kernel einen Prozess beendet. Mit ihm bekommen Sie zuerst einen langsamen Server und damit ein Zeitfenster zum Eingreifen. Die Regel lautet: Swap ja, aber pm.max_children ausschließlich gegen den echten Arbeitsspeicher rechnen, niemals gegen die Summe aus Arbeitsspeicher und Swap.
Ob Sie bereits auslagern, sehen Sie an zwei Stellen. free -m zeigt die Belegung, sagt aber nichts über die Aktivität, denn eine einmal ausgelagerte und nie wieder benötigte Seite ist harmlos. Aussagekräftig sind die Spalten si und so, also Ein- und Auslagerung pro Sekunde:
free -m
vmstat 1 5
Dauerhaft Werte über null heißen, der Server arbeitet gegen die Platte. Falls Ihr Kernel die Druckanzeige mitliefert, ist sie noch direkter, denn sie sagt nicht, wie viel ausgelagert wurde, sondern wie lange Prozesse deswegen warten mussten:
cat /proc/pressure/memory
Existiert die Datei nicht, ist die Funktion im Kernel abgeschaltet und Sie bleiben bei vmstat. Wie Sie Swap anlegen, dauerhaft eintragen und vm.swappiness passend setzen, steht im Beitrag Swap einrichten und Out-of-Memory beheben.
Zu hoch gesetzt: der OOM-Killer statt einer Warteschlange
Angenommen, Sie tragen 200 ein, damit die Warnung endlich verschwindet. Im Leerlauf passiert nichts, die Seite lädt, alles wirkt gelöst. Beim nächsten Ansturm spaltet FPM tatsächlich bis zu 200 Prozesse ab, jeder wächst mit den ersten Anfragen auf seine echte Größe, der freie Speicher fällt, der Kernel wirft zuerst den Datei-Zwischenspeicher weg (wodurch die Datenbank langsamer wird und ihre Abfragen länger dauern), dann lagert er aus, und dann greift der OOM-Killer.
Der wählt sein Opfer nach Speicherverbrauch, und der größte Einzelprozess auf einem Webserver ist nicht ein PHP-Arbeitsprozess mit 80 MB, sondern die Datenbank mit ihrem Pufferpool:
dmesg -T | grep -iE "out of memory|oom-kill"
journalctl -k --since "24 hours ago" | grep -i "out of memory"
Die beiden Zeilen, auf die es ankommt, sehen so aus:
php-fpm8.2 invoked oom-killer: gfp_mask=0x1100cca(GFP_HIGHUSER_MOVABLE), order=0, oom_score_adj=0
Out of memory: Killed process 1234 (mariadbd) total-vm:2891234kB, anon-rss:1783456kB,
file-rss:0kB, shmem-rss:0kB, UID:107 pgtables:4096kB oom_score_adj:0
Auslöser und Opfer sind hier zwei verschiedene Prozesse, und genau das macht die Fehlersuche unangenehm: Im Postfach liegt eine Meldung über eine abgestürzte Datenbank, und niemand denkt an die PHP-Konfiguration von vorgestern. Je nach Datenbank steht in Klammern mariadbd oder mysqld.
Trifft es doch einen Arbeitsprozess, meldet FPM das selbst, und bei gesetztem Speicherlimit steht es zusätzlich im Journal:
WARNING: [pool www] child 1234 exited on signal 9 (SIGKILL) after 3612.472183 seconds from start
php8.2-fpm.service: A process of this unit has been killed by the OOM killer.
Der Unterschied zwischen beiden Fehlerbildern ist der eigentliche Punkt dieses Beitrags. Ein zu niedriges pm.max_children erzeugt eine Warteschlange: messbar, im Protokoll dokumentiert, auf einen Wert zurückführbar, und der Server bleibt ansprechbar. Ein zu hohes erzeugt einen beendeten Prozess an einer Stelle, die Sie sich nicht ausgesucht haben, bei einem Dienst, der unter Umständen nicht von selbst zurückkommt. Wartezeit ist ein Betriebszustand, ein OOM-Ereignis ist ein Zwischenfall.
Häufige Fehler und Lösungen
WARNING: [pool www] server reached pm.max_children setting (5), consider raising it: Zu einem Zeitpunkt waren alle Arbeitsprozesse belegt. Die Zeile erscheint einmal je Sättigungsphase, eine einzelne Zeile kann also eine Stunde Vollauslastung bedeuten. Erhöhen Sie den Wert erst, nachdem Sie PSS gemessen und das Budget bestimmt haben.
WARNING: [pool www] server reached max_children setting (5), consider raising it: Dieselbe Lage unter pm = ondemand, im Wortlaut ohne das Präfix pm..
ALERT: [pool www] pm.min_spare_servers and pm.max_spare_servers cannot be greater than pm.max_children, gefolgt von ERROR: failed to post process the configuration und ERROR: FPM initialization failed: Der häufigste Fall beim Senken von pm.max_children. Wer von 50 auf 8 geht und pm.max_spare_servers = 20 stehen lässt, hat anschließend einen Dienst, der nicht mehr startet. Alle vier Werte gehören zusammen geändert.
ALERT: [pool www] pm.start_servers must not be less than pm.min_spare_servers and not greater than pm.max_spare_servers: pm.start_servers liegt außerhalb der Spanne. Korrigieren Sie den Wert oder entfernen Sie die Zeile, dann rechnet FPM selbst.
PHP Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 20480 bytes): Das ist memory_limit und hat mit pm.max_children nichts zu tun. Ein einzelnes Skript hat mehr angefordert, als PHP je Anfrage zulässt. Ein höheres pm.max_children ändert daran nichts, und ein niedrigeres memory_limit verkleinert umgekehrt nicht den Speicherbedarf eines Arbeitsprozesses, es bricht Skripte nur früher ab. Für die Planung gilt trotzdem: memory_limit ist die Obergrenze dessen, was ein Prozess anfordern darf.
connect() to unix:/run/php/php8.2-fpm.sock failed (11: Resource temporarily unavailable) while connecting to upstream im Fehlerprotokoll von nginx: Alle Prozesse belegt und zusätzlich die Annahmewarteschlange voll. Ein höheres listen.backlog verschiebt das nur, die Ursache liegt bei der Anzahl der Prozesse oder bei der Laufzeit der Skripte.
WARNING: [pool www] child 1234 exited on signal 9 (SIGKILL) after 3612.472183 seconds from start: Der Prozess wurde von außen hart beendet, in aller Regel durch den OOM-Killer. Hier ist der Wert zu hoch, nicht zu niedrig. Gegenprobe im Kernel-Protokoll.
"Ich habe den Wert erhöht, es ändert sich nichts." Fast immer wurde die falsche Datei bearbeitet: eine zweite PHP-Version unter /etc/php/, ein zweiter Pool oder eine Kopie in pool.d/, die gar nicht eingelesen wird. Maßgeblich ist, welchen Socket nginx anspricht und welcher Pool darauf lauscht:
grep -Rn "fastcgi_pass" /etc/nginx/
grep -n "^listen *=" /etc/php/*/fpm/pool.d/*.conf
php-fpm8.2 -tt 2>&1 | grep "pm.max_children"
"Nach dem Reload ist die Seite weg." Ein Reload startet FPM mit der neuen Konfiguration neu, und ist diese ungültig, beendet sich der Hauptprozess. Spielen Sie die Kopie aus /root/backups zurück und gewöhnen Sie sich php-fpm8.2 -t vor jedem Reload an.
Woran Sie erkennen, dass der Wert stimmt
"Die Warnung ist weg" ist kein Nachweis. Mit dem Wert 500 ist sie auch weg, bis zum ersten OOM-Ereignis. Belastbar sind fünf Prüfungen.
Erstens die Statusseite von FPM. Tragen Sie pm.status_path = /status in die Pool-Datei ein, prüfen und laden Sie, und fragen Sie den Socket direkt ab, vollständig an nginx vorbei. Damit ist die Seite nicht aus dem Netz erreichbar:
apt-get install -y libfcgi-bin
SCRIPT_NAME=/status SCRIPT_FILENAME=/status REQUEST_METHOD=GET \
cgi-fcgi -bind -connect /run/php/php8.2-fpm.sock
Vier Zeilen der Ausgabe tragen die Antwort:
| Feld | Bedeutung | Zielwert |
| max children reached | wie oft die Obergrenze seit dem Start erreicht wurde | 0 |
| max listen queue | längste beobachtete Warteschlange am Socket | 0 |
| max active processes | höchste gleichzeitig aktive Prozesszahl | deutlich unter pm.max_children |
| slow requests | Anfragen über request_slowlog_timeout | möglichst 0 |
Aussagekräftig wird das erst nach einer vollen Woche mit allen Stoßzeiten. Steht max active processes danach bei 12, während pm.max_children auf 43 steht, haben Sie Luft und können die Reserve anderswo verwenden.
Zweitens der Speicher unter Last, nicht nachts, sondern in der Spitze. available muss deutlich über null bleiben, die Spalten si und so sollen bei null stehen:
free -m
vmstat 1 5
Drittens: kein OOM-Ereignis. Diese Prüfung ist die wichtigste, weil sie das schlimmste Fehlerbild ausschließt. Eine leere Ausgabe ist das gewünschte Ergebnis:
journalctl -k --since "7 days ago" | grep -i "out of memory"
Viertens die Summe über alle Pools. Auf einem Server mit mehreren Auftritten zählt nicht der einzelne Wert, sondern die Summe, multipliziert mit dem PSS je Prozess. Sie muss ins Budget passen, auch wenn alle Pools gleichzeitig unter Last stehen:
php-fpm8.2 -tt 2>&1 | grep "pm.max_children"
Fünftens: Es übersteht einen Neustart. Der am häufigsten übersprungene Punkt. Ein Wert in einer Datei, die gar nicht eingelesen wird, fällt erst beim nächsten Neustart auf, und der passiert selten dann, wenn Sie gerade hinsehen:
systemctl is-enabled php8.2-fpm
systemctl restart php8.2-fpm
php-fpm8.2 -tt 2>&1 | grep "pm.max_children"
Starten Sie den Server danach einmal kontrolliert neu, solange Sie noch zusehen. Bei den KVM-Rootservern und Dedicated Servern von KernelHost lösen Sie diesen Neustart im Kundenbereich aus und verfolgen das Hochfahren über die VNC-Konsole, auch wenn der Webdienst noch nicht antwortet. Die Server stehen im Rechenzentrum maincubes in Frankfurt am Main.
Kurzcheckliste
- Kopie der Pool-Datei nach
/root/backups, Zugang über die VNC-Konsole einmal ausprobiert. - PSS je Arbeitsprozess unter echter Last messen, nicht nach einem Neustart, und nicht mit RSS rechnen.
- Budget bestimmen:
availableplus laufende PSS-Summe, abzüglich einer bewusst gesetzten Reserve. - Obergrenze ausrechnen, Bedarf aus Anfragen pro Sekunde mal p95-Laufzeit gegenrechnen, den kleineren Wert nehmen, abrunden.
- Betriebsart wählen und die Spare-Werte gemeinsam mit
pm.max_childrenanpassen. php-fpm8.2 -t, dannsystemctl reload, dannphp-fpm8.2 -ttals Nachweis, dass der Wert geladen ist.- Eine Woche später
max children reached,max listen queueund das Kernel-Protokoll prüfen.
Häufige Fragen
Wie berechne ich pm.max_children richtig?
Warum ist ein zu hoher Wert gefährlicher als ein zu niedriger?
Warum soll ich mit PSS rechnen und nicht mit RSS?
Wann nehme ich dynamic, wann ondemand und wann static?
Darf ich den Swap in die Rechnung einbeziehen?
Ich habe pm.max_children erhöht, es ändert sich nichts. Woran liegt das?
Nach dem Senken von pm.max_children startet PHP-FPM nicht mehr. Was ist passiert?
Hat memory_limit etwas mit pm.max_children zu tun?
2026 KernelHost GmbH. Alle Rechte vorbehalten. Diese Anleitung ist urheberrechtlich geschützt. Eine Veröffentlichung auf anderen Webseiten, auch auszugsweise oder in bearbeiteter Form, ist ohne unsere schriftliche Zustimmung nicht gestattet. Zitate mit Quellenangabe und Link sind ausdrücklich willkommen.

