pm.max_children voor PHP-FPM berekenen in plaats van gokken
De melding server reached pm.max_children betekent niet dat u de waarde moet verdubbelen. Meten, rekenen, de procesmodus kiezen en daarna aantonen dat de waarde echt past.
In het logboek van PHP-FPM staat op enig moment deze regel, en het advies staat er meteen bij:
WARNING: [pool www] server reached pm.max_children setting (5), consider raising it
Dat advies is niet verkeerd, alleen onvolledig. pm.max_children is de enige instelknop in PHP-FPM waarbij een te hoge waarde gevaarlijker is dan een te lage. Te laag kost wachttijd en in het uiterste geval een 502. Te hoog kost het werkgeheugen van de hele server, en dan kiest de kernel zelf uit welk proces hij beëindigt. In de praktijk is dat niet PHP, maar de database.
Deze handleiding laat de weg zien van gissen naar rekenen: de geheugenbehoefte van één werkproces meten, het budget bepalen, de procesmodus kiezen en daarna aantonen dat de waarde klopt.
Alle gegevens gelden voor Debian 13 (trixie), Debian 12 (bookworm), Ubuntu 24.04 LTS en Ubuntu 22.04 LTS. De commando's zijn geschreven voor gebruik als root, als gewone gebruiker zet u er sudo voor. Vervang overal het PHP-versienummer door dat van uw eigen systeem.
| Systeem | PHP | Service | Poolbestand | FPM-logbestand |
| 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 |
Welke versies geïnstalleerd zijn, verraadt een blik in de map. Op servers die een distributie-upgrade achter de rug hebben, zijn het er vaak twee:
ls /etc/php/
ls /etc/php/*/fpm/pool.d/
Wat pm.max_children werkelijk begrenst
De waarde begrenst geen bezoekers en geen verbindingen, maar het aantal PHP-aanvragen dat op hetzelfde moment wordt uitgevoerd. Een aanvraag van 80 milliseconden bezet een werkproces gedurende 80 milliseconden en geeft het daarna weer vrij. Daaruit volgt bijna al het andere:
- Veel verkeer heeft weinig processen nodig, zolang de scripts snel zijn. 40 aanvragen per seconde van elk 80 milliseconden leveren ruim drie gelijktijdige aanvragen op, geen 40.
- Eén traag onderdeel gooit de hele rekensom om. Een aanroep naar een externe programmeerinterface zonder eigen termijn houdt een proces vijf seconden bezet, terwijl dat proces niets doet. Vijf van zulke aanroepen per seconde bezetten blijvend 25 processen.
- Wachtende verbindingen bezetten geen proces. Een keep-alive-verbinding naar nginx kost een bestandsdescriptor, maar geen werkproces.
Zijn alle processen bezet, dan wachten nieuwe aanvragen in de wachtrij van de socket. De grootte daarvan staat in het poolbestand onder listen.backlog, standaard 511, en de kernel begrenst die daarnaast via net.core.somaxconn, dat op alle vier de systemen op 4096 staat:
sysctl net.core.somaxconn
Zolang de wachtrij toereikend is, merkt de bezoeker alleen langere laadtijden. Is ook die vol, dan wijst de kernel de verbinding af, nginx noteert 11: Resource temporarily unavailable en bij de bezoeker komt een 502 aan. Ter afbakening van de overige oorzaken: nginx 502 Bad Gateway oplossen.
Een detail dat veel verwarring veroorzaakt: de waarschuwing verschijnt één keer per verzadigingsfase, niet per getroffen aanvraag. FPM zet intern een markering en wist die pas als er weer een proces vrij is. Eén enkele regel kan voor een compleet piekuur staan. Wie regels telt, onderschat het probleem. De eerlijke meetwaarde is de teller max children reached op de statuspagina, die verderop wordt opgevraagd.
De weg terug, voordat u iets wijzigt
Twee dingen kunnen hier misgaan, en beide raken de draaiende server.
Ten eerste: FPM start niet meer. Een systemctl reload stuurt het hoofdproces het signaal USR2, waarna dat zichzelf met de nieuwe configuratie opnieuw start. Is die configuratie ongeldig, dan breekt de initialisatie af en beëindigt het hoofdproces zichzelf. Een draaiende instantie wordt zo een dode. Daarom zonder uitzondering eerst controleren, dan pas laden:
php-fpm8.2 -t
Bij succes eindigt de uitvoer met test is successful.
Ten tweede: de waarde is te hoog. Dan is de server niet meteen stuk, maar pas bij de volgende belastingpiek, en dan zo grondig dat ook een SSH-login blijft hangen of helemaal niet meer tot stand komt. Daarvoor hebt u een tweede weg naar de server nodig.
Bij de KVM-rootservers en dedicated servers van KernelHost is dat de VNC-console in het klantenpaneel. Die zit vast aan de virtualisatielaag dan wel aan de aansluiting zelf en blijft ook bereikbaar wanneer de SSH-service door geheugengebrek niet meer antwoordt. Log daar vooraf een keer op in. Een vluchtweg die u pas in het noodgeval voor het eerst uitprobeert, is geen vluchtweg.
Maak daarnaast een kopie van het poolbestand aan, voorzien van een tijdstempel, zodat een tweede poging de eerste kopie niet overschrijft:
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/
De weg terug is dan drie regels 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
Een vangrail voor het geval u zich verrekent
Een geheugenlimiet op de systemd-unit zorgt ervoor dat een verkeerde inschatting een PHP-werkproces treft en niet de database: de kernel beëindigt dan binnen de control group van FPM, in plaats van systeembreed het grootste proces op te zoeken.
mkdir -p /etc/systemd/system/php8.2-fpm.service.d
cat > /etc/systemd/system/php8.2-fpm.service.d/geheugen.conf <<'EOF'
[Service]
MemoryHigh=1500M
MemoryMax=2G
EOF
systemctl daemon-reload
systemctl restart php8.2-fpm
Controleren of het is aangekomen:
systemctl show php8.2-fpm -p MemoryHigh -p MemoryMax
MemoryHigh remt af en ruimt op, MemoryMax is de harde grens. Alle vier de systemen gebruiken de verenigde cgroup-hiërarchie, de waarden werken dus meteen door. Dit is een vangrail, geen vervanging voor de rekensom: wordt de grens bereikt, dan ziet de betrokken bezoeker nog steeds een fout, alleen niet de hele server. Voor het netjes wegzetten van zulke aanvullende bestanden: systemd-service aanmaken.
En de derde regel, waarvoor geen commando nodig is: één wijziging tegelijk. Wie de procesmodus, pm.max_children en de spare-waarden tegelijk aanpast, weet achteraf niet wat het effect heeft gehad.
Inventarisatie: welke pool geldt
Elke pool heeft zijn eigen pm.max_children, en voor het geheugen telt de som over alle pools:
grep -n "^pm" /etc/php/*/fpm/pool.d/*.conf
In de standaardtoestand staat op alle vier de systemen hetzelfde:
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
Dat zijn geen aanbevelingen, maar plaatshouders waarmee PHP ook op een heel kleine testmachine opstart. Daar komt nog een globale bovengrens over alle pools heen bij:
grep -n "^process.max" /etc/php/*/fpm/php-fpm.conf
Blijft de uitvoer leeg, dan is de regel uitgecommentarieerd en geldt de standaardwaarde 0, dus geen globale begrenzing. Op servers met veel pools voorkomt precies die regel dat de som het geheugen opblaast zodra meerdere pools tegelijk onder belasting staan.
Bepalend is uiteindelijk niet het bestand, maar wat FPM ervan maakt. De optie -tt geeft de configuratie volledig uitgewerkt weer, inclusief alle standaardwaarden die in geen enkel bestand staan:
php-fpm8.2 -tt 2>&1 | grep -E "^\[|pm |pm\.|listen ="
Deze uitvoer is later ook het bewijs dat een wijziging is aangekomen.
De geheugenbehoefte van een werkproces meten
Hier worden de meeste handleidingen onnauwkeurig. Drie getallen worden regelmatig door elkaar gehaald:
memory_limit, standaard 128M in FPM-bedrijf: een bovengrens per aanvraag, geen verbruik. Een script dat 12 MB nodig heeft, heeft ook met 512M maar 12 MB nodig.- RSS: alles wat op dat moment in het werkgeheugen staat, inclusief de gedeelde opcache en de gedeelde bibliotheken. Wie RSS over 20 processen optelt, telt dezelfde opcache twintig keer mee.
- PSS: gedeelde geheugenpagina's worden gedeeld door het aantal gebruikers ervan. Het enige van de drie getallen dat zich zinvol laat optellen.
Voor de rekensom hebt u PSS nodig. De kernel levert die kant-en-klaar samengevat in /proc/<pid>/smaps_rollup, leesbaar 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 "geen werkprocessen gevonden"; exit }
printf "Processen: %d Totaal: %.0f MiB Gemiddeld: %.1f MiB\n", n, s/1024, s/1024/n
}'
Ter vergelijking dezelfde pool via RSS:
ps -eo pid,rss,args --sort=-rss | grep '[p]hp-fpm' | head
De RSS-som ligt afhankelijk van de opcache-grootte duidelijk hoger. Wie daarmee rekent, krijgt een te kleine pm.max_children en koopt zich wachttijd die niet nodig was.
Twee voorwaarden bepalen of de meting iets waard is. Meet onder echte belasting, niet vlak na een herstart: een vers afgesplitst proces is goedkoop, omdat het de geheugenpagina's van het hoofdproces in eerste instantie alleen meegebruikt, en wordt pas met de eerste aanvragen duur. En neem niet alleen het gemiddelde: ligt het gemiddelde op 60 MiB en het grootste proces op 190 MiB, reken dan niet met 60.
De tweede bron: het toegangslogboek van FPM
FPM noteert op verzoek per aanvraag het piekverbruik en de looptijd. Beide regels staan uitgecommentarieerd in het poolbestand:
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%%"
De schrijfwijze %{mili}d is zo correct, die komt ongewijzigd uit het meegeleverde bestand en levert de looptijd in milliseconden, %{kilo}M het piekverbruik in kilobyte. Daarna controleren, laden en nagaan of het bestand daadwerkelijk ontstaat:
php-fpm8.2 -t
systemctl reload php8.2-fpm
ls -l /var/log/php8.2-fpm.access.log
Laat het logboek een volle dag meelopen, zodat de piekuren erin zitten. Daarna werkt u het piekverbruik uit als kwantielen:
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 "Aanvragen: %d Mediaan: %d kB p95: %d kB Maximum: %d kB\n", NR, v[int((NR+1)/2)], v[p], v[NR]
}'
Dezelfde uitwerking voor de looptijd, die u zo meteen voor de tweede rekensom nodig hebt, haalt de kolom ervoor op:
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 "Mediaan: %.0f ms p95: %.0f ms Maximum: %.0f ms\n", v[int((NR+1)/2)], v[p], v[NR]
}'
Twee beperkingen. Ten eerste hangen de veldposities aan de hierboven getoonde formaatregel, omdat die eindigt met looptijd, geheugen en CPU-aandeel. Wie access.format wijzigt, moet ze aanpassen. Ten tweede is %{kilo}M de geheugenboekhouding van PHP zelf en daarmee een ondergrens: programmacode, extensies en het geheugen dat de C-bibliotheek na een aanvraag niet meteen teruggeeft, ontbreken daarin. Voor de formule geldt daarom PSS. Het toegangslogboek dient om de uitschieters te vinden, de scripts die een proces blijvend groot maken.
Schakel het logboek daarna weer uit of richt er een rotatie voor in. De meegeleverde regel dekt alleen het foutlogboek, en een volle schijf levert foutbeelden op die niets meer met PHP-FPM te maken hebben: volle schijf op de Linux-server opruimen.
De rekensom
Er zijn twee getallen: een bovengrens uit het werkgeheugen en een behoefte uit de belasting. De juiste waarde is de kleinste van de twee.
Bovengrens uit het werkgeheugen
pm.max_children = budget voor PHP / PSS per werkproces
Het budget is niet het volledige werkgeheugen:
free -m
De kolom available houdt al rekening met de cache die vrijgemaakt kan worden, maar bevat niet het geheugen dat uw werkprocessen op dat moment vasthouden. Het budget is dus available plus de gemeten PSS-som, minus een reserve. Voor die reserve voldoet in de praktijk ongeveer 20 procent van het totale geheugen, met een ondergrens van 512 MB. Ze vangt op wat in geen enkele meting opduikt: een groeiende bufferpool van de database, een back-up, een pakketupgrade, een importklus op een ongelukkig moment.
| Totaal RAM | Blijvend bezet | Reserve | Budget voor PHP | PSS per proces | 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 |
Rond altijd naar beneden af. Eén proces meer levert niets op, één proces te veel kan alles kosten.
Behoefte uit de belasting
benodigde processen = aanvragen per seconde × gemiddelde looptijd in seconden
Het aantal aanvragen per seconde levert de statuspagina van FPM: accepted conn gedeeld door start since geeft het gemiddelde sinds de laatste start. De looptijd komt uit de uitwerking hierboven, en wel de p95-waarde en niet de mediaan, anders plant u voor de rustige middag.
Voorbeeld: 40 aanvragen per seconde in de piek, p95-looptijd 300 milliseconden, dat maakt 12 gelijktijdige aanvragen, met een opslag voor korte uitschieters ongeveer 20 tot 25. Ligt de bovengrens op 43, vul dan de behoeftewaarde in en laat het overige geheugen daar waar het meer oplevert, namelijk in de cache van de database.
Ligt de behoefte boven de bovengrens, vul die dan toch niet in. Dan is ofwel het werkgeheugen te klein, ofwel zijn de scripts te traag, ofwel lopen er aanvragen door PHP heen die net zo goed als statisch bestand of uit een cache konden komen. Alle drie de oorzaken zijn te verhelpen, een te hoge waarde niet: die verschuift alleen het moment van de uitval.
dynamic, ondemand of static
De procesmodus bepaalt wanneer processen ontstaan en verdwijnen. De bovengrens pm.max_children geldt in alle drie de gevallen.
| Procesmodus | Processen bij de start | Gedrag | Geheugen | Past bij |
| dynamic | pm.start_servers | houdt tussen min_spare en max_spare processen inactief gereed, splitst bij behoefte af tot max_children | schommelt mee met de belasting | het normale geval: één tot enkele pools, wisselende belasting |
| ondemand | geen | start pas een proces bij een aanvraag en beëindigt het na pm.process_idle_timeout weer | bij inactiviteit het laagst | veel pools op één server, websites met weinig verkeer |
| static | pm.max_children | precies dat aantal, blijvend, zonder afsplitsen en beëindigen tijdens bedrijf | constant op het maximum | één pool op daarvoor bestemde hardware, gelijkmatige belasting |
dynamic is de standaardinstelling en voor het normale geval de juiste keuze. Deze modus kost wat rekentijd voor het afsplitsen en vereist dat de vier waarden bij elkaar passen.
ondemand bespaart bij inactiviteit merkbaar geheugen wanneer er een stuk of twaalf pools voor zelden bezochte websites op één machine staan. De prijs is de eerste aanvraag na een rustperiode, die op het afsplitsen moet wachten. pm.process_idle_timeout stuurt hoelang een inactief proces blijft bestaan (standaard tien seconden) en werkt uitsluitend in deze procesmodus. Let daarnaast op het volgende: de verzadigingswaarschuwing noemt hier max_children zonder het voorvoegsel pm.. Wie op de vertrouwde formulering zoekt, vindt niets.
static is eerlijk: wat u invult, wordt meteen en blijvend bezet, maar daarmee is er onder belasting ook geen verrassing meer. Dat is zinvol wanneer PHP de grootverbruiker van de machine is. Deelt PHP het werkgeheugen met een database, dan ontneemt static die database de mogelijkheid om tijdelijk meer cache aan te houden.
Ongeacht de procesmodus loont een blik op pm.max_requests, standaard 0 en daarmee onbegrensd. Een waarde als 500 vervangt elk werkproces na 500 aanvragen. Tegen langzaam groeiend geheugengebruik door een onzuivere extensie is dat effectief en goedkoop, want de opcache blijft gedeeld en wordt niet opnieuw opgebouwd. Zet de waarde echter niet op 20, anders is FPM voortdurend bezig met afsplitsen.
start_servers en de spare-waarden
Deze drie waarden werken alleen bij pm = dynamic en bepalen hoe snel FPM op een belastingpiek reageert:
pm.min_spare_servers: zoveel inactieve processen houdt FPM minimaal gereed, de buffer voor uitschieters. Te laag betekent dat elke piek eerst op het afsplitsen wacht.pm.max_spare_servers: zoveel inactieve processen duldt FPM hoogstens. Zonder die grens zou FPM na een piek alle processen behouden en daarmee ook hun geheugen.pm.start_servers: zoveel processen bestaan er direct na de start.
FPM controleert bij de start vier voorwaarden en weigert dienst zodra er één wordt geschonden: beide spare-waarden moeten groter dan nul zijn, geen van beide mag groter zijn dan pm.max_children, max_spare mag niet kleiner zijn dan min_spare, en start_servers moet daartussen liggen. Ontbreekt pm.start_servers helemaal, dan rekent FPM zelf en noteert:
NOTICE: [pool www] pm.start_servers is not set. It's been set to 3.
De formule daarvoor is min_spare + (max_spare - min_spare) / 2, dus het midden tussen beide spare-waarden. Als vertrekpunt werkt in de praktijk:
pm.max_children = 40
pm.start_servers = 10
pm.min_spare_servers = 6
pm.max_spare_servers = 16
Snel over het hoofd gezien: ook inactieve processen bezetten geheugen. Het budget moet pm.max_children kunnen dragen, terwijl het dagelijkse verbruik ongeveer overeenkomt met max_spare plus de actieve processen. Een hoge max_spare houdt het geheugen ook om drie uur 's nachts bezet.
Het verband met de swap
Het is verleidelijk om het wisselgeheugen in de rekensom mee te nemen. Doe dat niet. Een werkproces waarvan de gegevens op de schijf staan, antwoordt ordes van grootte trager en blijft daardoor langer bezet. Het aantal gelijktijdige aanvragen stijgt, FPM splitst nog meer processen af, en die verdringen op hun beurt meer geheugen. Die terugkoppeling is de reden waarom een overboekte server niet geleidelijk slechter wordt, maar binnen enkele minuten omvalt.
Wat swap desondanks oplevert, is een buffer voor het foutgeval. Zonder swap eindigt een overboeking abrupt doordat de kernel een proces beëindigt. Met swap krijgt u eerst een trage server en daarmee een tijdvenster om in te grijpen. De regel luidt: swap ja, maar pm.max_children uitsluitend afzetten tegen het echte werkgeheugen, nooit tegen de som van werkgeheugen en swap.
Of er al wordt weggeschreven, ziet u op twee plaatsen. free -m toont de bezetting, maar zegt niets over de activiteit, want een pagina die één keer is weggeschreven en nooit meer nodig is, is ongevaarlijk. Veelzeggend zijn de kolommen si en so, oftewel het aantal pagina's dat per seconde uit en naar de swap gaat:
free -m
vmstat 1 5
Blijvende waarden boven nul betekenen dat de server voortdurend geheugen van en naar de schijf verplaatst. Levert uw kernel de drukindicator mee, dan is die nog directer, want die zegt niet hoeveel er is weggeschreven, maar hoelang processen daardoor hebben moeten wachten:
cat /proc/pressure/memory
Bestaat het bestand niet, dan is de functie in de kernel uitgeschakeld en blijft u bij vmstat. Hoe u swap aanmaakt, blijvend vastlegt en vm.swappiness passend instelt, leest u in het artikel Swap inrichten en crashes door geheugengebrek voorkomen.
Te hoog ingesteld: de OOM-killer in plaats van een wachtrij
Stel dat u 200 invult, zodat de waarschuwing eindelijk verdwijnt. Bij inactiviteit gebeurt er niets, de pagina laadt, alles lijkt opgelost. Bij de volgende toeloop splitst FPM daadwerkelijk tot 200 processen af, elk groeit met de eerste aanvragen naar zijn werkelijke omvang, het vrije geheugen daalt, de kernel gooit eerst de bestandscache weg (waardoor de database trager wordt en haar query's langer duren), daarna schrijft hij weg naar de swap, en dan grijpt de OOM-killer in.
Die kiest zijn slachtoffer op geheugengebruik, en het grootste afzonderlijke proces op een webserver is niet een PHP-werkproces met 80 MB, maar de database met haar bufferpool:
dmesg -T | grep -iE "out of memory|oom-kill"
journalctl -k --since "24 hours ago" | grep -i "out of memory"
De twee regels waar het om draait, zien er zo uit:
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
Aanleiding en slachtoffer zijn hier twee verschillende processen, en juist dat maakt het zoeken naar de fout vervelend: in de mailbox ligt een melding over een gecrashte database, en niemand denkt aan de PHP-configuratie van eergisteren. Afhankelijk van de database staat er tussen haakjes mariadbd of mysqld.
Treft het toch een werkproces, dan meldt FPM dat zelf, en met een ingestelde geheugenlimiet staat het bovendien in het 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.
Het verschil tussen beide foutbeelden is waar dit artikel eigenlijk om draait. Een te lage pm.max_children levert een wachtrij op: meetbaar, gedocumenteerd in het logboek, terug te voeren op één waarde, en de server blijft bereikbaar. Een te hoge levert een beëindigd proces op, op een plek die u niet zelf hebt uitgekozen, bij een dienst die mogelijk niet uit zichzelf terugkomt. Wachttijd is een bedrijfstoestand, een OOM-gebeurtenis is een incident.
Veelvoorkomende fouten en oplossingen
WARNING: [pool www] server reached pm.max_children setting (5), consider raising it: op enig moment waren alle werkprocessen bezet. De regel verschijnt één keer per verzadigingsfase, één enkele regel kan dus een uur volle bezetting betekenen. Verhoog de waarde pas nadat u PSS hebt gemeten en het budget hebt bepaald.
WARNING: [pool www] server reached max_children setting (5), consider raising it: dezelfde situatie onder pm = ondemand, in de formulering zonder het voorvoegsel pm..
ALERT: [pool www] pm.min_spare_servers and pm.max_spare_servers cannot be greater than pm.max_children, gevolgd door ERROR: failed to post process the configuration en ERROR: FPM initialization failed: het meest voorkomende geval bij het verlagen van pm.max_children. Wie van 50 naar 8 gaat en pm.max_spare_servers = 20 laat staan, heeft daarna een dienst die niet meer opstart. Alle vier de waarden moeten samen worden aangepast.
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 ligt buiten het toegestane bereik. Corrigeer de waarde of verwijder de regel, dan rekent FPM zelf.
PHP Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 20480 bytes): dat is memory_limit en heeft niets met pm.max_children te maken. Eén enkel script heeft meer aangevraagd dan PHP per aanvraag toestaat. Een hogere pm.max_children verandert daar niets aan, en omgekeerd verkleint een lagere memory_limit de geheugenbehoefte van een werkproces niet, die breekt scripts alleen eerder af. Voor de planning geldt niettemin: memory_limit is de bovengrens van wat een proces mag aanvragen.
connect() to unix:/run/php/php8.2-fpm.sock failed (11: Resource temporarily unavailable) while connecting to upstream in het foutlogboek van nginx: alle processen bezet en daarbovenop de wachtrij vol. Een hogere listen.backlog stelt dat alleen uit, de oorzaak ligt bij het aantal processen of bij de looptijd van de scripts.
WARNING: [pool www] child 1234 exited on signal 9 (SIGKILL) after 3612.472183 seconds from start: het proces is van buitenaf hard beëindigd, vrijwel altijd door de OOM-killer. Hier is de waarde te hoog, niet te laag. Controleer dat in het kernellogboek.
"Ik heb de waarde verhoogd, er verandert niets." Bijna altijd is het verkeerde bestand bewerkt: een tweede PHP-versie onder /etc/php/, een tweede pool of een kopie in pool.d/ die helemaal niet wordt ingelezen. Bepalend is welke socket nginx aanspreekt en welke pool daarop luistert:
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"
"Na de reload is de site weg." Een reload start FPM opnieuw met de nieuwe configuratie, en is die ongeldig, dan beëindigt het hoofdproces zichzelf. Zet de kopie uit /root/backups terug en wen uzelf php-fpm8.2 -t aan vóór elke reload.
Waaraan u ziet dat de waarde klopt
"De waarschuwing is weg" is geen bewijs. Met de waarde 500 is ze ook weg, tot aan de eerste OOM-gebeurtenis. Betrouwbaar zijn vijf controles.
Ten eerste de statuspagina van FPM. Zet pm.status_path = /status in het poolbestand, controleer en laad, en bevraag de socket rechtstreeks, volledig langs nginx heen. Daarmee is de pagina niet vanaf het netwerk bereikbaar:
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 regels van de uitvoer bevatten het antwoord:
| Veld | Betekenis | Streefwaarde |
| max children reached | hoe vaak de bovengrens sinds de start is bereikt | 0 |
| max listen queue | langste waargenomen wachtrij op de socket | 0 |
| max active processes | hoogste aantal gelijktijdig actieve processen | duidelijk onder pm.max_children |
| slow requests | aanvragen boven request_slowlog_timeout | bij voorkeur 0 |
Veelzeggend wordt dat pas na een volle week met alle piekuren. Staat max active processes daarna op 12 terwijl pm.max_children op 43 staat, dan hebt u lucht en kunt u de reserve elders inzetten.
Ten tweede het geheugen onder belasting, niet 's nachts, maar in de piek. available moet duidelijk boven nul blijven, de kolommen si en so horen op nul te staan:
free -m
vmstat 1 5
Ten derde: geen OOM-gebeurtenis. Deze controle is de belangrijkste, omdat ze het ergste foutbeeld uitsluit. Een lege uitvoer is het gewenste resultaat:
journalctl -k --since "7 days ago" | grep -i "out of memory"
Ten vierde de som over alle pools. Op een server met meerdere websites telt niet de afzonderlijke waarde, maar de som, vermenigvuldigd met de PSS per proces. Die moet in het budget passen, ook wanneer alle pools tegelijk onder belasting staan:
php-fpm8.2 -tt 2>&1 | grep "pm.max_children"
Ten vijfde: het overleeft een herstart. Het punt dat het vaakst wordt overgeslagen. Een waarde in een bestand dat helemaal niet wordt ingelezen, valt pas bij de volgende herstart op, en die vindt zelden plaats op het moment dat u toevallig meekijkt:
systemctl is-enabled php8.2-fpm
systemctl restart php8.2-fpm
php-fpm8.2 -tt 2>&1 | grep "pm.max_children"
Start de server daarna één keer gecontroleerd opnieuw op, zolang u nog meekijkt. Bij de KVM-rootservers en dedicated servers van KernelHost start u die herstart in het klantenpaneel en volgt u het opstarten via de VNC-console, ook wanneer de webdienst nog niet antwoordt. De servers staan in het datacenter maincubes in Frankfurt am Main.
Korte checklist
- Kopie van het poolbestand naar
/root/backups, toegang via de VNC-console één keer uitgeprobeerd. - PSS per werkproces meten onder echte belasting, niet na een herstart, en niet rekenen met RSS.
- Budget bepalen:
availableplus de actuele PSS-som, minus een bewust gekozen reserve. - Bovengrens uitrekenen, de behoefte uit aanvragen per seconde maal p95-looptijd ertegenover zetten, de kleinste waarde nemen, naar beneden afronden.
- Procesmodus kiezen en de spare-waarden samen met
pm.max_childrenaanpassen. php-fpm8.2 -t, dansystemctl reload, danphp-fpm8.2 -ttals bewijs dat de waarde geladen is.- Een week later
max children reached,max listen queueen het kernellogboek controleren.
Veelgestelde vragen
Hoe bereken ik pm.max_children correct?
Waarom is een te hoge waarde gevaarlijker dan een te lage?
Waarom moet ik met PSS rekenen en niet met RSS?
Wanneer kies ik dynamic, wanneer ondemand en wanneer static?
Mag ik de swap in de rekensom meenemen?
Ik heb pm.max_children verhoogd, er verandert niets. Hoe komt dat?
Na het verlagen van pm.max_children start PHP-FPM niet meer. Wat is er gebeurd?
Heeft memory_limit iets met pm.max_children te maken?
2026 KernelHost GmbH. Alle rechten voorbehouden. Deze handleiding is auteursrechtelijk beschermd. Publicatie op andere websites, geheel, gedeeltelijk of in bewerkte vorm, is zonder onze schriftelijke toestemming niet toegestaan. Citeren met bronvermelding en link is uitdrukkelijk welkom.

