Server Debian e Ubuntu in emergenza: checklist operativa rapida per VPS
Qualche mese fa, in un pomeriggio di metà settimana, mi è arrivata una chiamata dal titolare di una PMI marchigiana che gestisce un gestionale web custom in PHP 8.2 per coordinare la logistica di quattro magazzini. Il VPS era un Hetzner CX31 (4 vCPU, 8 GB di RAM, 80 GB NVMe) con Debian 12 Bookworm, Nginx, PHP-FPM e MySQL 8.0. Il sito era irraggiungibile da circa venti minuti, gli operatori di magazzino non potevano più processare le spedizioni, e l'unica informazione disponibile era "non va più niente". Nessun messaggio di errore, nessun monitoring attivo, nessun tecnico interno. Solo un VPS che aveva smesso di rispondere.
In poco più di mezz'ora ho identificato il problema (disco pieno al 100% per un binary log di MySQL non ruotato da mesi, cresciuto fino a decine di gigabyte), liberato spazio, riavviato i servizi e ripristinato l'operatività. Ho potuto farlo così rapidamente perché uso sempre la stessa checklist, sette fasi, stesso ordine, stessi comandi, costruita e affinata su una quindicina di emergenze reali. In questo articolo ti consegno quella checklist nella sua forma operativa: i comandi esatti, la spiegazione del perché ogni passaggio sta in quell'ordine e non in un altro, e i casi concreti in cui ciascuna fase mi ha risparmiato ore di debug. Non è un elenco teorico: è il runbook che apro davvero quando il telefono squilla e un business è fermo.
Stai cercando un Consulente Informatico esperto per gestire emergenze sulla tua infrastruttura VPS? Nel mio profilo professionale trovi l'esperienza concreta su Hetzner, OVH, Contabo, Digital Ocean e Aruba, con hardening Linux e recupero di server unmanaged in produzione.
Perché serve una checklist strutturata e non andare a intuito?
Perché sotto pressione il cervello salta i passaggi diagnostici e va dritto all'azione, e l'azione cieca su un server in crisi quasi sempre peggiora le cose. Quando un server è giù e il business è fermo, l'istinto spinge a reagire: riavviare tutto, cancellare file a caso, disinstallare pacchetti. Sono tutte azioni che ho visto fare, e tutte hanno aggravato la situazione. Un riavvio cieco su un server con disco pieno non risolve nulla e può corrompere il database a metà scrittura. Cancellare file senza sapere cosa sono rimuove dati di produzione. Disinstallare pacchetti a caldo rompe dipendenze e ti lascia con un sistema in uno stato peggiore di quello di partenza.
La checklist che uso è ancorata allo USE method (Utilization, Saturation, Errors) di Brendan Gregg, un framework sistematico per la diagnosi delle performance su Linux che esamina ogni risorsa (CPU, memoria, disco, rete) lungo tre dimensioni: quanto è utilizzata, quanto è satura, e se sta generando errori. L'ho adattato al contesto specifico delle emergenze VPS su Debian e Ubuntu, dove per esperienza la grande maggioranza dei problemi ricade in quattro categorie: disco pieno, servizio crashato, load anomalo, compromissione di sicurezza. Avere un ordine fisso significa che, qualunque sia lo stato emotivo del momento, i primi sessanta secondi producono sempre lo stesso quadro diagnostico affidabile.
La regola che non violo mai: prima si misura, poi si tocca. Ogni comando della Fase 1 è di sola lettura. Nessuna azione distruttiva prima di avere un quadro completo.
Fase 1: il triage in 60 secondi, capire cosa sta succedendo
Il primo minuto serve a ottenere un quadro generale senza modificare nulla. Se riesci a connetterti via SSH, esegui questi comandi in sequenza. Se SSH non risponde, usa la console del pannello del provider (Hetzner Cloud Console, OVH KVM/IPMI, Digital Ocean Recovery Console): la console grafica resta raggiungibile anche quando il networking applicativo è morto, ed è spesso l'unico modo di entrare su un server saturo.
# 1. Da quanto è su la macchina e qual è il carico
uptime
# 2. Errori kernel recenti (OOM killer, disk error, segfault)
dmesg -T | tail -30
# 3. Uso disco per ogni filesystem montato
df -hT
# 4. Inode disponibili (un filesystem può essere "pieno" senza byte usati)
df -i
# 5. Uso memoria reale (l'ultima colonna "available" è quella che conta)
free -h
# 6. Servizi in stato di errore
systemctl --failedQuesti comandi ti danno, in meno di dieci secondi, le informazioni per decidere quale fase affrontare per prima. L'output di uptime ti dice da quanto il server è acceso e se il load average è anomalo (su un VPS a 4 vCPU, un load average stabile sopra 8 è da prendere sul serio). Il dmesg rivela se il kernel ha dovuto uccidere processi per mancanza di memoria (l'OOM killer) o se ci sono errori del disco virtuale. Il df -hT mostra immediatamente se un filesystem è al 100%. Il df -i è il complemento spesso dimenticato: un filesystem può esaurire gli inode (tipicamente per milioni di file minuscoli di sessione o di cache) restando con spazio in byte disponibile, e in quel caso df -hT ti inganna mostrando spazio libero mentre il sistema non riesce a creare un solo file nuovo. Il free -h ti dice se la RAM è davvero esaurita, e i servizi failed da systemctl ti indicano cosa è crashato.
Nel caso della PMI marchigiana, df -hT ha mostrato il problema all'istante: /dev/sda1 al 100%, zero byte disponibili. Da quel momento sapevo esattamente dove guardare, e l'intera diagnosi è diventata una questione di minuti.
Fase 2: disco pieno, il problema più frequente e più sottovalutato
Il disco pieno è la causa singola più ricorrente delle emergenze VPS che gestisco. Quando il filesystem raggiunge il 100%, tutto collassa a cascata: MySQL non può scrivere nel binlog e si ferma, PHP-FPM non può scrivere i file di sessione e restituisce 500, Nginx non può scrivere i log e rifiuta connessioni. La diagnosi è rapida, la risoluzione richiede precisione perché qui è facile fare danni.
# Le directory più grandi partendo dalla root (-x resta sul filesystem corrente)
du -xsh /* 2>/dev/null | sort -rh | head -10
# Tipicamente il colpevole è /var: scendere nel dettaglio
du -xsh /var/* 2>/dev/null | sort -rh | head -10
# Se è /var/log, individuare il file specifico
du -sh /var/log/* 2>/dev/null | sort -rh | head -5
# File cancellati ma ancora aperti da un processo (occupano spazio, invisibili a du)
sudo lsof +L1 | sort -k7 -rn | head -10L'ultimo comando è fondamentale e spesso ignorato. Se cancelli un file di log mentre il processo che lo scrive è ancora attivo, il file scompare dalla directory ma lo spazio non viene liberato finché il processo non chiude il file descriptor. È il classico paradosso "df dice pieno, du non trova niente". Il sudo qui non è opzionale: senza, lsof mostra solo i file aperti dal tuo utente e ti perdi proprio i demoni di sistema che di solito sono i colpevoli. Per recuperare lo spazio hai due strade: riavviare il servizio che teneva aperto il file, oppure, se non puoi riavviarlo, svuotarlo attraverso il suo descriptor con truncate -s 0 /proc/<pid>/fd/<fd>. Questa seconda tecnica è sicura solo su semplici file di log: non farla mai su un write-ahead log o su un file di crash recovery di un database, o corrompi i dati.
Nel caso marchigiano il colpevole era una serie di /var/lib/mysql/mysql-bin.*, i binary log di MySQL che nessuno aveva configurato per ruotare. La tentazione è cancellarli con rm: è esattamente l'errore da non fare. MySQL mantiene un manifest mysql-bin.index che elenca i binlog esistenti; un rm lascia l'indice che punta a file inesistenti e al riavvio successivo il server si rifiuta di ruotare o crasha. La via supportata è PURGE BINARY LOGS, che aggiorna file e indice in modo atomico:
# Verificare quali binlog esistono e quanto pesano
ls -lh /var/lib/mysql/mysql-bin.*
# In MySQL: rimuovere i binlog più vecchi di 3 giorni (via supportata, mai rm)
mysql -u root -p -e "PURGE BINARY LOGS BEFORE DATE_SUB(NOW(), INTERVAL 3 DAY);"
# Impostare la retention automatica per il futuro (3 giorni = 259200 secondi)
mysql -u root -p -e "SET GLOBAL binlog_expire_logs_seconds = 259200;"Da MySQL 8.0, binlog_expire_logs_seconds ha sostituito il vecchio expire_logs_days con granularità al secondo; il default è 30 giorni e la rimozione automatica scatta all'avvio e a ogni flush del binlog, come documentato nel reference manual ufficiale di MySQL. Per renderlo persistente, la stessa direttiva va nel file di configurazione sotto [mysqld], non solo impostata a runtime, altrimenti al primo riavvio torna al default. I log applicativi non ruotati sono il killer silenzioso dei VPS unmanaged: la stessa disciplina di logrotate che applichi ai log di Nginx e PHP va estesa a ogni file che cresce in produzione, dal laravel.log ai binlog di MySQL.
Fase 3: servizi che non ripartono, diagnosi sistematica
Quando un servizio è down, il riflesso istintivo è systemctl restart. È sbagliato come prima mossa: prima devi capire perché si è fermato, altrimenti il restart fallisce o il servizio crasha di nuovo dopo pochi minuti, e tu hai solo perso tempo.
# Stato dettagliato del servizio (include le ultime righe di log)
systemctl status nginx --no-pager -l
systemctl status php8.2-fpm --no-pager -l
systemctl status mysql --no-pager -l
# Log recenti del servizio specifico
journalctl -u nginx --since "1 hour ago" --no-pager | tail -50
journalctl -u mysql --since "1 hour ago" --no-pager | tail -50
# Verifica della configurazione PRIMA di riavviare
nginx -t # Nginx
php-fpm8.2 -t # PHP-FPM
mysqld --validate-config # MySQL 8.0.16+Il nginx -t e il php-fpm8.2 -t sono passaggi critici: se la configurazione è rotta (un typo in un virtual host, un pool FPM con un path errato), il restart fallisce e ti ritrovi con un servizio né acceso né spento. Su MySQL 8.0.16 e successivi esiste l'equivalente mysqld --validate-config, che fa il parsing del file di configurazione ed esce senza avviare il server: exit code 0 se è tutto a posto, 1 e messaggio diagnostico se trova un errore (il dettaglio è nella documentazione di server configuration validation). È utilissimo dopo un aggiornamento, quando una direttiva deprecata può impedire l'avvio. Verificare la configurazione prima del restart è una regola che non violo mai.
Un pattern che vedo spesso su VPS con poca RAM (2-4 GB): MySQL viene ucciso dall'OOM killer perché il buffer pool InnoDB è dimensionato per più memoria di quanta ne abbia la macchina. Il kernel uccide mysqld, PHP non riesce più a connettersi al database e restituisce 500, mentre Nginx è perfettamente funzionante e serve solo pagine di errore. Il titolare vede "il sito non va" e pensa a Nginx; la causa reale è in dmesg:
# Cercare l'OOM killer nel kernel log
dmesg -T | grep -iE "out of memory|oom-kill|killed process"
# Se MySQL è stato killato, ridurre il buffer pool
# In /etc/mysql/mysql.conf.d/mysqld.cnf:
# innodb_buffer_pool_size = 256M # su VPS con ~4 GB di RAM
# poi:
systemctl restart mysqlSe il tuo VPS ha 4 GB di RAM o meno, uno swap file da 2-4 GB con vm.swappiness basso può fare la differenza tra un server che assorbe un picco e uno che viene falciato dall'OOM killer al primo import pesante: non è un rimpiazzo della RAM, ma una rete di sicurezza che evita il kill brutale di mysqld.
Fase 4: load anomalo, è CPU, I/O o memoria?
Un load average alto non significa automaticamente CPU satura. Su Linux il load average include anche i processi in attesa di I/O disco, quindi un VPS con storage lento o un noisy neighbor può mostrare load 20 con la CPU quasi scarica. Distinguere il tipo di carico è il bivio diagnostico: confondere I/O wait con saturazione CPU porta a interventi inutili.
# Vista d'insieme: CPU, I/O wait, swap in/out, context switch
vmstat 1 5
# I/O disco per device (la colonna %util è la chiave)
iostat -xz 1 3 # da: apt install -y sysstat
# Processi ordinati per I/O effettivo
iotop -oP -d 2 # da: apt install -y iotopL'output di vmstat ha tre colonne decisive: wa (I/O wait) e si/so (swap in / swap out). Se wa è stabilmente sopra il 20%, il collo di bottiglia è il disco. Se si e so sono entrambi sopra zero in modo continuo, il server è in swap thrashing: la RAM è esaurita e il sistema sposta pagine tra RAM e disco senza sosta, una condizione catastroficamente lenta in cui spesso il server è di fatto bloccato pur risultando "acceso". Se invece la colonna us (user CPU) è al 90%+ e wa è a zero, hai un processo che divora la CPU: tipicamente un loop infinito, un import batch fuori controllo o un cryptominer.
# Top 10 processi per CPU
ps aux --sort=-%cpu | head -11
# Top 10 processi per memoria
ps aux --sort=-%mem | head -11
# Ispezionare un processo sospetto a partire dal suo PID
ls -la /proc/PID/exe # path del binario
cat /proc/PID/cmdline # command line completa (separata da null)
ls -la /proc/PID/cwd # directory di lavoroQuando un processo sconosciuto consuma il 100% di una vCPU, l'ispezione di /proc/PID/exe e /proc/PID/cwd è ciò che distingue un job batch legittimo da un binario malevolo lanciato da una directory scrivibile come /tmp o /dev/shm. Trasformare questa diagnosi reattiva in prevenzione significa un monitoring serio, Prometheus e Grafana con il metodo RED per i servizi e USE per l'infrastruttura: il tipo di osservabilità che converte l'emergenza notturna in un alert ricevuto con calma al mattino, prima che il server cada.
Fase 5: sospetto di compromissione, il quick security check
Se durante le fasi precedenti noti processi sconosciuti, connessioni verso IP esterni anomali, binari in directory di sistema o utenti che non riconosci, fermati: non sei più in un problema operativo, sei in un possibile incidente di sicurezza, e il troubleshooting applicativo va sospeso. Ecco il quick check che eseguo sempre come parte della checklist:
# Login SSH riusciti di recente (cercare IP non riconosciuti)
grep "Accepted" /var/log/auth.log | tail -20
# Utenti con shell interattiva (UID 0 inattesi sono un campanello rosso)
awk -F: '($3==0)||($7 ~ /\/(bash|sh)$/){print}' /etc/passwd
# Crontab di tutti gli utenti (cercare entry sospette)
for u in $(cut -d: -f1 /etc/passwd); do
crontab -u "$u" -l 2>/dev/null | grep -vE "^#|^$" && echo "^^^ utente: $u"
done
# Connessioni di rete stabilite verso l'esterno
ss -tupn | grep ESTAB | grep -v "127.0.0.1"
# File modificati nelle ultime 24 ore in directory sensibili
find /etc /usr/local/bin /var/spool/cron -mtime -1 -type f -ls 2>/dev/nullSe uno di questi check rivela anomalie, la priorità si ribalta: contenere l'attaccante, non ripristinare il servizio. Non riavviare i processi, non cancellare file (distruggeresti evidenze), non spegnere la macchina se vuoi preservare lo stato della memoria. Isola il server a livello di rete dal pannello del provider e procedi con un protocollo di incident response strutturato. Ho scritto la procedura completa, dall'isolamento alla rotazione delle credenziali fino al ripristino pulito, nell'articolo sulla gestione urgente di intrusioni su VPS unmanaged. E quasi sempre, a posteriori, la compromissione si sarebbe evitata con un firewall serio: una porta di servizio esposta a tutta internet è una porta che verrà trovata, come spiego nella guida a configurare un firewall nftables senza personale tecnico.
Fase 6: ripristino da backup, quando riparare costa più che ricominciare
Se la diagnosi rivela corruzione del filesystem, compromissione grave, o un problema applicativo talmente intricato che il debug richiederebbe più tempo del ripristino, la scelta corretta è ripartire da un backup pulito. La condizione necessaria, ovviamente, è avere un backup, e averlo testato.
# Se usi BorgBackup (la mia scelta per i VPS)
borg list /path/to/repo # elencare i backup disponibili
borg extract /path/to/repo::NOME_ARCHIVIO # estrarre un archivio
# Se ripristini da un dump SQL
mysql -u root -p < backup_YYYYMMDD.sql
# Verificare l'integrità dei dati dopo il ripristino
mysqlcheck -u root -p --all-databases --checkIl problema che incontro nella maggioranza delle PMI è che il backup esiste ma non è mai stato ripristinato neanche una volta, oppure risiede sullo stesso disco del server di produzione, il che lo rende inutile in caso di guasto hardware o compromissione. Un backup che non hai mai testato non è un backup: è un'ipotesi. Per questo lo schema che applico è sempre offsite, cifrato e con un test di ripristino automatizzato e periodico.
Se non hai un backup utilizzabile e il filesystem è corrotto, le opzioni si riducono drasticamente: rescue mode del provider, fsck sulla partizione smontata (lanciare fsck su un filesystem montato in lettura/scrittura è un modo eccellente per corromperlo del tutto), e tentativo di data recovery. Ho documentato per esteso questo scenario, comprese le insidie del rescue boot e la sequenza corretta dei comandi, nell'articolo sul ripristino di filesystem corrotti su VPS Debian e Ubuntu.
Il backup non è uno script che gira ogni notte: è la coppia "copia + ripristino testato". Finché non hai ripristinato almeno una volta in un ambiente pulito, non sai se hai un backup. Sai solo di avere dei file.
Fase 7: hardening post-crisi e prevenzione
Ogni emergenza risolta senza un follow-up di prevenzione è un'emergenza che si ripeterà, identica, qualche mese dopo. Dopo aver riportato il server online dedico sempre del tempo a implementare le contromisure che avrebbero impedito il problema all'origine:
- Disco pieno: configurare
logrotateper tutti i log applicativi, impostarebinlog_expire_logs_secondsin MySQL in modo persistente, aggiungere un alert sudfche notifichi quando l'uso supera l'80% (e uno sudf -iper gli inode) - OOM kill: dimensionare correttamente
innodb_buffer_pool_sizesulla RAM reale, configurare uno swap file di sicurezza, impostarevm.swappiness = 10per ridurre l'aggressività dello swap - Servizio crashato: configurare il restart automatico in systemd con
Restart=on-failureeRestartSec=5s, più un alert che notifichi ogni riavvio non pianificato - Compromissione: SSH solo a chiave con
PasswordAuthentication no, Fail2ban sui servizi esposti, firewall default-deny, e un audit periodico con Lynis, lo scanner di hardening che produce un punteggio e una lista di remediation concrete
Il monitoring minimo che installo su ogni VPS dopo un'emergenza è composto da tre soli componenti: un cron che controlla lo spazio disco ogni ora e manda una mail sopra l'80%, un cron che verifica che i servizi critici (Nginx, PHP-FPM, MySQL) rispondano e avvisa se non lo fanno, e un backup giornaliero automatico verso storage offsite con verifica settimanale dell'integrità. Non è Prometheus con Grafana, quello arriva dopo se il progetto lo giustifica. Ma questi tre controlli da soli avrebbero evitato la grande maggioranza delle emergenze che ho gestito, perché quasi tutte erano problemi annunciati che nessuno stava guardando.
Domande frequenti sulle emergenze VPS
Cosa faccio per primo quando un VPS Debian o Ubuntu non risponde? Apri la console del provider se SSH è muto, poi esegui in sequenza df -hT, df -i, free -h, systemctl --failed e dmesg -T | tail. In meno di un minuto sai se sei davanti a un disco pieno, a un OOM kill o a un servizio crashato, e da lì segui la fase corrispondente. La prima azione non è mai un riavvio: è una misura.
Il disco è pieno ma du non trova file grandi: dov'è lo spazio? Quasi certamente in un file cancellato ma ancora aperto da un processo. Esegui sudo lsof +L1 | sort -k7 -rn: lo spazio si libera solo riavviando il processo che tiene aperto il descriptor, oppure svuotando il file via /proc/<pid>/fd/<fd> (mai su un file di database). Controlla anche gli inode con df -i: un filesystem può essere "pieno" di inode pur avendo byte liberi.
Conviene un VPS unmanaged o un managed? Dipende da chi gestisce le sette fasi di questa checklist alle tre di notte. Un VPS unmanaged a basso costo è la scelta razionale solo se hai (interno o a contratto) qualcuno in grado di fare diagnosi e ripristino. Senza quella competenza, il risparmio sul canone si paga moltiplicato al primo downtime prolungato, ed è esattamente lì che un'infrastruttura gestita ha senso.
Per un'infrastruttura cloud europea self-managed con ottimo rapporto prezzo/prestazioni e conformità GDPR, Hetzner resta un riferimento solido (datacenter UE), a patto di avere chi sa gestire le emergenze descritte qui. 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: in pratica, qualcuno che esegue questa checklist al posto tuo.
La differenza tra un VPS che ti tiene sveglio la notte e uno che gira senza pensieri non è il provider né l'hardware: è la preparazione. Una checklist operativa testata, un backup che hai davvero ripristinato e un minimo di monitoring trasformano le emergenze da crisi aziendali a incidenti gestibili in meno di un'ora. Se il tuo VPS Debian o Ubuntu è in produzione senza nessuna di queste protezioni, la domanda non è se avrai un'emergenza, ma quando, e quando arriverà sarà molto più costosa di quanto sarebbe stato prevenirla. Se vuoi mettere in sicurezza l'infrastruttura prima che sia troppo tardi, oppure se il problema lo stai vivendo proprio adesso, contattami per una consulenza diretta: tra la diagnosi e la prima contromisura, di solito, passano ore, non settimane.