Risolvere problemi critici di spazio disco su VPS gestite senza personale tecnico: guida operativa per Debian e Ubuntu

Risolvere problemi critici di spazio disco su VPS gestite senza personale tecnico: guida operativa per Debian e Ubuntu

Il 19 maggio 2025, alle 11:30 di un lunedì mattina, il responsabile amministrativo di una PMI romagnola - azienda di distribuzione alimentare con gestionale Laravel 10 per ordini, fatturazione e logistica - mi ha chiamato perché il gestionale restituiva errore 500 su tutte le pagine. Il VPS era un Hetzner CX21 - 2 vCPU, 4 GB di RAM, 80 GB NVMe - con Debian 12, Nginx, PHP-FPM 8.2 e MySQL 8.0. I 18 operatori commerciali che normalmente usavano il gestionale per inserire ordini e stampare bolle di accompagnamento erano completamente fermi. Il fatturato giornaliero dell'azienda era circa 45.000 euro, e ogni ora di fermo significava ordini non processati, consegne ritardate e clienti della ristorazione che non ricevevano la merce per il servizio serale.

La diagnosi è stata immediata: df -h mostrava /dev/sda1 al 100%, zero byte disponibili. MySQL non riusciva a scrivere nel binary log e si era fermato, PHP-FPM non poteva scrivere i file di sessione e restituiva errore, e il laravel.log stesso non riusciva a registrare gli errori perché non c'era spazio per scrivere. Un disco pieno al 100% è il guasto più comune e più paralizzante su un VPS unmanaged, e il percorso di risoluzione ha almeno tre trappole che ho visto intrappolare sysadmin inesperti per ore. In questo articolo ti racconto la diagnosi completa, i fix immediati e il protocollo di prevenzione che installo su ogni VPS.

Stai cercando un Consulente Informatico esperto per risolvere emergenze sulla tua infrastruttura VPS? Nel mio profilo professionale trovi l'esperienza concreta su Hetzner, OVH, Contabo e Digital Ocean. Contattami per una consulenza diretta.

Come si diagnostica un disco pieno e dove guardare per primo?

Il primo comando è df -hT - mostra lo spazio usato per ogni filesystem con il tipo di filesystem. Il secondo comando è ncdu / (se installato) o du -xsh /* | sort -rh | head -10 - mostra quali directory occupano più spazio. L'ordine è importante: prima capisci quanto spazio manca, poi capisci dove sta il problema.

# 1. Panoramica del disco
df -hT

# 2. Trovare le directory più grandi (ricorsivo)
du -xsh /* 2>/dev/null | sort -rh | head -10

# 3. Scendere nella directory colpevole
du -xsh /var/* 2>/dev/null | sort -rh | head -10
du -xsh /var/log/* 2>/dev/null | sort -rh | head -5

Per una diagnosi visuale e interattiva, ncdu (NCurses Disk Usage) è lo strumento migliore - un file manager per il terminale che mostra la dimensione di ogni directory in tempo reale con navigazione ad albero. Su Debian 12 si installa con apt install ncdu e si lancia con ncdu /. In meno di un minuto scansiona l'intero filesystem e ti mostra esattamente dove stanno i file più grandi, con la possibilità di cancellarli direttamente dall'interfaccia.

Il flag -x in du è fondamentale: limita la scansione al filesystem corrente, evitando di attraversare mount point (come /proc, /sys o mount NFS) che falserebbero i risultati. Senza -x, du su un server con volumi montati può impiegare minuti e restituire numeri senza senso.

Nel caso romagnolo, la gerarchia era chiara: /var occupava 71 GB su 80 GB totali. Dentro /var: /var/log occupava 38 GB e /var/lib/mysql occupava 29 GB. Dentro /var/log: un singolo file laravel.log da 34 GB - un anno di log senza rotazione, con livello di logging impostato su debug in produzione.

La prima trappola: cancellare un file non libera lo spazio

Questa è la trappola più insidiosa e quella che causa più confusione. Il responsabile IT dell'azienda - un diplomato con buone basi informatiche ma senza esperienza di sysadmin Linux - aveva già provato a risolvere il problema prima di chiamarmi: aveva fatto rm /var/www/html/storage/logs/laravel.log per rimuovere il file da 34 GB. Ma df -h continuava a mostrare il disco al 100%. Aveva provato a cancellare altri file. Ancora 100%. Aveva provato sync. Ancora 100%. Si era convinto che il disco fosse guasto.

Il motivo è una proprietà fondamentale dei filesystem Linux: quando cancelli un file che è ancora aperto da un processo, il file scompare dal filesystem (non lo vedi più con ls) ma lo spazio non viene liberato finché il processo non chiude il file descriptor. PHP-FPM stava scrivendo continuamente su laravel.log, quindi anche dopo l'rm il file descriptor restava aperto e i 34 GB restavano occupati.

La diagnosi di questa situazione si fa con lsof:

# Trovare file cancellati ma ancora aperti (occupano spazio fantasma)
lsof +L1 2>/dev/null | awk '{print $7, $1, $9}' | sort -rn | head -10

# Output tipico:
# 35987456000 php-fpm8 /var/www/html/storage/logs/laravel.log (deleted)
# 2147483648 mysql     /var/lib/mysql/binlog.000047 (deleted)

Il fix è semplice: riavvia il servizio che tiene aperto il file. Dopo systemctl restart php8.2-fpm, i 34 GB sono stati liberati istantaneamente. Ma l'approccio migliore - che evita il riavvio del servizio - è troncare il file anziché cancellarlo:

# CORRETTO: troncare il file a zero (il file descriptor resta valido)
truncate -s 0 /var/www/html/storage/logs/laravel.log

# SBAGLIATO: cancellare il file (lo spazio non si libera)
rm /var/www/html/storage/logs/laravel.log

Il truncate -s 0 imposta la dimensione del file a zero byte senza rimuoverlo dal filesystem. Il processo che lo ha aperto continua a scrivere nello stesso file (ora vuoto) senza problemi. È la differenza tra svuotare un bicchiere e rompere il bicchiere - nel primo caso puoi continuare a usarlo, nel secondo no.

La seconda trappola: il disco è "pieno" ma non ci sono file grandi

A volte df -h mostra il disco al 100% ma du -sh / riporta un totale molto inferiore allo spazio occupato. Oltre ai file deleted-but-open, c'è una seconda causa: l'esaurimento degli inode. Ogni file su un filesystem Linux occupa un inode, e il numero di inode è fisso (stabilito alla formattazione). Se crei milioni di file piccolissimi - tipicamente file di sessione PHP, file di cache, email in coda - puoi esaurire gli inode prima dello spazio disco.

# Controllare l'utilizzo degli inode
df -i

# Se gli inode sono al 100%, trovare dove sono i milioni di file
find / -xdev -printf '%h\n' | sort | uniq -c | sort -rn | head -10

Nel caso romagnolo gli inode non erano il problema, ma l'ho verificato come parte della diagnosi standard. Su un altro VPS di un cliente con Magento, ho trovato 4.2 milioni di file di sessione PHP in /var/lib/php/sessions/ - ogni sessione era un file da 2 KB, ma 4.2 milioni di file avevano esaurito il 98% degli inode disponibili.

La terza trappola: il journal di systemd che cresce senza limiti

Su Debian 12, systemd-journald salva i log di sistema in formato binario in /var/log/journal/. La documentazione ufficiale di systemd specifica che di default non c'è un limite esplicito alla dimensione del journal, che può crescere fino a occupare il 15% del filesystem. Su un VPS da 80 GB, significa fino a 12 GB di journal che pochi controllano.

# Quanto spazio occupa il journal?
journalctl --disk-usage

# Ridurre immediatamente a 200MB
journalctl --vacuum-size=200M

# Impostare un limite permanente
# In /etc/systemd/journald.conf:
# SystemMaxUse=200M
# MaxRetentionSec=7d
systemctl restart systemd-journald

Il protocollo di pulizia che uso in emergenza

Quando arrivo su un VPS con disco al 100% e i servizi fermi, eseguo questa sequenza in ordine di impatto decrescente:

# 1. Liberare spazio immediato: cache APT e kernel vecchi
apt clean
apt autoremove --purge -y

# 2. Journal systemd
journalctl --vacuum-size=200M

# 3. File grandi in /var/log (troncare, non cancellare)
find /var/log -type f -name "*.log" -size +100M -exec truncate -s 0 {} \;

# 4. Binary log MySQL (se non servono per replica)
mysql -u root -p -e "PURGE BINARY LOGS BEFORE DATE_SUB(NOW(), INTERVAL 1 DAY);"

# 5. File cancellati ma aperti (dopo i truncate/purge precedenti)
# Se lsof mostra ancora file (deleted), riavviare i servizi interessati
lsof +L1 2>/dev/null | grep deleted

# 6. File temporanei vecchi
find /tmp -type f -mtime +7 -delete 2>/dev/null
find /var/tmp -type f -mtime +30 -delete 2>/dev/null

Nel caso romagnolo, questa sequenza ha liberato 52 GB in meno di dieci minuti: 34 GB dal laravel.log (dopo il restart di PHP-FPM), 8 GB dalla cache APT, 6 GB dal journal systemd, e 4 GB dai binary log MySQL. Il disco è passato dal 100% al 35%, MySQL è ripartito, e il gestionale è tornato operativo.

I cinque colpevoli più comuni dello spazio disco su VPS di PMI

Dopo oltre venti interventi su disco pieno negli ultimi due anni, i colpevoli sono sempre gli stessi cinque, in ordine di frequenza:

Log applicativi non ruotati - Laravel, Magento, WordPress, applicazioni custom. Il log cresce indefinitamente perché nessuno ha configurato logrotate o perché il livello di logging è impostato su debug in produzione. Il record personale: un laravel.log da 87 GB su un Hetzner AX51, causato da un middleware di logging che registrava il body completo di ogni richiesta API inclusi gli upload di immagini in base64.

Binary log MySQL - MySQL scrive ogni operazione di scrittura nel binary log per la replica e il point-in-time recovery. Se binlog_expire_logs_seconds non è configurato (default: 2.592.000 secondi = 30 giorni), i binary log si accumulano per un mese intero. Su un database con molte scritture, 30 giorni di binary log possono occupare decine di GB.

Cache APT e kernel vecchi - Ogni aggiornamento di sistema scarica i pacchetti .deb in /var/cache/apt/archives/ e non li rimuove automaticamente. Ogni aggiornamento kernel installa un nuovo kernel image senza rimuovere il precedente. Dopo due anni di aggiornamenti, ho visto /var/cache/apt/ a 8 GB e cinque kernel vecchi che occupavano 3 GB.

Docker e i suoi volumi - Se il VPS usa Docker, la directory /var/lib/docker/ può crescere in modo incontrollato: immagini non utilizzate, container fermati, volumi orfani, build cache. Un docker system prune -a --volumes può liberare decine di GB, ma va usato con cautela perché rimuove tutto ciò che non è attivamente in uso.

Backup locali dimenticati - Dump MySQL schedulati con cron che si accumulano in /root/backups/ o /var/backups/ senza retention. Ho trovato VPS con 18 mesi di dump giornalieri - 540 file .sql.gz che occupavano 45 GB. Ho descritto la strategia corretta di backup per VPS unmanaged con BorgBackup e retention GFS in un articolo dedicato.

Prevenzione: logrotate e monitoring per non ritrovarsi mai più al 100%

La pulizia d'emergenza risolve il sintomo. La prevenzione risolve la causa. Sul VPS romagnolo ho implementato tre misure che installo come standard su ogni VPS.

Prima: logrotate per il log di Laravel e per tutti i log applicativi. Laravel non configura logrotate di default - il file laravel.log cresce indefinitamente se non intervieni:

# /etc/logrotate.d/laravel
/var/www/html/storage/logs/laravel.log {
    daily
    missingok
    rotate 7
    compress
    delaycompress
    notifempty
    create 644 www-data www-data
    postrotate
        /usr/lib/php/php8.2-fpm-reopenlogs > /dev/null 2>&1 || true
    endscript
}

La direttiva postrotate è cruciale: dopo la rotazione, segnala a PHP-FPM di riaprire i file di log, evitando il problema dei file cancellati-ma-aperti. Ho descritto la gestione strategica dei log Laravel con Monolog, canali separati e centralizzazione su Loki in un articolo dedicato.

Seconda: limite ai binary log MySQL con retention automatica:

# In /etc/mysql/mysql.conf.d/mysqld.cnf
binlog_expire_logs_seconds = 259200  # 3 giorni
max_binlog_size = 100M

Terza: monitoring dello spazio disco con alert all'80%:

# Cron ogni ora: alert se disco sopra 80%
0 * * * * df -h / | awk 'NR==2 && int($5)>80 {print "DISCO " $5 " pieno"}' | \
    mail -s "DISK ALERT $(hostname)" [email protected]

Ho documentato queste e altre misure preventive nell'articolo sulla checklist emergenza per VPS Debian e Ubuntu, e il piano completo di disaster recovery per applicazioni PHP che copre anche gli scenari in cui il disco non è solo pieno ma è corrotto.

Il disco pieno è il problema più prevedibile e più prevenibile nell'amministrazione di un VPS. Un logrotate configurato, un limite ai binary log MySQL e un alert all'80% costano meno di un'ora di setup e prevengono il 90% delle emergenze disco che vedo nelle PMI. Se il tuo VPS non ha nessuna di queste protezioni, è una questione di tempo - non di se - prima che il disco si riempia e il business si fermi. Contattami se vuoi mettere in sicurezza la tua infrastruttura prima che succeda. Un audit dello spazio disco con configurazione logrotate, retention dei binary log e monitoring automatico richiede meno di due ore e può evitare giorni di downtime e migliaia di euro di mancato fatturato - come il caso romagnolo ha dimostrato nel modo più costoso possibile.

Ultima modifica: