Backup Self-Hosted: Regola 3-2-1, Snapshot e Restore Provati Davvero
Il backup è quella cosa che tutti sanno di dover fare e che nessuno prova finché non serve. Il problema non è la tecnologia: gli strumenti sono maturi, gratuiti e ben documentati. Il problema è che un backup si giudica solo dal restore, e il restore nessuno lo fa finché non è troppo tardi.
Questo articolo non parla di hardware esotico né di soluzioni enterprise. Parla di come impostare una strategia di backup sensata per un server self-hosted — un gestionale, un sito, un NAS, qualche database — e di come verificarla davvero.
Prima le domande, poi lo strumento
Prima di scegliere restic, borg, rsync o quello che offre il provider, servono due numeri. Non sono numeri tecnici, sono decisioni di business.
- RPO (Recovery Point Objective): quanti dati posso permettermi di perdere, espresso in tempo. Se il backup gira ogni notte alle 2 e il guasto arriva alle 17, l'RPO è di circa 15 ore di lavoro perso.
- RTO (Recovery Time Objective): quanto tempo posso restare fermo mentre ripristino. Include il tempo per accorgersi del problema, procurarsi l'hardware, reinstallare e riportare i dati.
Quasi sempre l'RTO è la sorpresa: il ripristino di un database da 200 GB su un disco lento può richiedere ore, e nel frattempo l'attività è ferma.
| Scenario | RPO plausibile | RTO plausibile | Strategia minima |
|---|---|---|---|
| Blog personale, sito vetrina | giorni | giorni | snapshot periodico + copia fuori sede |
| Piccola azienda, gestionale | ore | ore | dump notturno + restore documentato |
| Servizio con dati continui | minuti | minuti | replica + WAL + backup immutabile |
I numeri nella tabella sono esempi, non prescrizioni: servono a capire che più stringi l'RPO e l'RTO, più crescono costi e complessità. Un RPO di un minuto costa ordini di grandezza in più di un RPO di un giorno.
La regola 3-2-1 e le sue estensioni
La formula classica è 3-2-1: tre copie dei dati, su due supporti o tecnologie diverse, di cui una fuori sede.
Nella pratica self-hosted significa, per esempio: il dato originale sul server, una copia su un disco esterno o su un NAS in un'altra stanza, una copia su object storage remoto. Tre copie, due tecnologie (disco locale e storage remoto), una geograficamente separata.
Negli ultimi anni si è aggiunta una coda: 3-2-1-1-0, dove il penultimo uno indica una copia offline o immutabile e lo zero indica zero errori nel restore verificato. È la parte che distingue un backup che esiste da un backup che funziona.
Le tre cose che non sono un backup
Vale la pena ripeterlo, perché sono le trappole più comuni.
Il RAID non è un backup. Il RAID protegge dalla morte di un disco, non da un file cancellato per sbaglio, da un ransomware che cifra tutto, da un filesystem corrotto o da un aggiornamento andato male. Anzi: con il RAID le cancellazioni si replicano istantaneamente e in modo affidabile.
Lo snapshot non è un backup. Uno snapshot sullo stesso storage è comodo per tornare indietro di qualche ora, ma vive e muore con il volume che fotografa. Se lo storage si rompe, si perde o viene rubato, gli snapshot spariscono insieme ai dati.
La sincronizzazione non è un backup. Nextcloud, Syncthing, Dropbox e simili sono strumenti di disponibilità, non di ripristino. Se cancelli un file sul portatile, la cancellazione si propaga al server. Il versioning remoto aiuta, ma va configurato e va verificato: molti lo scoprono inattivo nel momento peggiore.
Prendere i dati in modo coerente
Copiare i file di un database mentre è in uso produce, nella migliore delle ipotesi, un backup incoerente. Serve uno strumento che conosca il database.
Per PostgreSQL, un dump logico è il punto di partenza:
# un file per database, formato custom (compresso, ripristinabile selettivamente)
pg_dump --format=custom --file="/var/backups/db/app_$(date +%F).dump" app
# se serve tutto il cluster con possibilità di point-in-time recovery
pg_basebackup -D /var/backups/pg/base -Ft -z -P
Per SQLite la copia del file con cp è rischiosa se qualcosa sta scrivendo: il file può essere catturato a metà di una transazione.
# no: cp /srv/app/app.db /backup/app.db
# sì: usa l'API di backup, che gestisce il locking
sqlite3 /srv/app/app.db ".backup '/backup/app-$(date +%F).db'"
Per MySQL e MariaDB, mysqldump --single-transaction dà una fotografia coerente senza bloccare le scritture sulle tabelle InnoDB. Per volumi grandi conviene passare a XtraBackup o a una replica dedicata.
Oltre ai database, vale la pena includere esplicitamente: file di configurazione, variabili d'ambiente, certificati e chiavi, file compose, unit systemd, regole di firewall e il runbook di ripristino. Sono pochi kilobyte che, se mancano, trasformano un restore da mezz'ora a mezza giornata.
Se usi Docker, i volumi nominati non stanno in una cartella che sfogli con ls. Vanno esportati:
docker run --rm -v app_data:/data -v /backup:/backup alpine \
tar czf /backup/app_data_$(date +%F).tgz -C /data .
E attenzione alle esclusioni: escludere node_modules e le cache è giusto, escludere per sbaglio la cartella degli allegati o il file .env è un classico. Un buon esercizio è elencare il contenuto di un backup e chiedersi se manca qualcosa che serve per ripartire da zero.
Uno strumento solo, configurato bene
Per i file, strumenti come restic o borg coprono quasi tutto: snapshot incrementali con deduplica, cifratura lato client, retention configurabile, verifica dell'integrità. Il primo backup è lento, i successivi sono veloci perché trasferiscono solo i blocchi nuovi.
export RESTIC_REPOSITORY="s3:https://storage.example/bucket"
export RESTIC_PASSWORD_FILE=/etc/restic/password
restic backup /srv/app /etc /home \
--exclude-caches \
--exclude='**/node_modules' \
--tag daily
# retention: 7 giornalieri, 4 settimanali, 12 mensili
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 12 --prune
# verifica: controlla struttura e rilegge un campione di dati
restic check --read-data-subset=5%
Messo insieme a un dump del database, lo script di backup diventa qualcosa del genere:
#!/usr/bin/env bash
set -euo pipefail
STAMP=$(date +%F_%H%M)
BACKUP_DIR=/var/backups/db
mkdir -p "$BACKUP_DIR"
pg_dump --format=custom --file="$BACKUP_DIR/app_${STAMP}.dump" app
restic backup "$BACKUP_DIR" /srv/app --tag db
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 12 --prune
restic check --read-data-subset=2%
La riga set -euo pipefail è il dettaglio che salva più spesso: senza, un comando che fallisce dentro una pipe può non far fallire lo script, e il job risulta verde mentre il dump è vuoto.
Sulla password del repository vale una nota brutale: se la perdi, i backup sono rumore cifrato. Va custodita fuori dal backup stesso — password manager, copia cartacea, persona diversa da quella che amministra il server.
Immutabilità: difendersi anche dalle cancellazioni
Un ransomware che gira sul server con le credenziali di backup può cancellare la cronologia. Le contromisure non sono complicate:
- Object storage con versioning e object lock: i file restano per un periodo definito anche se qualcuno chiede di eliminarli.
- Repository append-only: il server di backup accetta scritture ma non cancellazioni, con un client che non ha i permessi per fare prune.
- Dischi a rotazione tenuti scollegati: il classico air gap, poco elegante e molto efficace.
- Credenziali separate: chi scrive i backup non è chi può distruggerli.
Il costo è una gestione della retention meno comoda. Il beneficio è che esiste una copia che un attaccante — o un comando sbagliato — non può raggiungere.
Il restore è il backup
Un backup mai provato è un'ipotesi, non una garanzia. Il test va fatto con regolarità e va documentato. Una checklist ragionevole:
- Runbook scritto e disponibile anche quando il server è spento: stampato, o su un altro dispositivo.
- Restore su una macchina diversa, mai sopra il sistema in produzione.
- Tempo misurato: cronometrare l'operazione e confrontarla con il proprio RTO.
- Verifica del contenuto, non solo dell'esito:
restic check,pg_restore --list, conteggio delle righe di qualche tabella, apertura di un file a caso. - Restore parziale automatizzato con cadenza periodica, per esempio settimanale, in un ambiente di staging.
- Registro degli esiti: data, durata, cosa è stato ripristinato, cosa non tornava.
Un esempio di restore di prova:
restic restore latest --target /tmp/restore-test --include /var/backups/db
pg_restore --dbname=app_restore_test --clean --if-exists \
/tmp/restore-test/var/backups/db/app_$(date +%F).dump
psql app_restore_test -c "select count(*) from ordini where data > now() - interval '30 days';"
Il numero che esce va confrontato con la produzione. Se è zero, o se manca una tabella, meglio scoprirlo il martedì pomeriggio che durante un guasto.
Monitoraggio: un backup che fallisce in silenzio
Il fallimento tipico non è rumoroso. Un cron che non trova più spazio su disco, una credenziale scaduta, un container che non parte: tutto tace, e il backup semplicemente non c'è più.
Tre contromisure semplici:
- Exit code controllato: il job deve uscire con codice diverso da zero in caso di errore, e qualcuno deve accorgersene.
- Dead man's switch: invece di aspettare la notifica di errore, si notifica il successo. Se il segnale non arriva entro la finestra prevista, scatta l'allarme. È l'unico modo affidabile per accorgersi di un job che non parte proprio.
- Alert sull'età dell'ultimo backup: una metrica che dice da quante ore non arriva un ripristino valido, con soglia vicina all'RPO dichiarato.
Errori comuni
| Errore | Perché fa male | Cosa fare |
|---|---|---|
| Backup solo accanto al server | un furto, un incendio o una sovratensione portano via tutto | almeno una copia fuori sede |
| Snapshot come unica copia | sparisce con lo storage che protegge | copia su supporto indipendente |
| Job che sembra girare | log assenti, exit code ignorato | log strutturati, alert, dead man's switch |
| Restore mai provato | il giorno del guasto è il primo test | test periodico documentato |
| Password del repository persa | i dati non sono recuperabili | custodia separata e ridondante |
| Backup non cifrato | dati sensibili su supporto che circola | cifratura lato client |
| Retention infinita | costi e spazio crescono senza controllo | policy esplicita di conservazione |
| Copia a caldo dei file di database | backup incoerente, restore che fallisce | dump logico o API dedicate |
Conclusione
- Il backup si giudica dal restore, non dalla spunta verde sul job. Provalo, cronometralo, documentalo.
- Parti da RPO e RTO: sono decisioni, non parametri tecnici, e guidano tutto il resto.
- Applica 3-2-1, meglio se nella versione 3-2-1-1-0 con una copia immutabile e zero errori verificati.
- Ricorda che RAID, snapshot e sincronizzazione non sono backup: risolvono problemi diversi.
- Prendi i database con gli strumenti del database, non con una copia di file.
- Custodisci la password del repository fuori dal repository, altrimenti hai cifrato i tuoi dati e gettato la chiave.
- Configura il monitoraggio sul silenzio, non sull'errore: un backup che non parte non manda nessuna email.
Foto di copertina di benjamin lehman su Unsplash.