PHP-FPM tuning voor WordPress: pm.max_children berekenen en de juiste procesmanager kiezen

PHP-FPM pool-instellingen bepalen hoeveel PHP workers je server tegelijk kan draaien en hoe ze worden beheerd. Deze gids loopt door de drie procesmanager-modi, de formule om pm.max_children te berekenen op basis van beschikbaar RAM, de bijbehorende richtlijnen voor reserveworkers en recycling, en een uitgewerkt voorbeeld voor een 4 GB VPS met WordPress en WooCommerce.

Elk niet-gecacht WordPress-verzoek draait in een PHP-FPM worker process. Wat die workers zijn en waarom ze uitgeput raken legt het concept uit. Dit artikel is het praktische vervolg: hoe je de pool configureert die die workers beheert, afgestemd op de server waarop hij draait.

PHP-FPM pool-configuratie staat in een apart bestand, niet in php.ini. Op Debian en Ubuntu is dat doorgaans /etc/php/8.x/fpm/pool.d/www.conf; op Red Hat en afgeleiden in /etc/php-fpm.d/www.conf. De PHP-handleiding documenteert elke richtlijn. De belangrijkste voor WordPress zijn pm (de procesmanager-modus), pm.max_children, pm.start_servers, pm.min_spare_servers, pm.max_spare_servers en pm.max_requests.

Een procesmanager-modus kiezen: dynamic, static, ondemand

De pm-richtlijn bepaalt hoe PHP-FPM worker-processen start en stopt. Er zijn drie modi:

pm = dynamic start een vast aantal workers bij het opstarten (pm.start_servers) en schaalt vervolgens tussen pm.min_spare_servers en pm.max_spare_servers inactieve workers, met pm.max_children als plafond. Dit is de standaard en de juiste keuze voor de meeste WordPress-servers. Het houdt een warme pool klaar voor verkeer zonder geheugen te verspillen in rustige periodes.

pm = static start exact pm.max_children workers bij het opstarten en houdt ze permanent draaiend. Er is geen opschaling, geen spawn-vertraging en geen opruimen van inactieve workers. Deze modus levert de laagste latentie per verzoek omdat er altijd een worker klaarstaat. De keerzijde is geheugen: elke worker verbruikt RAM, of er nu verzoeken zijn of niet. Gebruik static alleen op een dedicated server met voorspelbaar verkeer en voldoende RAM om elke worker altijd in het geheugen te houden.

pm = ondemand start met nul workers. Workers worden aangemaakt zodra een verzoek binnenkomt en opgeruimd na pm.process_idle_timeout seconden inactiviteit (standaard 10 seconden). Deze modus gebruikt het minste geheugen maar heeft de hoogste koude-start-latentie. Handig op shared hosting waar tientallen sites één server delen en de meeste op elk moment inactief zijn.

Voor een typische WordPress VPS of dedicated server is pm = dynamic het aanbevolen startpunt.

pm.max_children berekenen voor jouw server

pm.max_children is het harde plafond voor gelijktijdige PHP workers. Stel het te laag in en verzoeken komen in de wachtrij tijdens verkeerspieken (wat leidt tot hoge TTFB). Stel het te hoog in en de server heeft onvoldoende RAM, begint te swappen en wordt trager dan een kleinere pool zou zijn geweest.

De formule is:

pm.max_children = (Totaal beschikbaar RAM voor PHP) / (Gemiddeld RAM per worker)

Stap 1 — beschikbaar RAM voor PHP bepalen

Begin met het totale RAM van de server en trek af wat het besturingssysteem, de database, de webserver, Redis en eventuele andere diensten gebruiken. Op een 4 GB VPS met MySQL, nginx en Redis is een realistische schatting:

4096 MB totaal
 - 512 MB  OS + buffers
 - 1024 MB MySQL (controleer innodb_buffer_pool_size)
 - 128 MB  nginx
 - 256 MB  Redis (indien gebruikt voor object cache)
= 2176 MB beschikbaar voor PHP-FPM

Stap 2 — gemiddeld RAM per worker bepalen

De betrouwbaarste methode is meten op een draaiende server. Nadat de site minstens een uur verkeer heeft afgehandeld:

ps -eo rss,comm | grep php-fpm | grep -v master | awk '{sum+=$1; count++} END {printf "Gemiddeld worker RSS: %.0f MB\n", sum/count/1024}'

Kun je nog niet meten, gebruik dan deze startschattingen:

  • WordPress zonder WooCommerce: 40–60 MB per worker
  • WordPress met WooCommerce: 60–90 MB per worker
  • WordPress met zware plugins (paginabouwers, membership, LMS): 80–120 MB per worker

Stap 3 — delen

Met het voorbeeld hierboven (2176 MB beschikbaar, 60 MB per worker):

2176 / 60 ≈ 36 workers

Rond naar beneden af om een veiligheidsmarge te houden. pm.max_children = 30 instellen is een redelijk startpunt voor deze server.

Een veelgemaakte fout: memory_limit verwarren met worker-RAM

PHP's memory_limit-richtlijn (ingesteld in php.ini) begrenst hoeveel RAM een enkel PHP-script mag toewijzen op het piekmoment. Dat is niet hetzelfde als het daadwerkelijke geheugengebruik van een worker. De resident set size (RSS) van een worker is de echte geheugenvoetafdruk, en die is vrijwel altijd kleiner dan memory_limit. Een site met memory_limit = 256M betekent niet dat elke worker 256 MB gebruikt. Meet RSS, niet de limiet.

pm.start_servers, pm.min_spare_servers, pm.max_spare_servers instellen

Deze drie richtlijnen gelden alleen bij pm = dynamic.

  • pm.start_servers is het aantal workers dat bij het starten van FPM wordt aangemaakt. Een goede standaard is het midden tussen pm.min_spare_servers en pm.max_spare_servers.
  • pm.min_spare_servers is het minimumaantal inactieve workers dat de pool warm houdt. Als het aantal inactieve workers hieronder zakt, maakt FPM nieuwe aan. Stel het in op het basisverkeer zonder wachtrij. Voor een VPS met gemiddeld verkeer is 25–30% van pm.max_children een redelijk startpunt.
  • pm.max_spare_servers is het maximumaantal inactieve workers voordat FPM ze opruimt om geheugen vrij te maken. Stel dit iets hoger in dan pm.min_spare_servers. Het heeft geen zin om het gelijk te zetten aan pm.max_children — dat zou gelijkwaardig zijn aan pm = static.

Voorbeeld voor pm.max_children = 30:

pm = dynamic
pm.max_children = 30
pm.start_servers = 10
pm.min_spare_servers = 8
pm.max_spare_servers = 15

pm.max_requests: geheugenlekken voorkomen

pm.max_requests stelt het aantal verzoeken in dat een enkele worker afhandelt voordat hij wordt gestopt en opnieuw gestart. Dit voorkomt dat geheugenlekken zich ophopen gedurende de levensduur van een worker. Slecht geschreven plugins of thema's kunnen bij elk verzoek kleine hoeveelheden geheugen lekken; zonder recycling groeien workers langzaam totdat de server uitgeput raakt.

Een waarde van 0 betekent dat de worker voor altijd leeft (geen recycling). Dat is de standaard en dat is riskant op WordPress-sites met veel plugins.

Stel pm.max_requests in tussen 500 en 1000. Lagere waarden recyclen agressiever (meer overhead door herstart), hogere waarden recyclen minder (meer risico op opgehoopte lekken). Voor de meeste WordPress-sites is pm.max_requests = 500 een veilig startpunt.

pm.max_requests = 500

De PHP-FPM statuspagina en het foutenlog lezen

Het foutenlog

PHP-FPM schrijft poolgebeurtenissen naar het foutenlog (doorgaans /var/log/php8.x-fpm.log op Debian/Ubuntu). De belangrijkste regel om op te letten is:

WARNING: [pool www] server reached pm.max_children setting (30), consider raising it

Deze regel betekent dat alle workers tegelijk bezig waren en minstens één verzoek moest wachten. Als het af en toe verschijnt tijdens verkeerspieken is het informatief. Als het continu verschijnt, is de pool te klein voor de werklast.

De statuspagina

PHP-FPM heeft een ingebouwd status-endpoint. Schakel het in door toe te voegen aan de poolconfiguratie:

pm.status_path = /fpm-status

Configureer nginx vervolgens om het lokaal te serveren:

location = /fpm-status {
    allow 127.0.0.1;
    deny all;
    fastcgi_pass unix:/run/php/php8.3-fpm.sock;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}

Na het herladen van zowel FPM als nginx:

curl http://127.0.0.1/fpm-status

De uitvoer bevat active processes (workers die momenteel een verzoek afhandelen), idle processes, total processes, max active processes (het hoogste punt sinds FPM is gestart) en max children reached (het aantal keren dat het poolplafond is bereikt). Deze cijfers vertellen of pm.max_children goed is ingesteld: als max active processes regelmatig gelijk is aan pm.max_children, is de pool te klein.

Uitgewerkt voorbeeld: 4 GB VPS met WordPress + WooCommerce

Server: 4 GB RAM, 2 vCPU's, Debian 12, PHP 8.3, nginx, MariaDB, Redis object cache.

Stap 1 — beschikbaar RAM voor PHP:

4096 MB totaal
 - 400 MB  OS
 - 1024 MB MariaDB (innodb_buffer_pool_size = 768M + overhead)
 - 100 MB  nginx
 - 256 MB  Redis
= 2316 MB beschikbaar voor PHP-FPM

Stap 2 — worker RSS meten:

Na een dag draaien rapporteert ps een gemiddelde van 72 MB per worker (WooCommerce met 25 actieve plugins).

Stap 3 — berekenen:

2316 / 72 ≈ 32 workers

Afronden naar 28 om ruimte te laten voor geheugenpieken.

Poolconfiguratie (/etc/php/8.3/fpm/pool.d/www.conf):

[www]
pm = dynamic
pm.max_children = 28
pm.start_servers = 8
pm.min_spare_servers = 6
pm.max_spare_servers = 12
pm.max_requests = 500
pm.status_path = /fpm-status

FPM herladen:

sudo systemctl reload php8.3-fpm

Controleren: monitor /var/log/php8.3-fpm.log op pm.max_children-waarschuwingen gedurende de komende 48 uur. Als de waarschuwing nooit verschijnt en max active processes in /fpm-status ruim onder de 28 blijft, is de pool goed gedimensioneerd. Als de waarschuwing regelmatig afgaat, verhoog dan pm.max_children (als het RAM het toelaat) of verlaag het geheugenverbruik per worker door plugins te auditen en OPcache-tuning toe te passen.

Klaar met terugkerende traagheid?

Traagheid komt vaak terug na snelle fixes. Professioneel onderhoud houdt updates, caching en limieten consequent op orde.

Bekijk WordPress onderhoud

Doorzoek deze site

Begin met typen om te zoeken, of blader door de kennisbank en blog.