...

PHP-FPM-procesbeheer correct configureren: pm.max_children & Co. uitgelegd

php-fpm afstemmen bepaalt hoeveel PHP-FPM-processen tegelijkertijd mogen draaien, hoe snel nieuwe processen starten en hoe lang ze verzoeken verwerken. Ik laat je zien hoe je pm.max_kinderen, pm, pm.start_servers, pm.min_spare_servers, pm.max_spare_servers en pm.max_requests zo instelt dat je applicatie snel reageert onder belasting en de server niet in swapping terechtkomt.

Centrale punten

  • pm-modus: Kies tussen static, dynamic of ondemand, zodat processen beschikbaar zijn die passen bij je verkeer.
  • pm.max_kinderen: Het aantal gelijktijdige PHP-processen afstemmen op het RAM-geheugen en het werkelijke procesverbruik.
  • Start-/reservewaarden: pm.start_servers, pm.min_spare_servers, pm.max_spare_servers op een zinvolle manier in evenwicht brengen.
  • recycling: Met pm.max_requests geheugenlekken opvangen zonder onnodige overhead te creëren.
  • Controle: Houd logbestanden, status en RAM in de gaten en pas vervolgens stapsgewijs aan.

Waarom procesbeheer belangrijk is

Ik stuur mee PHP-FPM de uitvoering van elk PHP-script als een afzonderlijk proces, en elke parallelle aanvraag heeft zijn eigen worker nodig. Zonder passende limieten blokkeren aanvragen in wachtrijen, wat leidt tot Time-outs en fouten leidt. Als ik de bovengrenzen te hoog instel, slokt de procespool het werkgeheugen op en begint de kernel te swappen. Deze balans is geen gok: ik baseer me op reële meetwaarden en houd een veiligheidsmarge aan. Zo blijft de latentie laag en de doorvoer stabiel, zelfs als de belasting schommelt.

Voor mij is het belangrijk dat er een duidelijke streefcijfer: Hoeveel gelijktijdige PHP-uitvoeringen wil ik mogelijk maken zonder het RAM-geheugen te overschrijden? Tegelijkertijd controleer ik of er eerder knelpunten zijn in de Database, bij externe API's of op de webserver liggen. Alleen als ik de bottleneck ken, kies ik de juiste waarden voor pm, pm.max_children en Co. Ik begin conservatief, meet en verhoog dan stapsgewijs. Zo voorkom ik harde herstarts en onverwachte uitval.

De drie pm-modi: static, dynamic, ondemand

De modus statisch houdt altijd precies pm.max_children processen beschikbaar. Dit levert zeer voorspelbare latenties op, omdat er geen opstartproces nodig is. Ik gebruik static wanneer de belasting zeer gelijkmatig is en er voldoende RAM beschikbaar is. Bij wisselende vraag verspil ik met static echter gemakkelijk Geheugen. Daarom gebruik ik static specifiek op plaatsen waar ik een constante uitvoering nodig heb.

Met dynamisch start ik een starthoeveelheid en laat ik de poolgrootte tussen min_spare en max_spare ademen. Deze modus is geschikt voor verkeer met pieken, omdat workers naar behoefte worden aangemaakt en weer worden beëindigd. Ik houd daarbij altijd voldoende idle-processen achter de hand om pieken zonder wachttijd op te vangen. Te veel idle-workers leggen echter onnodig beslag op RAM, daarom houd ik de speling klein. Zo blijft het zwembad beweeglijk zonder op te zwellen.

In de modus ondemand Er zijn in eerste instantie geen workers, PHP-FPM start ze pas bij verzoeken. Dat bespaart geheugen in rustfasen, maar de eerste hit kost wat latentie. Ik kies ondemand voor zelden gebruikte pools, admin-tools of cron-eindpunten. Voor drukbezochte websites levert ondemand meestal slechtere reactietijden. Daar geef ik duidelijk de voorkeur aan dynamic met duidelijk ingestelde reservewaarden.

pm.max_children correct dimensioneren

Ik denk pm.max_kinderen uit het beschikbare RAM voor PHP en het gemiddelde geheugen per worker. Hiervoor reserveer ik eerst geheugen voor het systeem, de webserver, de database en de caches, zodat het systeem niet in de Uitbesteding loopt. Ik deel het resterende RAM door het werkelijk gemeten procesverbruik. Uit de theorie trek ik 20-30 % veiligheidsmarge af om uitschieters en piekbelastingen op te vangen. Ik gebruik het resultaat als startwaarde en observeer vervolgens het effect.

Ik bepaal het gemiddelde procesverbruik met tools zoals ps, top of htop en kijk naar RSS/RES. Belangrijk: ik meet onder normale belasting, niet in ruststand. Als ik veel plug-ins, frameworks of grote bibliotheken laad, stijgt het verbruik per worker merkbaar. Bovendien beperkt de CPU de curve: meer processen helpen niet als een Eendraads-Prestaties van de CPU per aanvraag beperkt. Wie zich verder wil verdiepen in de CPU-kenmerken, vindt achtergrondinformatie over de Single-thread-prestaties.

Ik houd mijn aannames transparant: hoeveel RAM heeft PHP werkelijk tot zijn beschikking? Hoe groot is een worker bij typische verzoeken? Welke pieken doen zich voor? Als de antwoorden kloppen, stel ik pm.max_children in, voer ik een zachte herlaadbeurt uit en controleer ik het RAM-geheugen, de responstijden en de foutpercentages. Pas daarna ga ik in kleine stapjes verder omhoog of omlaag.

Richtwaarden volgens servergrootte

De volgende tabel geeft mij Startwaarden aan de hand. Het vervangt het meten niet, maar biedt een solide richtlijn voor de eerste instellingen. Ik pas de waarden aan per toepassing en controleer ze met monitoring. Als er reserves onbenut blijven, verhoog ik ze voorzichtig. Als de server de RAM-limiet bereikt, verlaag ik de waarden weer.

Server-RAM RAM voor PHP Ø MB/werknemer pm.max_kinderen (Start) Gebruik
1–2 GB ~1 GB 50–60 15–20 Kleine websites, blogs
4–8 GB ~4–6 GB 60–80 30–80 Zakelijk, kleine winkels
16+ GB ~10–12 GB 70–90 100–160 Hoge belasting, API, winkels

Ik lees de tabel van rechts naar links: Past de Gebruik voor het project controleer ik of er voldoende RAM voor PHP is gereserveerd. Vervolgens kies ik een worker-grootte die past bij de codebasis en uitbreidingen. Daarna stel ik pm.max_children in en observeer ik het effect in de live-omgeving. De trefkans en stabiliteit nemen toe als ik deze stappen nauwkeurig documenteer.

Start-, reserve- en verzoekwaarden instellen

Met pm.start_servers Ik bepaal hoeveel processen direct beschikbaar zijn. Te laag zorgt voor koude starts onder belasting, te hoog verbruikt onnodig RAM. Ik richt me vaak op 15-30 % van pm.max_children en rond af als de belasting rustig begint. Bij pieken in het verkeer kies ik voor een iets hogere starthoeveelheid, zodat verzoeken niet binnenstromen voordat er voldoende workers klaarstaan. Deze fijnafstemming verlaagt de eerste responstijd aanzienlijk.

De waarden pm.min_spare_servers en pm.max_spare_servers definiëren de idle-spanne. Ik houd zoveel vrije workers beschikbaar dat nieuwe verzoeken direct toegang krijgen, maar niet zoveel dat de idle-processen geheugen verspillen. Bij winkels stel ik graag een smaller venster in om pieken af te vlakken. Met pm.max_aanvragen Ik recycle processen na enkele honderden verzoeken om geheugendrift te beperken. Voor onopvallende toepassingen kies ik 500-800, bij vermoeden van lekken ga ik bewust lager.

Bewaking en probleemoplossing

Ik controleer regelmatig Logboeken, statuspagina's en RAM. Waarschuwingen over bereikte pm.max_children-limieten zijn voor mij een duidelijk signaal om de bovengrens te verhogen of de code/database te optimaliseren. Als er zich 502/504-fouten voordoen, kijk ik in de webserverlogs en de wachtrijen. Duidelijke schommelingen in de latentie duiden op te weinig processen, blokkerende I/O of te hoge proceskosten. Ik kijk eerst naar de harde feiten en reageer dan met kleine stapjes, nooit met XXL-sprongen.

Ik herken knelpunten sneller als ik de Wachttijden langs de hele keten meten: webserver, PHP-FPM, database, externe diensten. Als de backend-tijd alleen bij bepaalde routes toeneemt, isoleer ik de oorzaken door middel van profilering. Als er overal wachttijden optreden, kijk ik naar de server- en poolgrootte. Het is ook nuttig om te kijken naar de wachtrijen van de workers en naar processen in de D-status. Pas als ik de situatie begrijp, wijzig ik de limieten – en documenteer ik elke wijziging nauwkeurig.

Webserver en PHP-FPM in combinatie

Ik zorg ervoor dat webserver-limieten en PHP-FPM harmoniëren. Te veel gelijktijdige verbindingen in de webserver bij te weinig workers veroorzaken wachtrijen en time-outs. Als het aantal workers hoog is, maar de webserver het aantal acceptaties beperkt, blijft de prestatie achter. Parameters zoals worker_connections, event-loop en Keep-Alive hebben een directe invloed op de PHP-belasting. Een praktische inleiding tot de fijnafstemming wordt gegeven door aanwijzingen over Threadpools in de webserver.

Ik behoud Keep-Alive-Tijdvenster in het oog houden, zodat lege verbindingen niet onnodig werknemers blokkeren. Voor statische assets zet ik agressieve caching voor PHP in om de werklast uit de pool te houden. Reverse-proxy-caches helpen bovendien wanneer identieke antwoorden vaak worden opgevraagd. Zo kan ik pm.max_children lager houden en toch sneller leveren. Minder werk per aanvraag is vaak de meest effectieve instelling.

Fijne instelschroeven in php-fpm.conf

Ik ga verder dan de basiswaarden en pas de Poolparameters prima. Met pm.max_spawn_rate beperk ik hoe snel nieuwe workers mogen worden aangemaakt, zodat de server bij piekbelastingen niet te agressief processen start en in CPU-thrashing terechtkomt. Voor ondemand stel ik met pm.process_idle_timeout vast hoe snel ongebruikte workers weer verdwijnen – te kort zorgt voor start-overhead, te lang bindt RAM. Bij luisteren-Socket kies ik tussen Unix-Socket en TCP. Een Unix-Socket bespaart overhead en biedt een overzichtelijke toewijzing van rechten via listen.owner, listen.group en listen.mode. Voor beide varianten gebruik ik listen.backlog voldoende hoog, zodat binnenkomende bursts in de kernelbuffer terechtkomen in plaats van onmiddellijk te worden afgewezen. Met rlimit_files verhoog ik indien nodig het aantal open bestanden per worker, wat bij veel gelijktijdige uploads en downloads voor stabiliteit zorgt. En als er prioriteiten nodig zijn, gebruik ik procesprioriteit, om weinig kritische pools wat minder prioriteit te geven aan de CPU-kant.

Slowlog en bescherming tegen vastlopers

Om trage verzoeken zichtbaar te maken, activeer ik de Slowlog. Met verzoek_slowlog_timeout Ik definieer de drempel (bijvoorbeeld 2-3 s) vanaf wanneer een stacktrace in het slowlog wordt geschreven. Zo vind ik blokkerende I/O, dure loops of onverwachte locks. Tegen echte vastlopers zet ik verzoek_terminate_timeout, dat abrupt wordt afgebroken als een verzoek te lang duurt. Ik beschouw deze tijdvensters als consistent met max_uitvoering_tijd uit PHP en de time-outs van de webserver, zodat de ene laag niet eerder afbreekt dan de andere. In de praktijk begin ik conservatief, analyseer ik slowlogs onder belasting en pas ik de drempels stapsgewijs aan totdat de signalen betekenisvol zijn, zonder de log te overspoelen.

Opcache, memory_limit en hun invloed op de grootte van de workers

Ik ontvang de Opcache in mijn RAM-planning. Zijn gedeelde geheugenruimte telt niet per worker, maar wordt door alle processen gezamenlijk gebruikt. Grootte en fragmentatie (opcache.geheugen_verbruik, interned_strings_buffer) hebben een aanzienlijke invloed op de opwarmtijd en het trefpercentage. Een goed gedimensioneerde Opcache verlaagt de CPU- en RAM-belasting per verzoek, omdat er minder code opnieuw wordt gecompileerd. Tegelijkertijd merk ik dat geheugenlimiet: Een hoge waarde beschermt weliswaar tegen out-of-memory in individuele gevallen, maar verhoogt het theoretische worst-case-budget per worker. Ik plan daarom met een gemeten gemiddelde plus buffer, niet met de kale memory_limit. Functies zoals preloading of JIT verhogen het geheugengebruik – ik test ze gericht en bereken het extra verbruik in de pm.max_children-berekening.

Pools scheiden en prioriteren

Ik deel applicaties op meerdere zwembaden wanneer de belastingsprofielen sterk verschillen. Een pool voor frontend-verkeer, een voor admin/backend, een derde voor cron/uploads: zo isoleer ik pieken en wijs ik gedifferentieerde limieten toe. Voor zelden bezochte eindpunten stel ik ondemand met korte idle-time-out, voor de frontend dynamisch met een krappe speling. Over gebruiker/groep en indien van toepassing. chroot zorg ik voor een zuivere isolatie, terwijl socketrechten bepalen welk webserverproces toegang krijgt. Waar prioriteiten nodig zijn, krijgt de frontend meer pm.max_kinderen en indien nodig een neutrale procesprioriteit, terwijl Cron/Reports met een kleiner budget en een lagere prioriteit worden uitgevoerd. Zo blijft de gebruikersinterface snel reageren, zelfs als er op de achtergrond zware taken worden uitgevoerd.

Status-eindpunten correct gebruiken

Voor de looptijddiagnose activeer ik pm.status_path en optioneel ping.path per pool. In de status zie ik actieve/inactieve werknemers die Lijstenwachtrij, doorvoersnelheidsgerelateerde tellers en slow-request-metrics. Een voortdurend groeiende lijstwachtrij of constant 0 idle-workers zijn voor mij alarmsignalen. Ik bescherm deze eindpunten achter Auth en een intern netwerk, zodat er geen bedrijfsdetails naar buiten komen. Daarnaast activeer ik catch_workers_output, als ik op korte termijn stdout/stderr uit de workers wil verzamelen – bijvoorbeeld bij moeilijk reproduceerbare fouten. Ik combineer deze signalen met systeemstatistieken (RAM, CPU, I/O) om te beslissen of ik pm.max_children verhoog, reservewaarden bijwerk of aan de toepassing werk.

Bijzonderheden in containers en VM's

Op Containeren en kleine VM's houd ik rekening met cgroup-limieten en het gevaar van de OOM-killer. Ik stel pm.max_children strikt in volgens de Containergeheugenlimiet en test ik piekbelastingen, zodat geen enkele worker wordt afgesloten. Zonder swap in containers is de veiligheidsmarge bijzonder belangrijk. Bij CPU-quota's schaal ik het aantal workers naar het beschikbare aantal vCPU's: als de toepassing CPU-gebonden is, leidt meer parallelliteit eerder tot wachtrijen dan tot doorvoer. IO-gebonden workloads kunnen meer processen aan, zolang het RAM-budget toereikend is. Daarnaast stel ik emergency_restart_threshold en emergency_restart_interval voor het masterproces, om een crashspiraal op te vangen als een zeldzame bug meerdere kinderen in korte tijd treft. Zo blijft de dienst beschikbaar terwijl ik de oorzaak analyseer.

Vlotte implementaties en herlaadbeurten zonder uitval

Ik ben van plan Herlaadbeurten zodat lopende verzoeken netjes worden afgerond. Een graceful reload (bijv. via systemd reload) neemt nieuwe configuraties over zonder open verbindingen abrupt te beëindigen. Ik houd het socketpad stabiel, zodat de webserver geen verbroken verbinding ziet. Bij versieveranderingen die veel Opcache ongeldig maken, warm ik de cache voor (preloading/warmup-verzoeken) om de latentiepieken direct na de implementatie te beperken. Grotere wijzigingen test ik eerst op een kleinere pool of in een Canary-instantie met identieke configuratie, voordat ik de waarden op grote schaal implementeer. Elke aanpassing komt met een tijdstempel en metrische screenshots in mijn wijzigingslogboek terecht – dat verkort het opsporen van fouten als er onverwachte bijwerkingen zijn.

Burstgedrag en wachtrijen

Ik vang piekbelastingen op met een afgestemd Wachtrijontwerp af. Ik zet listen.backlog zo hoog dat de kernel tijdelijk meer verbindingspogingen kan bufferen. Aan de webserverzijde beperk ik het maximale aantal gelijktijdige FastCGI-verbindingen per pool zodanig dat ze pm.max_kinderen past. Daardoor stapelen bursts zich liever kort op in de webserver (voordelig) dan diep in PHP (duur). Ik meet de Lijstenwachtrij in FPM-status: als deze regelmatig stijgt, verhoog ik het aantal workers, optimaliseer ik de cache-hitpercentages of verlaag ik agressieve keep-alive-waarden. Het doel is om bij pieken de Tijd-tot-eerste-byte stabiel te houden, in plaats van verzoeken in eindeloze wachtrijen te laten verzanden.

Praktische workflow voor aanpassingen

Ik begin met een Controle: RAM-budget, procesgrootte, I/O-profielen. Vervolgens stel ik conservatieve startwaarden in voor pm.max_children en de pm-modus. Daarna voer ik belastingstests uit of observeer ik echte piekuren. Ik log alle wijzigingen, inclusief statistieken en tijdvensters. Na elke aanpassing controleer ik het RAM-geheugen, de latentie P50/P95 en de foutpercentages – pas daarna volg ik de volgende stap.

Als ik herhaaldelijk tegen mijn limiet aanloop, escaleer ik niet meteen de Werknemer-getal. Eerst optimaliseer ik queries, cache-hitpercentages en dure functies. Ik verplaats IO-intensieve taken naar wachtrijen en verkort de responstijden. Pas als de applicatie efficiënt werkt, vergroot ik de poolgrootte. Deze procedure bespaart resources en voorkomt gevolgschade op andere plaatsen.

Typische scenario's: voorbeeldwaarden

Op een 2 GB vServer reserveer ik ongeveer 1 GB voor PHP-FPM en stel een worker-verbruik van ongeveer 50-60 MB in. Daarmee begin ik bij pm.max_children rond 15-20 en gebruik ik dynamic met een kleine starthoeveelheid. Ik houd min_spare op 2-3, max_spare op 5-6. Ik stel pm.max_requests in op 500, zodat processen regelmatig worden gewisseld. Deze instellingen zorgen voor stabiele reactietijden bij kleine projecten.

Bij 8 GB RAM plan ik meestal 4-6 GB voor PHP en stel de grootte van de workers in op 60-80 MB. Dit resulteert in 30-80 child-processen als startbereik. pm.start_servers ligt tussen 15 en 20, min_spare tussen 10 en 15, max_spare tussen 25 en 30. pm.max_requests kies ik tussen 500 en 800. Onder belasting controleer ik of de RAM-piek ruimte laat en verhoog ik deze vervolgens voorzichtig.

In high-load-opstellingen met 16+ GB RAM reserveer ik 10–12 GB voor FPM. Bij 70-90 MB per worker kom ik al snel uit op 100-160 processen. Of statisch of dynamisch zinvol is, hangt af van de belasting. Voor een permanent hoge belasting is statisch de beste keuze, voor een golvende vraag is dynamisch beter. In beide gevallen blijft consequente monitoring verplicht.

Struikelblokken vermijden en prioriteiten stellen

Ik verwar het aantal niet met het Bezoekers met het aantal gelijktijdige PHP-scripts. Veel paginaweergaven raken caches, leveren statische bestanden of blokkeren buiten PHP. Daarom dimensioneer ik pm.max_children op basis van gemeten PHP-tijd, niet op basis van sessies. Als processen te zuinig worden ingesteld, zie ik wachtende verzoeken en stijgende foutpercentages. Bij te hoge waarden kantelt het geheugen in swap en wordt alles langzamer.

Een veel voorkomende misvatting: meer processen betekent meer Snelheid. In werkelijkheid gaat het om de balans tussen CPU, IO en RAM. Als de CPU naar 100 % gaat en de latentie omhoog schiet, helpen extra workers nauwelijks. Het is beter om de echte bottleneck weg te nemen of de belasting via de cache te verminderen. Waarom workers vaak de bottleneck zijn, wordt uitgelegd in de gids over PHP-worker als bottleneck.

Kort samengevat

Ik bepaal eerst de werkelijke RAM-verbruik per worker en stel op basis daarvan pm.max_children in met buffer. Vervolgens kies ik de pm-modus die past bij de belastingvorm en breng ik de start- en reservewaarden in evenwicht. Met pm.max_requests houd ik processen fris, zonder onnodige overhead. Logs, status en statistieken leid ik naar een overzichtelijke monitoring, zodat elke wijziging meetbaar blijft. Zo bereik ik korte responstijden, stabiele pools en een serverbelasting die reserves heeft voor pieken.

Huidige artikelen