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_serversis het aantal workers dat bij het starten van FPM wordt aangemaakt. Een goede standaard is het midden tussenpm.min_spare_serversenpm.max_spare_servers.pm.min_spare_serversis 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% vanpm.max_childreneen redelijk startpunt.pm.max_spare_serversis het maximumaantal inactieve workers voordat FPM ze opruimt om geheugen vrij te maken. Stel dit iets hoger in danpm.min_spare_servers. Het heeft geen zin om het gelijk te zetten aanpm.max_children— dat zou gelijkwaardig zijn aanpm = 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.