Calcolare pm.max_children di PHP-FPM invece di tirare a indovinare
Il messaggio server reached pm.max_children non significa che devi raddoppiare il valore. Misura, calcola, scegli la modalità e dimostra poi che il valore regge davvero.
Prima o poi nel log di PHP-FPM compare questa riga, che porta con sé anche il consiglio:
WARNING: [pool www] server reached pm.max_children setting (5), consider raising it
Il consiglio non è sbagliato, è solo incompleto. pm.max_children è l'unico parametro di PHP-FPM in cui un valore troppo alto è più pericoloso di uno troppo basso. Troppo basso costa tempo di attesa e, nel caso estremo, un 502. Troppo alto costa la RAM dell'intero server, e a quel punto è il kernel a scegliere quale processo terminare. Per esperienza non tocca a PHP, ma al database.
Questa guida mostra il percorso che porta dall'indovinare al calcolare: misurare il fabbisogno di memoria di un processo di lavoro, stabilire il budget, scegliere la modalità e dimostrare poi che il valore è quello giusto.
Tutte le indicazioni valgono per Debian 13 (trixie), Debian 12 (bookworm), Ubuntu 24.04 LTS e Ubuntu 22.04 LTS. I comandi sono scritti per l'uso come root; se lavori come utente normale, anteponi sudo. Sostituisci ovunque il numero di versione di PHP con quello del tuo sistema.
| Sistema | PHP | Servizio | File del pool | Log di FPM |
| 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 |
Quali versioni siano installate lo mostra un'occhiata alla directory. Sui server che hanno alle spalle un upgrade di distribuzione spesso sono due:
ls /etc/php/
ls /etc/php/*/fpm/pool.d/
Che cosa limita davvero pm.max_children
Il valore non limita i visitatori e nemmeno le connessioni, ma il numero di richieste PHP che nello stesso istante vengono eseguite. Una richiesta da 80 millisecondi occupa un processo di lavoro per 80 millisecondi e poi lo libera. Da qui discende quasi tutto il resto:
- Molto traffico richiede pochi processi, finché gli script sono veloci. 40 richieste al secondo da 80 millisecondi ciascuna danno poco più di tre richieste simultanee, non 40.
- Un solo punto lento ribalta il calcolo. Una chiamata a un'interfaccia di programmazione esterna senza un termine proprio tiene occupato un processo per cinque secondi, anche se questo non sta facendo nulla. Cinque chiamate del genere al secondo impegnano stabilmente 25 processi.
- Le connessioni in attesa non occupano alcun processo. Una connessione keep-alive verso nginx costa un descrittore di file, non un processo di lavoro.
Se tutti i processi sono occupati, le nuove richieste attendono nella coda di accettazione del socket. La sua dimensione è indicata nel file del pool sotto listen.backlog, di serie 511, e il kernel la limita in più tramite net.core.somaxconn, che su tutti e quattro i sistemi vale 4096:
sysctl net.core.somaxconn
Finché la coda basta, il visitatore nota soltanto tempi di caricamento più lunghi. Quando si riempie anche quella, il kernel rifiuta la connessione, nginx registra 11: Resource temporarily unavailable e al visitatore arriva un 502. Per distinguerlo dalle altre cause: risolvere l'errore nginx 502 Bad Gateway.
Un dettaglio che genera parecchia confusione: l'avviso compare una volta per fase di saturazione, non per ogni richiesta coinvolta. FPM imposta internamente un contrassegno e lo cancella solo quando torna libero un processo. Una singola riga può quindi rappresentare un'intera ora di punta. Chi conta le righe sottovaluta il problema. La misura onesta è il contatore max children reached nella pagina di stato, che viene richiamata più avanti.
La via del ritorno, prima di cambiare qualcosa
Qui due cose possono andare storte, ed entrambe colpiscono il servizio in produzione.
Primo: FPM non si avvia più. Un systemctl reload invia al processo principale il segnale USR2, dopodiché questo si riavvia da solo con la nuova configurazione. Se la configurazione non è valida, l'inizializzazione si interrompe e il processo principale termina. Un'istanza in esecuzione diventa così un'istanza morta. Per questo, senza eccezioni, prima si verifica e poi si ricarica:
php-fpm8.2 -t
Se tutto è in ordine, l'output termina con test is successful.
Secondo: il valore è troppo alto. In quel caso il server non si rompe subito, ma al prossimo picco di carico, e in modo così radicale che anche un accesso SSH si blocca o non si stabilisce affatto. Per questo ti serve una seconda via d'ingresso al server.
Sui server root KVM e sui server dedicati di KernelHost è la console VNC nell'area clienti. È agganciata al livello di virtualizzazione, o al collegamento stesso, e resta raggiungibile anche quando il servizio SSH non risponde più per mancanza di memoria. Accedi prima almeno una volta per quella strada. Una via di salvataggio provata per la prima volta durante l'emergenza non è una via di salvataggio.
Crea inoltre una copia del file del pool, con marca temporale, in modo che un secondo tentativo non sovrascriva la prima copia:
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/
La via del ritorno è poi lunga tre righe:
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
Un parapetto per il caso in cui sbagli i conti
Un limite di memoria sull'unità systemd fa sì che una stima sbagliata colpisca un processo di lavoro PHP e non il database: il kernel termina allora un processo all'interno della control group di FPM, invece di cercarsi il più grande dell'intero sistema.
mkdir -p /etc/systemd/system/php8.2-fpm.service.d
cat > /etc/systemd/system/php8.2-fpm.service.d/memoria.conf <<'EOF'
[Service]
MemoryHigh=1500M
MemoryMax=2G
EOF
systemctl daemon-reload
systemctl restart php8.2-fpm
Verifica che sia stato recepito:
systemctl show php8.2-fpm -p MemoryHigh -p MemoryMax
MemoryHigh frena e libera memoria, MemoryMax è il limite rigido. Tutti e quattro i sistemi usano la gerarchia unificata delle control group, quindi i valori hanno effetto immediato. Questo è un parapetto, non un sostituto del calcolo: al raggiungimento del limite il visitatore coinvolto vede comunque un errore, però non cade l'intero server. Per collocare in modo pulito questi file aggiuntivi: creare un servizio systemd personalizzato.
E la terza regola, quella che non richiede alcun comando: una modifica per volta. Chi tocca insieme la modalità, pm.max_children e i valori spare, poi non sa che cosa abbia funzionato.
Ricognizione: quale pool conta davvero
Ogni pool ha il proprio pm.max_children, e per la memoria conta la somma su tutti i pool:
grep -n "^pm" /etc/php/*/fpm/pool.d/*.conf
Nella configurazione predefinita su tutti e quattro i sistemi c'è la stessa cosa:
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
Non sono raccomandazioni, ma segnaposto che permettono a PHP di avviarsi anche su una macchina di prova molto piccola. A questo si aggiunge un limite massimo globale valido per tutti i pool:
grep -n "^process.max" /etc/php/*/fpm/php-fpm.conf
Se l'output resta vuoto, la riga è commentata e vale il valore predefinito 0, cioè nessun limite globale. Sui server con molti pool è proprio questa riga a impedire che la somma faccia saltare la memoria non appena più pool si trovano sotto carico contemporaneamente.
Alla fine non conta il file, ma quello che FPM ne ricava. L'opzione -tt stampa la configurazione completamente risolta, con tutti i valori predefiniti che non compaiono in nessun file:
php-fpm8.2 -tt 2>&1 | grep -E "^\[|pm |pm\.|listen ="
Più avanti questo output sarà anche la prova che una modifica è stata recepita.
Misurare il fabbisogno di memoria di un processo di lavoro
È qui che la maggior parte delle guide diventa imprecisa. Tre numeri vengono regolarmente confusi:
memory_limit, di serie 128M in modalità FPM: un tetto massimo per richiesta, non un consumo. Uno script che ha bisogno di 12 MB continua a usarne 12 anche con 512M.- RSS: tutto ciò che in quel momento si trova nella RAM, compresi l'opcache condiviso e le librerie condivise. Chi somma l'RSS di 20 processi conta lo stesso opcache venti volte.
- PSS: le pagine condivise vengono divise per il numero di processi che le usano. È l'unico dei tre numeri che si possa sommare con senso.
Per il calcolo ti serve il PSS. Il kernel lo fornisce già aggregato in /proc/<pid>/smaps_rollup, leggibile come 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 "nessun processo di lavoro trovato"; exit }
printf "Processi: %d Somma: %.0f MiB Media: %.1f MiB\n", n, s/1024, s/1024/n
}'
Per confronto, lo stesso pool misurato con l'RSS:
ps -eo pid,rss,args --sort=-rss | grep '[p]hp-fpm' | head
La somma dell'RSS risulta nettamente più alta, a seconda della dimensione dell'opcache. Chi la usa per i conti ottiene un pm.max_children troppo piccolo e si compra tempo di attesa di cui non ci sarebbe bisogno.
Due condizioni decidono se la misura vale qualcosa. Misura sotto carico reale, non dopo un riavvio: un processo appena generato costa poco, perché all'inizio si limita a condividere le pagine di memoria del processo principale, e diventa costoso solo con le prime richieste. E non fermarti al valore medio: se la media è 60 MiB e il processo più grande arriva a 190 MiB, non fare i conti con 60.
La seconda fonte: il log degli accessi di FPM
Su richiesta FPM registra per ogni richiesta il consumo di picco e il tempo di esecuzione. Le due righe si trovano commentate nel file del pool:
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%%"
La grafia %{mili}d è corretta così, arriva invariata dal file fornito con il pacchetto e restituisce il tempo di esecuzione in millisecondi, mentre %{kilo}M dà il consumo di picco in kilobyte. Poi verifica, ricarica e controlla che il file venga creato:
php-fpm8.2 -t
systemctl reload php8.2-fpm
ls -l /var/log/php8.2-fpm.access.log
Lascia girare il log per un giorno intero, così da includere le ore di punta. Poi analizza il consumo di picco per quantili:
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 "Richieste: %d Mediana: %d kB p95: %d kB Massimo: %d kB\n", NR, v[int((NR+1)/2)], v[p], v[NR]
}'
La stessa analisi per il tempo di esecuzione, che ti servirà subito dopo per il secondo calcolo, prende la colonna precedente:
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 "Mediana: %.0f ms p95: %.0f ms Massimo: %.0f ms\n", v[int((NR+1)/2)], v[p], v[NR]
}'
Due limitazioni. Primo, le posizioni dei campi dipendono dalla riga di formato mostrata sopra, perché questa termina con tempo di esecuzione, memoria e quota di CPU. Chi modifica access.format deve adattarle. Secondo, %{kilo}M è la contabilità di memoria di PHP stesso e quindi un limite inferiore: non comprende il codice del programma, le estensioni e la memoria che la libreria C non restituisce subito dopo una richiesta. Per la formula vale perciò il PSS. Il log degli accessi serve a trovare gli script anomali, quelli che tengono un processo grande in modo permanente.
Dopo, disattiva di nuovo il log oppure predisponi per esso una rotazione. La regola fornita di serie copre solo il log degli errori, e un disco pieno produce sintomi che con PHP-FPM non hanno più nulla a che vedere: disco pieno: trovare e liberare spazio su un server Linux.
Il calcolo
I numeri sono due: un limite massimo che deriva dalla RAM e un fabbisogno che deriva dal carico. Il valore giusto è il più piccolo dei due.
Il limite massimo dalla RAM
pm.max_children = budget per PHP / PSS per processo di lavoro
Il budget non è tutta la RAM:
free -m
La colonna available tiene già conto della cache liberabile, ma non comprende la memoria che i tuoi processi di lavoro stanno occupando in quel momento. Il budget è quindi available più la somma PSS misurata, meno una riserva. Per la riserva funziona bene circa il 20% della memoria totale, con un minimo di 512 MB. La riserva assorbe ciò che in nessuna misura compare: un buffer pool del database che cresce, un backup, un upgrade dei pacchetti, un'importazione lanciata nel momento sbagliato.
| RAM totale | Occupata stabilmente | Riserva | Budget per PHP | PSS per processo | 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 |
Arrotonda sempre per difetto. Un processo in più non porta nulla, un processo di troppo può costare tutto.
Il fabbisogno dal carico
processi necessari = richieste al secondo × tempo medio di esecuzione in secondi
Le richieste al secondo le fornisce la pagina di stato di FPM: accepted conn diviso start since dà la media dall'ultimo avvio. Il tempo di esecuzione viene dall'analisi qui sopra, e serve il valore p95, non la mediana, altrimenti dimensioni per il pomeriggio tranquillo.
Un esempio: 40 richieste al secondo nel picco, tempo p95 di 300 millisecondi, fanno 12 richieste simultanee, e con un margine per le brevi impennate si arriva a circa 20-25. Se il limite massimo è 43, inserisci il valore del fabbisogno e lascia la memoria restante dove rende di più, cioè nella cache del database.
Se il fabbisogno risulta superiore al limite massimo, non inserirlo comunque. In quel caso o la RAM è troppo poca, o gli script sono troppo lenti, oppure passano per PHP richieste che potrebbero essere servite come file statico o da una cache. Tutte e tre le cause si possono risolvere, un valore troppo alto no: sposta soltanto il momento del guasto.
dynamic, ondemand o static
La modalità stabilisce quando i processi nascono e scompaiono. Il limite massimo pm.max_children vale in tutti e tre i casi.
| Modalità | Processi all'avvio | Comportamento | Memoria | Adatta a |
| dynamic | pm.start_servers | tiene pronti tra min_spare e max_spare processi inattivi, ne genera altri fino a max_children quando serve | oscilla con il carico | al caso normale: da uno a pochi pool, carico variabile |
| ondemand | nessuno | avvia un processo solo quando arriva una richiesta e lo termina dopo pm.process_idle_timeout | a riposo la più bassa | a molti pool su un solo server, a siti con poco traffico |
| static | pm.max_children | esattamente questo numero, in modo permanente, senza generare né terminare processi durante l'esercizio | costante al massimo | a un solo pool su hardware dedicato allo scopo, con carico uniforme |
dynamic è l'impostazione predefinita ed è quella giusta nel caso normale. Costa un po' di tempo di calcolo per generare i processi e richiede che i quattro valori siano coerenti fra loro.
ondemand a riposo fa risparmiare memoria in modo sensibile, quando su una macchina convivono una dozzina di pool per siti consultati di rado. Il prezzo è la prima richiesta dopo una fase di quiete, che deve aspettare la generazione del processo. pm.process_idle_timeout regola quanto sopravvive un processo inattivo (di serie dieci secondi) e agisce esclusivamente in questa modalità. Tieni presente inoltre che qui l'avviso di saturazione nomina max_children senza il prefisso pm.. Chi cerca la formulazione abituale non trova nulla.
static è onesta: quello che inserisci viene occupato subito e in modo permanente, in cambio sotto carico non ci sono più sorprese. Ha senso quando PHP è il consumatore principale della macchina. Se invece PHP condivide la RAM con un database, static toglie a quest'ultimo la possibilità di tenere temporaneamente più cache.
Indipendentemente dalla modalità vale la pena guardare pm.max_requests, di serie 0 e quindi senza limite. Un valore come 500 sostituisce ogni processo di lavoro dopo 500 richieste. Contro un consumo di memoria che cresce lentamente per colpa di un'estensione poco pulita è una misura efficace ed economica, perché l'opcache resta condiviso e non viene ricostruito. Non impostare però il valore su 20, altrimenti FPM passa il tempo a generare processi.
start_servers e i valori spare
Questi tre valori agiscono solo con pm = dynamic e decidono quanto in fretta FPM reagisce a un picco di carico:
pm.min_spare_servers: è il numero minimo di processi inattivi che FPM tiene pronti, il cuscinetto per le impennate. Troppo basso significa che ogni picco deve prima aspettare la generazione dei processi.pm.max_spare_servers: è il numero massimo di processi inattivi che FPM tollera. Senza questo limite, dopo un picco FPM terrebbe tutti i processi e con essi la loro memoria.pm.start_servers: è il numero di processi che esistono subito dopo l'avvio.
All'avvio FPM verifica quattro condizioni e rifiuta il servizio se una di esse viene violata: entrambi i valori spare devono essere maggiori di zero, nessuno dei due può superare pm.max_children, max_spare non può essere minore di min_spare e start_servers deve stare in mezzo. Se pm.start_servers manca del tutto, FPM calcola da sé e registra:
NOTICE: [pool www] pm.start_servers is not set. It's been set to 3.
La formula è min_spare + (max_spare - min_spare) / 2, cioè il punto medio tra i due valori spare. Come punto di partenza nella pratica funziona questo:
pm.max_children = 40
pm.start_servers = 10
pm.min_spare_servers = 6
pm.max_spare_servers = 16
Facile da trascurare: anche i processi inattivi occupano memoria. Il budget deve reggere pm.max_children, mentre il consumo di tutti i giorni corrisponde più o meno a max_spare più i processi attivi. Un max_spare alto tiene la memoria occupata anche alle tre di notte.
Il rapporto con lo swap
Viene la tentazione di includere lo swap nel calcolo. Non farlo. Un processo di lavoro i cui dati si trovano sul disco risponde più lentamente di ordini di grandezza e resta quindi occupato più a lungo. Il numero di richieste simultanee sale, FPM genera altri processi e questi spingono fuori altra memoria. Questa retroazione è il motivo per cui un server con la memoria sovrallocata non peggiora gradualmente, ma crolla nel giro di pochi minuti.
Quello che lo swap offre comunque è un cuscinetto per il caso di guasto. Senza di esso una sovrallocazione finisce di colpo con il kernel che termina un processo. Con lo swap ottieni prima un server lento e quindi una finestra di tempo per intervenire. La regola è questa: swap sì, ma pm.max_children si calcola esclusivamente sulla RAM reale, mai sulla somma di RAM e swap.
Se stai già scrivendo in swap lo vedi in due punti. free -m mostra l'occupazione, ma non dice nulla sull'attività, perché una pagina spostata una volta sola e mai più richiesta è innocua. Significative sono le colonne si e so, cioè le pagine lette dallo swap e scritte nello swap al secondo:
free -m
vmstat 1 5
Valori stabilmente sopra lo zero significano che il server sta lavorando contro il disco. Se il tuo kernel mette a disposizione l'indicatore di pressione, questo è ancora più diretto, perché non dice quanto è stato spostato in swap, ma per quanto tempo i processi hanno dovuto aspettare per quel motivo:
cat /proc/pressure/memory
Se il file non esiste, la funzione è disattivata nel kernel e ti resta vmstat. Come si crea uno swap, come lo si rende permanente e come si imposta vm.swappiness nel modo giusto è spiegato nell'articolo configurare lo swap ed evitare i crash per memoria esaurita.
Valore troppo alto: l'OOM killer al posto di una coda
Poniamo che tu inserisca 200, così l'avviso finalmente sparisce. A riposo non succede nulla, la pagina carica, sembra tutto risolto. Alla prossima ondata di traffico FPM genera davvero fino a 200 processi, ognuno cresce con le prime richieste fino alla sua dimensione reale, la memoria libera cala, il kernel butta via prima la cache dei file (per cui il database rallenta e le sue query durano di più), poi comincia a spostare pagine in swap, e infine entra in azione l'OOM killer.
Questo sceglie la vittima in base al consumo di memoria, e il singolo processo più grande su un server web non è un processo di lavoro PHP da 80 MB, ma il database con il suo buffer pool:
dmesg -T | grep -iE "out of memory|oom-kill"
journalctl -k --since "24 hours ago" | grep -i "out of memory"
Le due righe che contano hanno questo aspetto:
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
Qui chi provoca e chi subisce sono due processi diversi, ed è proprio questo a rendere sgradevole la ricerca dell'errore: nella casella di posta arriva un avviso su un database andato in crash, e nessuno pensa alla configurazione PHP dell'altro ieri. A seconda del database, tra parentesi compare mariadbd oppure mysqld.
Se invece a essere colpito è un processo di lavoro, FPM lo segnala da sé, e con un limite di memoria impostato la cosa compare anche nel 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.
La differenza tra i due quadri di guasto è il punto vero di questo articolo. Un pm.max_children troppo basso produce una coda: misurabile, documentata nel log, riconducibile a un valore preciso, e il server resta raggiungibile. Uno troppo alto produce un processo terminato in un punto che non hai scelto tu, in un servizio che in certi casi non riparte da solo. Il tempo di attesa è uno stato di esercizio, un evento OOM è un incidente.
Errori frequenti e soluzioni
WARNING: [pool www] server reached pm.max_children setting (5), consider raising it: in un dato momento tutti i processi di lavoro erano occupati. La riga compare una volta per fase di saturazione, quindi una sola riga può significare un'ora di carico pieno. Aumenta il valore solo dopo aver misurato il PSS e stabilito il budget.
WARNING: [pool www] server reached max_children setting (5), consider raising it: la stessa situazione con pm = ondemand, nella formulazione senza il prefisso pm..
ALERT: [pool www] pm.min_spare_servers and pm.max_spare_servers cannot be greater than pm.max_children, seguito da ERROR: failed to post process the configuration e ERROR: FPM initialization failed: è il caso più frequente quando si abbassa pm.max_children. Chi passa da 50 a 8 e lascia pm.max_spare_servers = 20 si ritrova poi con un servizio che non parte più. I quattro valori vanno cambiati insieme.
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 si trova fuori dall'intervallo. Correggi il valore oppure rimuovi la riga, così FPM calcola da sé.
PHP Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 20480 bytes): questo è memory_limit e non ha nulla a che vedere con pm.max_children. Un singolo script ha richiesto più di quanto PHP consenta per richiesta. Un pm.max_children più alto non cambia nulla, e al contrario un memory_limit più basso non riduce il fabbisogno di memoria di un processo di lavoro, interrompe soltanto gli script prima. Per la pianificazione vale comunque questo: memory_limit è il tetto massimo di ciò che un processo può richiedere.
connect() to unix:/run/php/php8.2-fpm.sock failed (11: Resource temporarily unavailable) while connecting to upstream nel log degli errori di nginx: tutti i processi occupati e in più la coda di accettazione piena. Un listen.backlog più alto sposta solo il problema, la causa sta nel numero di processi o nel tempo di esecuzione degli script.
WARNING: [pool www] child 1234 exited on signal 9 (SIGKILL) after 3612.472183 seconds from start: il processo è stato terminato con forza dall'esterno, di regola dall'OOM killer. Qui il valore è troppo alto, non troppo basso. Controprova nel log del kernel.
"Ho aumentato il valore e non cambia nulla." Quasi sempre è stato modificato il file sbagliato: una seconda versione di PHP sotto /etc/php/, un secondo pool oppure una copia in pool.d/ che non viene nemmeno letta. Conta quale socket contatta nginx e quale pool è in ascolto su di esso:
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"
"Dopo il reload il sito è sparito." Un reload riavvia FPM con la nuova configurazione e, se questa non è valida, il processo principale termina. Ripristina la copia da /root/backups e abituati a eseguire php-fpm8.2 -t prima di ogni reload.
Come riconosci che il valore è giusto
"L'avviso è sparito" non è una prova. Anche con il valore 500 sparisce, fino al primo evento OOM. Solide sono cinque verifiche.
Primo, la pagina di stato di FPM. Inserisci pm.status_path = /status nel file del pool, verifica e ricarica, poi interroga il socket direttamente, aggirando del tutto nginx. Così la pagina non è raggiungibile dalla rete:
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
La risposta sta in quattro righe dell'output:
| Campo | Significato | Valore obiettivo |
| max children reached | quante volte il limite massimo è stato raggiunto dall'avvio | 0 |
| max listen queue | coda più lunga osservata sul socket | 0 |
| max active processes | numero massimo di processi attivi contemporaneamente | nettamente sotto pm.max_children |
| slow requests | richieste oltre request_slowlog_timeout | possibilmente 0 |
Il dato diventa significativo solo dopo una settimana intera, con tutte le ore di punta. Se poi max active processes resta a 12 mentre pm.max_children è a 43, hai margine e puoi impiegare la riserva altrove.
Secondo, la memoria sotto carico, non di notte ma nel picco. available deve restare nettamente sopra lo zero, le colonne si e so devono stare a zero:
free -m
vmstat 1 5
Terzo: nessun evento OOM. Questa verifica è la più importante, perché esclude il quadro di guasto peggiore. Un output vuoto è il risultato desiderato:
journalctl -k --since "7 days ago" | grep -i "out of memory"
Quarto, la somma su tutti i pool. Su un server con più siti non conta il singolo valore, ma la somma, moltiplicata per il PSS di ogni processo. Deve rientrare nel budget anche quando tutti i pool si trovano sotto carico nello stesso momento:
php-fpm8.2 -tt 2>&1 | grep "pm.max_children"
Quinto: il valore sopravvive a un riavvio. È il punto che si salta più spesso. Un valore scritto in un file che non viene nemmeno letto salta fuori solo al riavvio successivo, e quello capita di rado proprio mentre stai guardando:
systemctl is-enabled php8.2-fpm
systemctl restart php8.2-fpm
php-fpm8.2 -tt 2>&1 | grep "pm.max_children"
Riavvia poi il server una volta in modo controllato, finché sei ancora lì a guardare. Sui server root KVM e sui server dedicati di KernelHost questo riavvio lo lanci dall'area clienti e segui l'avvio dalla console VNC, anche quando il servizio web non risponde ancora. I server si trovano nel datacenter maincubes di Francoforte sul Meno.
Checklist rapida
- Copia del file del pool in
/root/backups, accesso tramite la console VNC provato almeno una volta. - Misurare il PSS per processo di lavoro sotto carico reale, non dopo un riavvio, e non fare i conti con l'RSS.
- Stabilire il budget:
availablepiù la somma PSS corrente, meno una riserva scelta consapevolmente. - Calcolare il limite massimo, confrontarlo con il fabbisogno (richieste al secondo per tempo p95), prendere il valore più piccolo, arrotondare per difetto.
- Scegliere la modalità e adattare i valori spare insieme a
pm.max_children. php-fpm8.2 -t, poisystemctl reload, poiphp-fpm8.2 -ttcome prova che il valore è stato caricato.- Una settimana dopo, controllare
max children reached,max listen queuee il log del kernel.
Domande frequenti
Come calcolo correttamente pm.max_children?
Perché un valore troppo alto è più pericoloso di uno troppo basso?
Perché devo fare i conti con il PSS e non con l'RSS?
Quando scelgo dynamic, quando ondemand e quando static?
Posso includere lo swap nel calcolo?
Ho aumentato pm.max_children e non cambia nulla. Da che cosa dipende?
Dopo aver abbassato pm.max_children, PHP-FPM non parte più. Che cosa è successo?
memory_limit ha qualcosa a che vedere con pm.max_children?
2026 KernelHost GmbH. Tutti i diritti riservati. Questa guida è protetta dal diritto d'autore. La ripubblicazione su altri siti web, anche parziale o in forma modificata, non è consentita senza il nostro consenso scritto. Le citazioni con indicazione della fonte e un link sono le benvenute.

