Backup VPS su Hetzner, OVH, Contabo, Digital Ocean e Aruba: strategie avanzate per aziende

Backup VPS su Hetzner, OVH, Contabo, Digital Ocean e Aruba: strategie avanzate per aziende

Qualche mese fa mi ha contattato il responsabile IT di una PMI manifatturiera del Nord-Est dopo che un ransomware aveva cifrato l'intero filesystem di un VPS Contabo da 8 vCPU, 24 GB di RAM e SSD. Sul server girava il gestionale che coordinava la produzione di tre linee di assemblaggio, con un volume nell'ordine del centinaio di ordini al giorno: ogni ora di fermo si traduceva in ritardi sulle consegne e penali contrattuali nell'ordine delle migliaia di euro. Il backup esistente era un mysqldump schedulato ogni notte alle 02:00 e salvato in /var/backups/, sullo stesso disco del server cifrato. Inutile. Il secondo "backup" era una copia manuale che il responsabile scaricava via SFTP sul proprio portatile, quando se ne ricordava: l'ultimo download risaliva a oltre una settimana prima. Una settimana di ordini, fatture, note di produzione e modifiche ai listini, evaporata.

Quel cliente ha ricostruito a mano i giorni mancanti incrociando email e copie cartacee, con un costo complessivo dell'incidente, tra fermo, lavoro di ricostruzione e penali, di gran lunga superiore a quello di un backup offsite configurato come si deve. Questo articolo è la strategia che ho implementato dopo quell'incidente e che oggi installo come standard su ogni VPS unmanaged che gestisco, su Hetzner, OVH, Contabo, Digital Ocean o Aruba. Non è teoria: è il protocollo operativo, con gli script reali, che separa un backup vero da una speranza schedulata.

Stai cercando un Consulente Informatico esperto per mettere in sicurezza i backup della tua infrastruttura prima che serva il ripristino? Nel mio profilo professionale trovi l'esperienza concreta su VPS e server dedicati Hetzner, OVH, Contabo, Digital Ocean e Aruba, hardening Linux, disaster recovery e recupero di infrastrutture compromesse.

Perché la regola 3-2-1 non basta più e cosa significa 3-2-1-1-0?

La regola 3-2-1 (tre copie dei dati, su due supporti diversi, di cui una offsite) è stata per vent'anni il riferimento per la strategia di backup. Oggi non basta più, e la ragione è una sola: il ransomware moderno caccia attivamente i backup. Prima di cifrare, l'attaccante enumera e distrugge tutto ciò che il server compromesso può raggiungere con le proprie credenziali: share di rete montate, repository remoti accessibili con la stessa chiave SSH, bucket S3 senza object lock, snapshot del pannello provider se l'account è lo stesso. Se il tuo backup è raggiungibile e cancellabile dal server di produzione, non è un backup: è un secondo bersaglio che hai gentilmente etichettato.

La risposta è il modello 3-2-1-1-0, ormai adottato come standard di settore e formalizzato anche da vendor di backup come Barracuda Networks: tre copie, due supporti, una offsite, una immutabile (non cancellabile nemmeno con i privilegi di root del server di produzione) e zero errori, dove lo zero non è un auspicio ma il risultato di test di ripristino regolari. I due elementi nuovi sono il cuore della difesa anti-ransomware. L'immutabilità garantisce che, anche con il server compromesso al 100% (root, credenziali rubate, tutto), almeno una copia sopravviva. Lo zero garantisce che quella copia sia davvero ripristinabile, e non un archivio corrotto scoperto tale solo nel momento peggiore.

Un backup mai testato non è un backup, è una scommessa. La regola 3-2-1-1-0 esiste perché negli incidenti reali falliscono quasi sempre gli ultimi due numeri: l'immutabilità (il ransomware raggiunge i backup) e lo zero (il restore non era mai stato provato).

Lo strumento di base: BorgBackup con repository cifrato

Per i backup dei VPS che gestisco uso come standard BorgBackup, per tre ragioni concrete. La prima è la deduplicazione a livello di blocco: Borg spezza i file in chunk e archivia ogni chunk una sola volta, così la dimensione effettiva del repository resta vicina al volume dei dati unici anche tenendo decine di snapshot storici. La seconda è l'encryption AES-256 lato client: i dati vengono cifrati sul server prima di partire, lo storage di destinazione riceve solo blocchi cifrati e il provider non può leggerli. La terza è il supporto nativo alla modalità append-only, su cui costruisco l'immutabilità.

L'inizializzazione di un repository cifrato è diretta:

# Installazione (Debian/Ubuntu)
apt install -y borgbackup

# Inizializzazione con encryption: la chiave vive nel repo, protetta da passphrase
borg init --encryption=repokey-blake2 ssh://backup@STORAGE_HOST/./borg-repo

# CRITICO: esportare la chiave del repository in un luogo sicuro OFFLINE.
# Senza chiave + passphrase, i backup cifrati sono irrecuperabili. Per sempre.
borg key export ssh://backup@STORAGE_HOST/./borg-repo /root/borg-key-export.txt

La scelta di repokey-blake2 rispetto al classico repokey non è cosmetica: l'algoritmo BLAKE2 è più veloce di SHA-256 nell'hashing dei chunk e offre le stesse garanzie di integrità, un vantaggio misurabile su un VPS con CPU contesa. Ma il punto da fissare con un chiodo è un altro: la chiave del repository va custodita offline, separata dal server. Un backup cifrato la cui unica copia della chiave vive sul server cifrato dal ransomware ha lo stesso valore di nessun backup. Questa è la prima cosa che verifico quando faccio l'audit di un'infrastruttura altrui, ed è quella che manca più spesso.

L'append-only mode: cosa protegge davvero (e cosa no)

Qui c'è una sottigliezza che la maggior parte dei tutorial sbaglia, e che fa la differenza tra credere di essere protetti ed esserlo. Su un repository servito via SSH si forza il comando borg serve con il flag --append-only direttamente nel file authorized_keys dell'utente di backup, lato storage:

command="borg serve --restrict-to-path /home/backup/borg-repo --append-only",restrict ssh-ed25519 AAAAC3Nza...chiave_pubblica_del_vps...

Il flag --append-only istruisce borg serve ad accettare nuovi archivi e nuovi chunk, ma non blocca del tutto le operazioni di cancellazione. Come chiarisce la documentazione ufficiale di Borg, in append-only mode un client può ancora eseguire borg delete o borg prune: la differenza è che queste operazioni si limitano a marcare i dati come eliminati in una nuova transazione, senza rimuoverli fisicamente, perché la compattazione (borg compact) è vietata. Ogni transazione viene registrata con timestamp UTC nel file transactions.

Cosa significa in pratica? Significa che l'append-only di Borg garantisce reversibilità, non prevenzione assoluta. Se un attaccante ottiene root sul VPS, trova la chiave SSH e lancia un borg delete malevolo, i dati non sono persi: l'amministratore dello storage può leggere il log delle transazioni, identificare il momento del compromesso, rimuovere i segment file creati dopo l'attacco e ripristinare lo stato precedente, poi rieseguire borg check --verify-data e borg compact. È un meccanismo solido, ma richiede un attore separato (lo storage, con credenziali diverse) che faccia da custode.

Append-only di Borg non vuol dire "il ransomware non può toccare i backup". Vuol dire "il ransomware non può distruggerli in modo irreversibile, e un secondo attore con credenziali separate può sempre tornare indietro". È una garanzia di reversibilità, e va progettata di conseguenza.

Da qui derivano due decisioni di design che applico sempre. Primo: il pruning non lo fa mai il VPS. Il server di produzione ha una chiave append-only e può solo aggiungere; la rotazione della retention (l'unica operazione che cancella per davvero) gira lato storage, con credenziali che il VPS non possiede. Secondo: per i clienti dove il rischio lo giustifica, sotto Borg metto un secondo strato di immutabilità a livello di storage (object lock in modalità compliance su backend S3-compatibili, dove nemmeno l'amministratore dello storage può cancellare prima della scadenza della retention). Lo stesso principio vale se si preferisce restic come tool: il suo rest-server ha una modalità append-only equivalente, con i medesimi limiti, e l'object lock S3 resta la cintura di sicurezza più robusta.

Quale storage di destinazione, provider per provider

L'append-only e l'immutabilità hanno senso solo se la destinazione è fisicamente separata dal VPS di produzione, con credenziali distinte. La scelta del target dipende dal provider su cui gira il server, e ogni provider ha le sue specificità.

Come destinazione standard per i clienti uso la Hetzner Storage Box: storage dedicato al backup con accesso SSH/SFTP/SCP/rsync nativo e supporto diretto a BorgBackup e restic, snapshot lato server inclusi e fino a 100 sub-account per isolare le chiavi. È perfetta come repository Borg append-only via SSH, e la uso anche come target per VPS che girano altrove: la latenza tra datacenter europei è trascurabile per un backup notturno. Per chi vuole il VPS di produzione su cloud UE self-managed con buon rapporto prezzo/prestazioni e datacenter in Germania o Finlandia, Hetzner resta il riferimento del mercato europeo (listino EUR sulle region eu-central, con la classe entry shared intorno ai 3-4 euro/mese IVA esclusa, rilevazione 2026-06-11 su hetzner.com/cloud).

Sugli altri provider la logica non cambia, cambiano gli strumenti:

  • OVHcloud include su alcuni piani uno snapshot automatico del VPS, ma è tutto-o-niente e a grana grossa: utile come complemento, inadeguato come backup granulare. Per backup veri si appoggia uno storage esterno, ad esempio l'Object Storage S3-compatibile, sincronizzato con rclone. OVH è anche l'unico tra questi con una region 3-AZ a Milano e certificazioni di sovranità (SecNumCloud, qualifica ACN per la PA italiana).
  • Contabo ha pricing aggressivo sul VPS ma nessuna soluzione di backup integrata di livello: il mio default è puntare gli script Borg verso una Storage Box esterna. Da tenere a mente, separato dai fatti tecnici: Contabo ha una reputazione consolidata di oversubscription sui piani shared, quindi sul VPS di produzione conviene non tirare troppo.
  • Digital Ocean offre Spaces (object storage S3-compatibile) come target per i backup Borg via rclone, più i backup automatici dei Droplet a sovrapprezzo. Attenzione però: è una società USA, quindi piena esposizione al CLOUD Act anche per i Droplet in region UE, un fattore da pesare se i dati di backup sono sensibili.
  • Aruba Cloud ha datacenter di proprietà in Italia e object storage S3-compatibile, scelta naturale quando il vincolo è la residenza del dato sul territorio nazionale.

La regola che non negozio mai: backup e server di produzione non condividono né infrastruttura né credenziali né account. Se un singolo compromesso può raggiungere entrambi, non hai un backup, hai due copie dello stesso bersaglio.

Per le PMI italiane che non vogliono gestire in proprio nemmeno lo strato di backup e cercano un'infrastruttura gestita con il dato che non lascia l'Italia, la prima scelta che indico è RHX: private cloud con gestione sistemistica inclusa, backup automatizzati e ripristino tra i servizi managed, datacenter a Milano (adiacente al MiX) e Padova. Il posizionamento è private cloud gestito, non VPS low-cost: il confronto si fa sul TCO gestito, non sul prezzo nudo per core.

Lo script di backup che installo in produzione

Lo script che metto su ogni VPS gestito esegue in sequenza quattro operazioni: dump del database, backup del filesystem con Borg, log strutturato e verifica periodica dell'integrità. Lo lancio come systemd timer, non come cron: ottengo logging centralizzato in journald, gestione pulita dei fallimenti con OnFailure= e niente sorprese sul PATH o sull'ambiente.

#!/usr/bin/env bash
set -euo pipefail

# Configurazione (in produzione: passphrase e password DB da file con chmod 600)
export BORG_REPO="ssh://backup@STORAGE_HOST/./borg-repo"
export BORG_PASSPHRASE="$(cat /root/.borg-passphrase)"
readonly LOG="/var/log/borg-backup.log"
readonly ARCHIVE="{hostname}-{now:%Y-%m-%d_%H%M}"

log() { echo "$(date '+%Y-%m-%d %H:%M:%S') $1" | tee -a "$LOG"; }

# 1. Dump MySQL consistente, senza lock sulle tabelle InnoDB
log "START: MySQL dump"
mysqldump --defaults-extra-file=/root/.my.cnf \
    --single-transaction --quick --routines --triggers --events \
    --all-databases | zstd -6 > /tmp/mysql-dump.sql.zst
log "DONE: MySQL dump ($(du -sh /tmp/mysql-dump.sql.zst | cut -f1))"

# 2. Backup BorgBackup del filesystem + dump
log "START: Borg create"
borg create --compression zstd,6 --stats --one-file-system \
    "::${ARCHIVE}" \
    /etc /var/www /home /tmp/mysql-dump.sql.zst \
    --exclude '/var/www/*/vendor' \
    --exclude '/var/www/*/node_modules' \
    --exclude '/var/www/*/storage/logs/*.log' \
    --exclude '*.cache' 2>> "$LOG"
log "DONE: Borg create"

# 3. Pulizia del dump temporaneo
rm -f /tmp/mysql-dump.sql.zst

# 4. Verifica integrita settimanale (sabato), a basso costo sul repo
if [ "$(date +%u)" -eq 6 ]; then
    log "START: Borg check"
    borg check "${BORG_REPO}" 2>> "$LOG"
    log "DONE: Borg check"
fi

Tre dettagli che fanno la differenza e che vedo saltati di continuo. Le esclusioni non sono pignoleria: vendor/ e node_modules/ si rigenerano con composer install e npm ci, includerli spreca spazio, banda e tempo di ripristino; i log applicativi in storage/logs/ non servono a ripristinare nulla. La compressione zstd,6 offre un rapporto compressione/velocità eccellente, tipicamente intorno al 50% di riduzione con overhead CPU contenuto. E il --single-transaction del mysqldump è non negoziabile su InnoDB: garantisce un dump consistente senza bloccare le scritture, evitando lo scenario classico in cui una tabella ordini referenzia righe di una tabella prodotti dumpata in un istante diverso. Aggiungo --quick per non bufferizzare l'intero result set in RAM su database grandi.

La retention policy: il modello GFS senza sprechi

La retention segue il modello GFS (Grandfather-Father-Son), che bilancia granularità di ripristino e costo dello storage tenendo molti backup recenti e via via meno backup man mano che si va indietro nel tempo. La configurazione che uso come default copre tre orizzonti diversi:

  • 7 backup giornalieri, per recuperare da un errore scoperto in fretta (una migration sbagliata, una cancellazione accidentale di ieri).
  • 4 backup settimanali, per i problemi che emergono dopo qualche giorno.
  • 6 backup mensili, per audit, compliance e per la corruzione lenta, quella che si insinua e si nota solo settimane dopo.

Su Borg la rotazione si esprime così, e gira lato storage, mai dal VPS:

# Pruning: eseguito SOLO dallo storage di backup, con credenziali diverse dal VPS
borg prune --keep-daily=7 --keep-weekly=4 --keep-monthly=6 \
    --stats "${BORG_REPO}" 2>> "$LOG"

# Compattazione: libera davvero lo spazio dopo il pruning
borg compact "${BORG_REPO}" 2>> "$LOG"

Il borg compact dopo il prune è il passaggio che quasi tutti dimenticano: il pruning marca gli archivi come eliminati, ma lo spazio si libera solo con la compattazione. Senza, il repository continua a crescere all'infinito nonostante il prune. Grazie alla deduplicazione, l'occupazione effettiva di una retention completa resta vicina al volume dei dati unici più i delta: su un'applicazione tipica, dove tra un giorno e l'altro cambia una frazione minima dei file, decine di archivi storici costano una manciata di gigabyte oltre alla baseline. È il motivo per cui tenere mesi di storia su una Storage Box ha un costo marginale che vale ogni centesimo rispetto al rischio che copre.

Il backup del database: mysqldump non è l'unica strada

Per database MySQL o MariaDB fino a una decina di gigabyte, mysqldump --single-transaction resta lo strumento più pratico: produce un file SQL portabile, leggibile e verificabile a mano. Se vuoi i dettagli sul perché --single-transaction è la chiave per dumpare senza lock un database in produzione, l'ho trattato a fondo nell'articolo su mysqldump senza lock su server di produzione. Oltre i 20-30 GB, però, il dump logico diventa un collo di bottiglia: un mysqldump da 40 GB può richiedere decine di minuti, durante i quali la transazione lunga mantiene una vista consistente che pesa sulle performance e gonfia gli undo log.

Per quei casi uso Percona XtraBackup, che esegue un backup fisico hot copiando i file InnoDB a livello di pagina mentre il database continua a scrivere, applicando i redo log alla fine per garantire la consistenza. Il ripristino è molto più veloce di un import SQL, perché non deve ricostruire gli indici: si copiano i file e si avvia il server. Lo svantaggio è la minore portabilità tra versioni diverse di MySQL, ma per il disaster recovery sullo stesso stack è la soluzione più efficiente. Un errore che vedo di continuo, indipendente dal tool: il dump viene schedulato e nessuno controlla mai se è andato a buon fine. Il passo in più che implemento sempre è un check sulla dimensione del dump: se l'archivio è significativamente più piccolo della media storica (diciamo una variazione oltre il 20%), qualcosa non torna e il backup va marcato come sospetto, non archiviato in silenzio.

Il test di ripristino automatizzato: la parte che tutti saltano

Lo ripeto perché è il numero 0 della regola e il punto dove le strategie altrui crollano: un backup che non è mai stato ripristinato non è un backup, è una speranza. In più di un'occasione ho fatto l'audit di infrastrutture dove i backup giravano da anni, regolarmente, e si è scoperto solo durante un'emergenza che erano corrotti, incompleti o cifrati con una chiave persa. La causa è sempre la stessa: nessuno aveva mai verificato che il ripristino funzionasse davvero.

Il test che configuro su ogni VPS estrae l'ultimo archivio in dry-run e verifica che non ci siano errori di integrità, ed è abbastanza leggero da girare ogni notte:

#!/usr/bin/env bash
set -euo pipefail

export BORG_REPO="ssh://backup@STORAGE_HOST/./borg-repo"
export BORG_PASSPHRASE="$(cat /root/.borg-passphrase)"

# Ultimo archivio, verifica integrita senza scrivere su disco
LAST="$(borg list --last 1 --format '{archive}' "${BORG_REPO}")"

if borg extract --dry-run "${BORG_REPO}::${LAST}" 2>/tmp/restore-test.log; then
    echo "RESTORE TEST OK: ${LAST}" | mail -s "Backup OK" [email protected]
else
    echo "RESTORE TEST FAILED: ${LAST}" | mail -s "BACKUP ALERT" [email protected]
fi

Il --dry-run di borg extract legge e decifra l'intero archivio verificandone l'integrità senza scrivere nulla su disco, quindi è veloce e senza impatto sullo spazio. Ma il dry-run non basta da solo. Una volta al mese eseguo un restore reale completo: estraggo l'archivio in una directory pulita, importo il dump in un'istanza MySQL temporanea e controllo che l'applicazione si avvii e che le tabelle critiche contengano dati coerenti. È l'unico modo per validare l'intera catena, dal chunk cifrato all'applicazione in piedi, e per misurare un RTO realistico: quanto tempo serve davvero a tornare operativi. Un RTO scritto in un documento ma mai cronometrato è un numero di fantasia.

Se quello che stai affrontando non è la prevenzione ma il dopo (un filesystem già corrotto da ripristinare), ho documentato la procedura passo-passo nell'articolo sul ripristino di un filesystem corrotto su VPS Debian e Ubuntu. E se vuoi inquadrare il backup nel disegno più ampio della continuità operativa, è il primo pilastro del piano di disaster recovery per applicazioni PHP: backup, RPO/RTO, runbook e ruoli sono pezzi dello stesso sistema, non isole.

Il backup non è un task tecnico da configurare una volta e dimenticare: è un'assicurazione sulla sopravvivenza del business digitale, e come ogni assicurazione vale solo se l'hai testata prima del sinistro. La PMI manifatturiera del Nord-Est ha pagato a caro prezzo la lezione che uno script schedulato non è una strategia, e che un backup sullo stesso disco del server è un placebo. Una destinazione offsite, immutabile, con encryption lato client e un restore verificato ogni settimana costa una frazione minima di una singola giornata di fermo. Se vuoi impostare una strategia di backup seria sulla tua infrastruttura, o semplicemente verificare che quella che hai oggi sopravviva davvero a un ransomware e a un test di ripristino reale, contattami per una consulenza diretta: tra la diagnosi e la prima contromisura, di solito, passano ore e non settimane.

Ultima modifica: