Configurare swap file per risolvere saturazioni RAM su VPS gestite senza supporto tecnico: guida pratica per Debian e Ubuntu
Qualche mese fa ho preso in carico un VPS per una PMI piemontese del settore forniture per ufficio: un e-commerce Laravel su una macchina cloud con 3 vCPU, 4 GB di RAM e 80 GB di storage NVMe. Il sintomo era preciso: ogni notte, in una finestra di pochi minuti tra le 2 e le 3, il gestionale diventava irraggiungibile. Il titolare lo scopriva la mattina dai log di un monitoring esterno e dall'assenza degli ordini notturni inviati in automatico dai suoi clienti. La diagnosi con dmesg ha mostrato il pattern senza ambiguità:
Out of memory: Killed process 1847 (mysqld) total-vm:2847632kB, anon-rss:1982104kB
oom_reaper: reaped process 1847 (mysqld), now anon-rss:0kBIl kernel aveva attivato l'OOM (Out Of Memory) killer e aveva terminato MySQL, cioè il processo che in quel momento consumava più memoria, perché la RAM fisica era esaurita al 100% e non c'era swap configurato. Il VPS, come la maggior parte delle macchine cloud, era stato provisioned senza swap: zero byte di swap file e nessuna swap partition. Quando MySQL tentava di allocare memoria per l'import notturno dei listini, un job che caricava in RAM circa 800 MB di dati, la memoria superava il limite fisico e il kernel non aveva alternative all'uccidere il processo più affamato.
Lo swap non è un sostituto della RAM, è una rete di sicurezza. Configurarlo correttamente è uno dei primi interventi che faccio su ogni macchina che prendo in gestione, e in questa guida ti spiego come dimensionarlo, come tarare lo swappiness, perché su VPS con NVMe ZRAM è spesso la scelta migliore rispetto allo swap tradizionale su disco, e come blindare i processi critici contro l'OOM killer in modo che la protezione sopravviva a un riavvio.
Stai cercando un Consulente Informatico esperto per ottimizzare le performance del tuo VPS? Nel mio profilo professionale trovi l'esperienza concreta su tuning del kernel Linux, gestione della memoria e ottimizzazione di stack LEMP per PMI.
Configurare lo swap su un VPS Linux: la sequenza corretta in cinque comandi
Se i tuoi processi critici vengono terminati durante i picchi di carico e la macchina non ha swap, la base si risolve in pochi comandi. Su Debian o Ubuntu crei un file di swap con fallocate, lo proteggi con chmod 600, lo formatti con mkswap, lo attivi con swapon e abbassi vm.swappiness a 10. Tutto qui sotto è la versione ragionata, con i dettagli che fanno la differenza tra una configurazione che regge e una che non sopravvive al primo reboot.
Perché tutto questo serve davvero? Quando un processo richiede memoria e non ce n'è più di disponibile, il kernel ha due strade: se esiste uno swap, sposta su disco le pagine meno usate liberando RAM; se non esiste, attiva l'OOM killer e termina un processo. La vittima viene scelta in base a un punteggio interno (oom_score) che pesa quanta memoria usa il processo e quanto a lungo è in esecuzione. Su un VPS LEMP il bersaglio è quasi sempre MySQL o PHP-FPM, cioè proprio i processi da cui dipende l'applicazione.
L'OOM kill di MySQL è particolarmente distruttivo. MySQL non gestisce bene la terminazione forzata: le transazioni InnoDB in corso si perdono, il buffer pool non viene flushato su disco e al riavvio il motore deve eseguire un crash recovery che, su database grandi, richiede minuti o decine di minuti. Nei casi peggiori il recovery stesso fallisce e serve intervento manuale. Ho descritto le problematiche di performance di MySQL, inclusa la configurazione del buffer pool che riduce la pressione sulla RAM, nell'articolo sulla diagnosi di connessioni MySQL lente su VPS.
Creare e configurare uno swap file su Debian e Ubuntu
La creazione di uno swap file è diretta. Non uso mai dd per generarlo: fallocate è istantaneo perché alloca lo spazio senza scrivere fisicamente gli zeri.
# 1. Creare il file di swap (2 GB per un VPS con 4 GB di RAM)
fallocate -l 2G /swapfile
# 2. Restringere i permessi: solo root puo' leggere e scrivere
chmod 600 /swapfile
# 3. Formattare lo spazio come area di swap
mkswap /swapfile
# 4. Attivare lo swap nella sessione corrente
swapon /swapfile
# 5. Verificare che sia attivo
swapon --show
free -hA questo punto lo swap è attivo, ma solo fino al prossimo riavvio. Per renderlo permanente devi aggiungere una riga in /etc/fstab:
echo '/swapfile none swap sw 0 0' >> /etc/fstabQui c'è il dettaglio che molte guide sbagliano. Per verificare che la riga di fstab sia corretta e che lo swap si riattivi davvero da quel file, non si usa mount -a: quel comando rimonta i filesystem, ma non tocca le aree di swap. Lo swap si attiva con swapon, quindi il test giusto è disabilitare e riattivare lo swap leggendo proprio da fstab:
# systemd genera le swap-unit da fstab: dopo aver modificato il file, ricaricalo
systemctl daemon-reload
# Disattivare tutto lo swap, poi riattivarlo SOLO da fstab
swapoff -a
swapon -a
# Confermare che lo swap sia tornato attivo leggendo dalla configurazione persistente
swapon --show
free -hUna riga errata in
/etc/fstabpuò impedire il boot della macchina. Il giroswapoff -a && swapon -a && swapon --showverifica che la voce di swap sia corretta senza riavviare: seswapon --showriporta/swapfile, la configurazione persistente è valida.
Quanto swap configurare su un VPS?
La vecchia regola "swap = 2× RAM" è obsoleta da un decennio. Su un VPS cloud la regola che applico è dimensionata sulla RAM disponibile:
- VPS con 1-2 GB di RAM: swap 2 GB. La RAM è molto limitata, serve un margine ampio.
- VPS con 4-8 GB di RAM: swap 2 GB. Sufficiente come buffer per i picchi temporanei.
- VPS con 16+ GB di RAM: swap 1-2 GB. Solo come paracadute estremo, la RAM dovrebbe bastare.
Lo swap non è performance, è sopravvivenza. Se il tuo VPS usa lo swap in modo costante, il vero problema è che hai bisogno di più RAM, non di più swap. Lo swap su disco, anche su NVMe, è ordini di grandezza più lento della RAM (parliamo di centinaia di microsecondi contro decine di nanosecondi per accesso), e una macchina che swappa attivamente diventa lentissima.
Swappiness: quanto aggressivamente il kernel usa lo swap
Il parametro vm.swappiness controlla quanto il kernel è propenso a spostare pagine dalla RAM verso lo swap. Il default è 60, troppo aggressivo per un server applicativo. Con un valore così alto il kernel inizia a swappare ben prima che la RAM sia realmente sotto pressione, e su un VPS con PHP e MySQL questo significa swapping anche in condizioni di carico normale, con un degrado di latenza del tutto inutile.
# Verificare il valore attuale
cat /proc/sys/vm/swappiness
# Impostarlo a 10: swap solo quando strettamente necessario
sysctl vm.swappiness=10
# Renderlo permanente
echo 'vm.swappiness=10' >> /etc/sysctl.d/99-swap.conf
# Opzionale: ridurre anche la pressione sulla cache dei metadati del filesystem
echo 'vm.vfs_cache_pressure=50' >> /etc/sysctl.d/99-swap.conf
sysctl -p /etc/sysctl.d/99-swap.confCon vm.swappiness=10 il kernel ricorre allo swap solo quando la RAM è quasi esaurita: esattamente il comportamento che vuoi su un server. La RAM serve alle applicazioni, lo swap è il paracadute che si apre solo quando serve davvero.
ZRAM: l'alternativa compressa che su VPS con NVMe è spesso la scelta migliore
ZRAM è un modulo del kernel Linux che crea un dispositivo a blocchi compresso direttamente in memoria. Invece di scrivere le pagine swappate su disco, le comprime e le tiene in RAM. Con un algoritmo veloce come LZ4, o con ZSTD che comprime meglio, un blocco ZRAM da 1 GB di RAM fisica può contenere anche 3-4 GB di dati compressi: è come avere più RAM senza toccare lo storage.
Su un VPS con NVMe la differenza tra swap su disco e ZRAM è misurabile ma non sempre drammatica, perché l'NVMe è già molto veloce. Su VPS con storage SATA o network storage, tipici dei piani entry-level di alcuni provider, ZRAM diventa invece nettamente più veloce dello swap su disco perché evita del tutto l'I/O. La configurazione su Debian e Ubuntu è semplice grazie al pacchetto zram-tools:
# Installare zram-tools
apt install -y zram-tools
# Configurare ZRAM: 50% della RAM, algoritmo LZ4, priorita' alta
cat > /etc/default/zramswap << 'EOF'
ALGO=lz4
PERCENT=50
PRIORITY=100
EOF
# Riavviare il servizio per applicare la configurazione
systemctl restart zramswap
# Verificare
swapon --show
# NAME TYPE SIZE USED PRIO
# /dev/zram0 partition 2G 0B 100Il PRIORITY=100 è importante: se hai sia ZRAM sia uno swap file su disco, il kernel usa per primo quello con priorità più alta. Con ZRAM a 100 e swap file a priorità negativa (il default), la macchina comprime in RAM finché può e spilla su disco solo quando ZRAM è saturo.
C'è però un trade-off che va dichiarato, perché è la ragione per cui ZRAM non è una scelta automatica. La compressione e la decompressione delle pagine costano CPU. LZ4 è progettato per essere velocissimo e su CPU moderne l'overhead è quasi trascurabile, ma su un VPS piccolo da 1 o 2 vCPU quel lavoro di compressione compete con i cicli che servono a PHP-FPM e a MySQL. Sotto pressione di memoria sostenuta, su una macchina già a corto di CPU, ZRAM può aiutare a evitare l'OOM ma a costo di latenza aggiuntiva sull'applicazione. ZSTD comprime di più ma costa ancora più CPU di LZ4: su VPS a basso numero di core preferisco quasi sempre LZ4.
Su un VPS minuscolo da 1 GB di RAM e 1 vCPU, riservare metà della RAM a ZRAM è eccessivo: lascia troppo poco respiro alle applicazioni e carica la singola CPU di lavoro di compressione. Su queste macchine scendo a
PERCENT=25oPERCENT=30e tengoALGO=lz4, così ZRAM resta un cuscinetto utile contro i picchi senza diventare esso stesso un collo di bottiglia.
La mia configurazione di partenza su un VPS con qualche GB di RAM e CPU sufficiente è: ZRAM al 50% della RAM come swap primario, più uno swap file da 1 GB su disco come fallback estremo. Questo copre sia i picchi temporanei (gestiti da ZRAM senza I/O) sia gli scenari catastrofici in cui anche ZRAM si riempie. Su un 1 GB scarno, invece, il profilo conservativo da PERCENT=25 è la scelta più sicura.
Proteggere MySQL e PHP-FPM dall'OOM killer
Anche con lo swap configurato, in situazioni estreme il kernel può comunque attivare l'OOM killer. La difesa aggiuntiva è l'oom_score_adj, un parametro per-processo che dice al kernel quali processi sono prioritari e non devono essere uccisi per primi. Una correzione temporanea, valida nella sessione corrente, si applica scrivendo direttamente nel /proc del processo. Qui c'è una trappola che vedo spesso: il nome del binario di PHP-FPM cambia con la versione (php-fpm8.2, php-fpm8.3, php-fpm8.4), quindi un pgrep -x php-fpm8.2 hardcoded fallisce silenziosamente sul server con una PHP diversa. Più robusto è agganciare il master process per il suo command line, identico a prescindere dalla minor:
# Proteggere MySQL dall'OOM killer (valore -900 = quasi immune)
echo -900 > /proc/$(pgrep -x mysqld)/oom_score_adj
# Proteggere il master process di PHP-FPM, indipendentemente dalla versione installata
echo -500 > /proc/$(pgrep -fx 'php-fpm: master process')/oom_score_adjQuesti valori però vengono persi al primo riavvio del servizio o della macchina. La protezione permanente si imposta con un override di systemd, ed è qui che molte guide si fermano un passo prima della fine. Creare il drop-in e fare daemon-reload non basta: l'override viene applicato solo quando il servizio (ri)parte, perché OOMScoreAdjust è una proprietà che systemd assegna al processo all'avvio. Su un MySQL già in esecuzione, il nuovo valore non si applica finché non riavvii il servizio.
# Creare il drop-in di override per il servizio MySQL
mkdir -p /etc/systemd/system/mysql.service.d
cat > /etc/systemd/system/mysql.service.d/oom.conf << 'EOF'
[Service]
OOMScoreAdjust=-900
EOF
# Ricaricare la configurazione di systemd
systemctl daemon-reload
# Riavviare il servizio: SENZA questo passo l'override NON si applica al processo gia' attivo
systemctl restart mysql
# Verificare che systemd abbia recepito il valore configurato
systemctl show mysql -p OOMScoreAdjust
# Output atteso: OOMScoreAdjust=-900
# Conferma a livello di kernel sul processo in esecuzione
cat /proc/$(pgrep -x mysqld)/oom_score_adj
# Output atteso: -900Il valore va da -1000 (mai uccidere) a +1000 (uccidere per primo). Impostando MySQL a -900, il kernel sceglie di terminare qualunque altro processo prima di toccare il database: è quasi sempre il comportamento desiderato su un server applicativo, perché un worker PHP terminato viene rigenerato da FPM in millisecondi, mentre un MySQL terminato si porta dietro il crash recovery. Proteggere tutto, però, vanifica l'OOM killer: si blindano i due o tre processi davvero critici, non l'intero sistema. Su Ubuntu 24.04 e successive è inoltre presente systemd-oomd, un componente userspace che interviene prima del kernel basandosi sulla pressione di memoria misurata via PSI: convive con OOMScoreAdjust, ma è bene sapere che esiste quando si tara la protezione.
Monitoring della pressione sulla memoria: sapere quando lo swap non basta
Il segnale più affidabile per capire se il server è sotto pressione di memoria non è free -h, è PSI (Pressure Stall Information), disponibile dai kernel 4.20 in poi (i kernel di Debian e Ubuntu in versione di supporto attuale lo includono di serie):
# Pressione sulla memoria in tempo reale
cat /proc/pressure/memory
# some avg10=0.00 avg60=0.00 avg300=0.00 total=0
# full avg10=0.00 avg60=0.00 avg300=0.00 total=0
# "some" > 0 per periodi prolungati: il sistema rallenta per mancanza di memoria
# "full" > 0: alcuni processi sono completamente bloccati in attesa di memoriaLo script di monitoring che installo controlla sia lo swap attivo sia la PSI, e manda un alert quando una delle due soglie viene superata:
#!/usr/bin/env bash
# /root/scripts/memory-pressure-check.sh - eseguito da cron ogni 5 minuti
set -euo pipefail
SWAP_USED=$(free -m | awk '/Swap:/{print $3}')
SWAP_TOTAL=$(free -m | awk '/Swap:/{print $2}')
PSI_SOME=$(awk -F= '/some/{print $2}' /proc/pressure/memory | cut -d' ' -f1)
# Alert se lo swap usato supera il 50% della capacita'
if [ "${SWAP_TOTAL}" -gt 0 ] && [ "$((SWAP_USED * 100 / SWAP_TOTAL))" -gt 50 ]; then
echo "SWAP al $((SWAP_USED * 100 / SWAP_TOTAL))% su $(hostname), PSI some=${PSI_SOME}" | \
mail -s "MEMORY ALERT" [email protected]
fiUn VPS che usa stabilmente più del 50% dello swap ha bisogno di più RAM: lo swap sta tamponando un deficit strutturale. La sequenza corretta di intervento è in ordine preciso: prima configuri lo swap come rete di sicurezza (questo articolo), poi ottimizzi l'applicazione per ridurre il consumo di memoria (buffer pool di MySQL, numero di worker PHP-FPM, query che caricano troppi dati in RAM), e solo se il consumo resta alto dopo l'ottimizzazione valuti l'upgrade della RAM. Ho descritto l'intero workflow di ottimizzazione delle performance PHP e MySQL su VPS Hetzner e OVH, incluso il dimensionamento del buffer pool InnoDB e il calcolo dei worker FPM, in un articolo dedicato.
Su questo profilo di intervento la scelta del provider conta meno della disciplina di configurazione, ma vale la pena ricordarla.
Per un'infrastruttura cloud europea self-managed con buon rapporto prezzo/prestazioni e conformità GDPR, Hetzner resta un riferimento solido (datacenter UE). Per le PMI italiane che vogliono un'infrastruttura gestita, NIS-compliant e con il dato che non lascia l'Italia, la prima scelta è RHX, con datacenter a Milano e Padova e gestione sistemistica inclusa. Se invece stai scegliendo la macchina, ne ho parlato in dettaglio nella guida strategica alla scelta del provider VPS.
Il risultato sul VPS della PMI piemontese
Dopo la configurazione di 2 GB di swap file con swappiness a 10, e con la protezione systemd attiva su MySQL, l'OOM killer non si è più presentato. L'import notturno dei listini ora "spilla" qualche centinaio di MB nello swap durante il picco (visibile con vmstat 1 mentre il job gira) ma completa correttamente senza uccidere il database. Il response time del sito durante l'import resta leggermente più alto, perché una parte della memoria è temporaneamente in swap, ma il servizio non va più offline.
La soluzione definitiva, che ho implementato poco dopo, è stata ridurre alla radice il consumo di memoria dell'import. La query caricava l'intero catalogo (centinaia di migliaia di prodotti) in un array PHP con un Product::all(); l'ho riscritta usando Product::cursor(), che processa un record alla volta sfruttando un generator. Il consumo di RAM del job è crollato da circa 800 MB a poche decine di MB, e lo swap non viene più toccato nemmeno al picco. Lo swap, però, resta configurato, perché il prossimo picco potrebbe arrivare da un punto diverso, e la rete di sicurezza deve esserci sempre.
Domande frequenti sullo swap su VPS
Lo swap rallenta il server? Solo se viene usato in modo sostenuto. Configurato come rete di sicurezza con vm.swappiness=10, resta quasi sempre inattivo e non incide sulle prestazioni in condizioni normali. Diventa un problema solo se la macchina swappa in continuazione, e in quel caso il rallentamento è il sintomo, non la causa: il problema vero è la RAM insufficiente.
Meglio swap file o swap partition su un VPS? Lo swap file. Su una macchina cloud non riprovisioni il partizionamento del disco per aggiungere swap: il file è ridimensionabile, si crea in pochi secondi con fallocate e non ha svantaggi prestazionali apprezzabili sui kernel attuali rispetto a una partizione dedicata.
Posso usare ZRAM e uno swap file insieme? Sì, ed è la mia configurazione standard su macchine con CPU sufficiente: ZRAM a priorità alta come primo livello (compressione in RAM, niente I/O) e uno swap file su disco a priorità più bassa come fallback estremo. Su VPS minuscoli da 1 GB e 1 vCPU, invece, conviene tenere ZRAM più piccolo (PERCENT=25-30) per non sottrarre CPU e RAM all'applicazione.
Perché l'OOM killer colpisce sempre MySQL? Perché l'OOM killer sceglie tipicamente il processo con il maggior consumo di memoria, e su uno stack LEMP è quasi sempre il database con il suo buffer pool. È esattamente il motivo per cui conviene proteggerlo esplicitamente con un OOMScoreAdjust negativo via systemd.
Se il tuo VPS non ha swap e i processi critici vengono terminati senza preavviso durante i picchi di carico, la base si risolve davvero in pochi minuti: cinque comandi, la riga in fstab, e l'override systemd verificato con systemctl show. Se non sai da dove partire, oppure vuoi una configurazione tarata sul tuo carico di lavoro reale (con ZRAM dimensionato sulla CPU disponibile e i processi critici protetti correttamente), contattami per una consulenza diretta: tra la diagnosi del pattern di OOM e la prima contromisura passano ore, non settimane.