Chi gestisce uno stack LEMP o LAMP in produzione conosce bene il dilemma: PHP-FPM va lasciato “respirare” con un process manager dinamico, oppure conviene bloccare tutto su un numero fisso di worker? La risposta non è scontata, e sbagliarla ha un costo diretto in latenza durante i picchi di traffico. Vediamo perché, per molti carichi di lavoro, pm static è la scelta più solida — e come calcolare i parametri giusti senza affidarsi a numeri presi a caso da qualche post su Stack Overflow.
I tre process manager di PHP-FPM
PHP-FPM gestisce i processi worker attraverso la direttiva pm nel file di pool (tipicamente /etc/php/8.x/fpm/pool.d/www.conf), che può assumere tre valori:
- dynamic: il numero di processi figlio oscilla tra
pm.min_spare_serversepm.max_spare_servers, fino al tetto dipm.max_children. È il default su molte distribuzioni ed è pensato per bilanciare consumo di RAM e capacità di risposta. - ondemand: i processi vengono creati solo quando arriva una richiesta e vengono terminati dopo
pm.process_idle_timeoutsecondi di inattività. Nessun worker “a riposo” occupa memoria, ma ogni nuovo processo comporta un fork che ha un costo in latenza. - static: il numero di worker è fissato esattamente a
pm.max_children, tutti avviati all’avvio del servizio e mai terminati. Nessun fork a runtime, nessuna sorpresa.
La differenza pratica tra dynamic/ondemand e static si vede sotto carico: ogni volta che PHP-FPM deve forkare un nuovo processo per rispondere a un picco di richieste, quel fork costa tempo di CPU e, sotto pressione, può accodare le richieste in ingresso nel socket backlog. Su un server che serve traffico costante e prevedibile, questo overhead è puro spreco.
Perché “dynamic” può ingannare
Il process manager dinamico sembra la scelta “intelligente” perché si adatta al carico. In realtà introduce una variabile in più proprio nei momenti in cui non la vorresti: durante un picco di traffico, quando i worker spare non bastano più, PHP-FPM deve forkarne di nuovi mentre il sistema è già sotto stress. Il risultato è un aumento di latenza percepibile negli access log, spesso confuso con un problema del database o del codice applicativo quando in realtà è il process manager stesso a introdurre il collo di bottiglia.
Quando usare pm static
La regola pratica è semplice: se il server ha traffico sostenuto e memoria sufficiente per tenere tutti i worker sempre attivi, static è la scelta giusta. Se invece si gestiscono più pool PHP-FPM su un host condiviso con memoria limitata (tipico di ambienti multi-tenant o hosting condiviso), ondemand resta preferibile perché libera RAM quando il traffico cala.
; /etc/php/8.3/fpm/pool.d/www.conf
pm = static
pm.max_children = 40
pm.max_requests = 1000
Da notare pm.max_requests: anche in modalità static conviene non lasciarlo a 0 (illimitato). Un valore alto — 1000 richieste è un buon punto di partenza — fa sì che ogni worker venga riciclato periodicamente, mitigando eventuali memory leak nelle librerie PHP senza introdurre l’overhead di respawn continui tipico della modalità dinamica.
Come calcolare pm.max_children senza indovinare
Il valore corretto di pm.max_children dipende da quanta RAM è disponibile e da quanta ne consuma mediamente un singolo worker PHP-FPM — un dato che varia moltissimo in base al framework (un’app Symfony con Doctrine pesa parecchio di più di uno script WordPress minimale).
Per misurare il consumo medio reale dei worker già in esecuzione:
ps --no-headers -o rss -C php-fpm8.3 | awk '{ sum += $1; n++ } END { print sum/n/1024 " MB" }'
Questo comando interroga la RSS (Resident Set Size) di tutti i processi php-fpm attivi e ne calcola la media in MB. Con quel numero in mano, la formula diventa:
pm.max_children = (RAM dedicata a PHP-FPM in MB) / (RSS medio per worker in MB)
Ad esempio, su un server con 6 GB di RAM riservati al pool PHP-FPM e worker che consumano in media 60 MB ciascuno:
6000 MB / 60 MB ≈ 100 worker
È fondamentale non allocare a PHP-FPM tutta la RAM disponibile sulla macchina: bisogna lasciare margine per il sistema operativo, il web server (Nginx o Apache in front-end), la cache degli oggetti (Redis/Memcached) e, se presente sullo stesso host, il database. Una regola prudente è dedicare a PHP-FPM non più del 60-70% della RAM totale su un server dedicato al solo stack web.
Monitoraggio continuo, non solo calcolo una tantum
Il consumo medio per worker cambia nel tempo — un deploy che introduce una libreria pesante, una query N+1 che gonfia il footprint di memoria, o semplicemente un aumento del traffico su endpoint più complessi. Vale la pena schedulare periodicamente il comando ps sopra (ad esempio via cron con output su un file di log, oppure integrandolo in un dashboard di monitoring come Netdata o Prometheus con node_exporter) per verificare che il valore di pm.max_children resti coerente con la realtà, invece di scoprirlo durante un incidente in produzione.
Un altro segnale da tenere d’occhio è il log di PHP-FPM stesso: se compaiono righe del tipo
WARNING: [pool www] server reached pm.max_children setting (40), consider raising it
significa che il tetto è stato raggiunto e le richieste in eccesso stanno finendo in coda sul socket, con conseguente aumento del tempo di risposta. È il segnale più diretto per capire se il dimensionamento fatto a tavolino regge davvero sotto carico reale.
Conclusione
Non esiste un process manager “giusto” in assoluto: la scelta dipende dal profilo di traffico e dalla disponibilità di memoria. Ma per la maggior parte dei server di produzione con traffico prevedibile e risorse dedicate, pm static abbinato a un pm.max_children calcolato sul consumo reale di RSS — e non copiato da un tutorial generico — elimina una fonte di latenza spesso invisibile finché non arriva il picco di traffico che la rende evidente. Vale la pena rivedere la configurazione dei propri pool PHP-FPM con questo approccio, misurando prima di decidere.
Fonte: LinuxBlog.io — PHP-FPM tuning: Using ‘pm static’ for max performance