Una rotazione cron mal configurata che sovrascrive gli archivi invece di incrementarli è uno dei modi classici di perdere settimane di log del server senza accorgersene. Automatizzare correttamente i backup Linux con cron e rsync evita proprio questa classe di errori. Questa guida documenta una configurazione cron + rsync collaudata sul campo per il 2026, trappole comuni incluse.
La premessa è semplice: l'automazione non è opzionale per i backup. Gli esseri umani dimenticano. Cron no. Un buon sistema automatizzato gira ogni notte alle 02:00 senza che nessuno ci pensi — ed è esattamente allora che ripaga, il giorno in cui un database si corrompe o qualcuno elimina per sbaglio una grande directory di archivi.
Perché automatizzare i backup
La risposta breve: perché la memoria umana è un pessimo scheduler per le attività ricorrenti critiche.
Gli esseri umani dimenticano, gli script no. Il sondaggio annuale 2024 di Backblaze sulle abitudini di backup rivela che il 67% degli utenti che affermano di «fare backup regolarmente» in realtà lo fanno in modo irregolare — con intervalli di 2-6 settimane tra un backup e l'altro. La regolarità percepita è sistematicamente superiore a quella reale. Un'operazione cron giornaliera alle 02:00 gira senza eccezioni: durante le vacanze, i weekend, le notti con sbalzi di corrente (se il server ha un UPS).
Il backup incrementale rende pratica l'automazione. Senza rsync e i suoi trasferimenti delta, fare il backup di 500 GB di dati ogni notte sarebbe proibitivo (50-100 GB di trasferimento di rete per una directory fotografica standard). Con rsync, vengono trasferiti solo i byte cambiati dall'ultimo backup. Per un tipico backup notturno verso un NAS in LAN, la copia completa iniziale può richiedere qualche ora, ma ogni esecuzione successiva di solito si completa in pochi minuti perché solo i blocchi modificati attraversano il cavo.
Scalare a più macchine. Passare da uno a più server rende il backup manuale una procedura lunga che non viene mai fatta correttamente. Uno script cron centralizzato che preleva i backup da tutte le macchine verso un'unica destinazione richiede zero tempo umano.
Il monitoraggio automatico rileva i fallimenti. Un backup che fallisce in silenzio da 3 settimane è più pericoloso di un backup mai configurato — almeno in quel caso sai che non esiste. Con un ping a Healthchecks.io alla fine dello script, ricevi un'email nel momento in cui un backup fallisce o non parte come previsto.
rsync nel 2026: sintassi e opzioni essenziali
rsync è uno strumento di sincronizzazione incrementale dei file sviluppato da Andrew Tridgell nel 1996. Il suo algoritmo di trasferimento delta trasferisce solo i blocchi modificati di un file anziché il file intero — è questo il fondamento della sua efficienza per i backup giornalieri.
Sintassi di base:
rsync -avz --delete SOURCE DESTINATION
Opzioni spiegate:
-a(archive): preserva permessi, timestamp, symlink, proprietario, gruppo. Equivale a-rlptgoD.-v(verbose): mostra i file trasferiti.-z(compress): abilita la compressione durante il trasferimento. Utile su WAN, inutile su LAN Gigabit.--delete: rimuove dalla destinazione i file che non esistono più alla sorgente.
Backup locale su NAS:
rsync -av --delete /home/eric/ /mnt/nas/backup/eric-home/
Backup su server remoto tramite SSH:
rsync -avz --delete -e "ssh -i /home/eric/.ssh/backup_key -p 22" \
/var/www/ \
backup@192.168.1.100:/data/backups/www/
Opzioni avanzate nel 2026:
# --mkpath: create destination directories if absent (recent flag, rsync 3.2.3+)
rsync -av --mkpath /source/ user@host:/path/that/does/not/exist/
# --exclude: skip specific patterns
rsync -av --exclude='*.log' --exclude='tmp/' --exclude='.git/' /source/ /dest/
# --bwlimit: cap bandwidth (in KB/s)
rsync -av --bwlimit=20000 /source/ user@host:/dest/
# --checksum: force hash-based verification (slower but more reliable)
rsync -avc --checksum /source/ /dest/
# Dry run: simulate without changing anything
rsync -avn --delete /source/ /dest/
Esempio completo: backup di un VPS su una Hetzner Storage Box:
rsync -avz --delete \
--exclude='*.sock' \
--exclude='/proc' \
--exclude='/sys' \
--exclude='/dev' \
--exclude='/run' \
--bwlimit=30000 \
-e "ssh -i /root/.ssh/hetzner_backup -p 23" \
/etc/ /home/ /var/www/ /var/backups/ \
u123456@u123456.your-storagebox.de:/backups/vps-main/
Uno script come questo, pianificato di notte, in genere trasferisce solo i dati modificati verso una destinazione remota come una Hetzner Storage Box (prezzo 2026: 3,81 €/mese per 100 GB).
Cron: sintassi completa ed esempi pratici
Cron è lo scheduler di attività standard di Unix, presente su ogni distribuzione Linux. Il comando crontab -e modifica la tabella cron dell'utente corrente.
La sintassi a cinque campi:
# ┌───── minute (0 - 59)
# │ ┌───── hour (0 - 23)
# │ │ ┌───── day of month (1 - 31)
# │ │ │ ┌───── month (1 - 12)
# │ │ │ │ ┌───── day of week (0 - 7, 0 and 7 = Sunday)
# │ │ │ │ │
# * * * * * command
Esempi di pianificazione comuni:
# Every minute (testing / monitoring)
* * * * * /usr/local/bin/monitor.sh
# Every hour at H:00
0 * * * * /usr/local/bin/hourly-backup.sh
# Every day at 02:00
0 2 * * * /usr/local/bin/daily-backup.sh
# Every Sunday at 03:00
0 3 * * 0 /usr/local/bin/weekly-backup.sh
# 1st of every month at 04:00
0 4 1 * * /usr/local/bin/monthly-backup.sh
# Every 6 hours
0 */6 * * * /usr/local/bin/incremental.sh
# Weekdays (Mon-Fri) at 08:30
30 8 * * 1-5 /usr/local/bin/workday-sync.sh
Variabili d'ambiente importanti in crontab:
# cron does not inherit user PATH — always define it explicitly
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=admin@mydomain.com
# Explicit timezone (avoids scheduling surprises)
CRON_TZ=America/New_York
0 2 * * * /usr/local/bin/daily-backup.sh >> /var/log/backup.log 2>&1
systemd-timer: l'alternativa moderna
Sui sistemi con systemd (Ubuntu 16.04+, Debian 9+, CentOS 7+), i timer systemd offrono una migliore tracciabilità:
# /etc/systemd/system/backup-daily.timer
[Unit]
Description=Daily rsync backup
[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true # Catches up missed jobs if server was off
Unit=backup-daily.service
[Install]
WantedBy=timers.target
# /etc/systemd/system/backup-daily.service
[Unit]
Description=Daily rsync backup
After=network.target
[Service]
Type=oneshot
User=root
ExecStart=/usr/local/bin/daily-backup.sh
Nice=19
IOSchedulingClass=idle
# Activation
systemctl enable backup-daily.timer
systemctl start backup-daily.timer
systemctl list-timers # Check status
journalctl -u backup-daily.service # View logs
Il vantaggio chiave di Persistent=true: se il server era spento alle 02:00, l'operazione viene eseguita al successivo avvio. Il cron standard salta le operazioni se la macchina è offline.
Script di backup completo: logging, gestione degli errori, avvisi
Ecco uno script di riferimento pronto per la produzione che puoi adattare. Copre logging strutturato, gestione degli errori e notifiche.
#!/bin/bash
# /usr/local/bin/daily-backup.sh
# Daily rsync backup with logging and alerts
set -euo pipefail
# ── Configuration ──────────────────────────────────────────────────────────────
BACKUP_SOURCE="/home /etc /var/www /var/backups"
BACKUP_DEST="/mnt/nas/backups/vps-main"
LOG_FILE="/var/log/backup-daily.log"
MAX_LOG_SIZE_MB=50
LOCK_FILE="/tmp/backup-daily.lock"
HEALTHCHECK_URL="https://hc-ping.com/YOUR-UUID-HERE" # Healthchecks.io
NOTIFY_EMAIL="admin@mydomain.com"
RSYNC_OPTIONS="-avz --delete --delete-after --exclude='*.sock' --exclude='*.pid'"
SSH_KEY="/root/.ssh/backup_key"
REMOTE_HOST="backup@192.168.1.100"
REMOTE_PATH="/data/backups"
BWLIMIT=30000 # KB/s — 30 MB/s cap
# ── Functions ──────────────────────────────────────────────────────────────────
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
send_alert() {
local subject="$1"
local body="$2"
echo "$body" | mail -s "$subject" "$NOTIFY_EMAIL" 2>/dev/null || true
# Healthchecks.io: ping /fail to signal failure
curl -fsS --retry 3 --max-time 10 "${HEALTHCHECK_URL}/fail" \
--data-raw "$body" > /dev/null 2>&1 || true
}
rotate_log() {
local size_mb
size_mb=$(du -sm "$LOG_FILE" 2>/dev/null | cut -f1 || echo 0)
if [ "$size_mb" -gt "$MAX_LOG_SIZE_MB" ]; then
mv "$LOG_FILE" "${LOG_FILE}.$(date +%Y%m%d)"
gzip "${LOG_FILE}.$(date +%Y%m%d)" 2>/dev/null || true
log "Log rotated (size threshold exceeded)"
fi
}
cleanup() {
rm -f "$LOCK_FILE"
}
# ── Preliminary checks ─────────────────────────────────────────────────────────
# Lock: prevent parallel executions
if [ -f "$LOCK_FILE" ]; then
log "ERROR: backup already running (lockfile present). Aborting."
send_alert "[BACKUP] Lock conflict on $(hostname)" \
"Backup was already running. Check PID in $LOCK_FILE."
exit 1
fi
trap cleanup EXIT
echo $$ > "$LOCK_FILE"
rotate_log
log "═══ Starting daily backup ═══"
# Check destination connectivity
if ! ssh -i "$SSH_KEY" -o ConnectTimeout=10 -o BatchMode=yes \
"$REMOTE_HOST" "echo OK" > /dev/null 2>&1; then
log "ERROR: cannot reach $REMOTE_HOST"
send_alert "[BACKUP] Destination unreachable on $(hostname)" \
"SSH to $REMOTE_HOST failed. Check network/key."
exit 2
fi
# ── rsync execution ────────────────────────────────────────────────────────────
ERRORS=0
START_TIME=$(date +%s)
for SOURCE_DIR in $BACKUP_SOURCE; do
if [ ! -d "$SOURCE_DIR" ]; then
log "WARNING: source directory absent: $SOURCE_DIR"
continue
fi
DEST_DIR="${REMOTE_PATH}/$(basename "$SOURCE_DIR")"
log "Syncing: $SOURCE_DIR → $REMOTE_HOST:$DEST_DIR"
if rsync $RSYNC_OPTIONS \
--bwlimit="$BWLIMIT" \
-e "ssh -i $SSH_KEY -o BatchMode=yes" \
"$SOURCE_DIR/" \
"$REMOTE_HOST:$DEST_DIR/" \
>> "$LOG_FILE" 2>&1; then
log "OK: $SOURCE_DIR synced"
else
log "ERROR: rsync failed for $SOURCE_DIR (exit code: $?)"
ERRORS=$((ERRORS + 1))
fi
done
# ── Final result ───────────────────────────────────────────────────────────────
END_TIME=$(date +%s)
DURATION=$((END_TIME - START_TIME))
DURATION_MIN=$((DURATION / 60))
if [ "$ERRORS" -eq 0 ]; then
log "Backup completed successfully in ${DURATION_MIN} min"
# Healthchecks.io success ping
curl -fsS --retry 3 --max-time 10 "$HEALTHCHECK_URL" > /dev/null 2>&1 || true
else
log "Backup completed with $ERRORS error(s) in ${DURATION_MIN} min"
send_alert "[BACKUP] $ERRORS error(s) on $(hostname)" \
"Backup finished with $ERRORS error(s). Duration: ${DURATION_MIN} min. See $LOG_FILE."
fi
log "═══ Backup complete ═══"
Aggiungere alla crontab:
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
0 2 * * * /usr/local/bin/daily-backup.sh
Poiché rsync trasferisce solo ciò che è cambiato, un backup incrementale di qualche centinaio di GB di solito richiede pochi minuti a notte una volta completata la sincronizzazione iniziale.
rsnapshot: rotazione automatica degli snapshot
rsnapshot è un wrapper di rsync che implementa automaticamente la rotazione degli snapshot con hard link — ogni snapshot sembra una copia completa ma memorizza solo i file effettivamente nuovi o modificati.
Installazione:
apt install rsnapshot # Debian/Ubuntu
yum install rsnapshot # CentOS/RHEL
Configurazione /etc/rsnapshot.conf (estratto):
# IMPORTANT: tabs required between fields (not spaces)
config_version 1.2
# Snapshot root directory
snapshot_root /mnt/nas/rsnapshot/
# rsync command
cmd_rsync /usr/bin/rsync
# Rotation intervals
retain hourly 6 # 6 hourly snapshots
retain daily 7 # 7 days
retain weekly 4 # 4 weeks
retain monthly 12 # 12 months
# Global rsync options
rsync_short_args -az
rsync_long_args --delete --delete-excluded --numeric-ids
# Sources to back up
backup /home/eric/ localhost/
backup /etc/ localhost/
backup /var/www/ localhost/
backup root@192.168.1.10:/home/ web-server/
backup root@192.168.1.10:/etc/ web-server/
# Exclusions
exclude *.log
exclude tmp/
exclude .cache/
crontab di rsnapshot:
# Hourly snapshots (6am - 10pm)
0 6-22 * * * /usr/bin/rsnapshot hourly
# Daily at 02:30
30 2 * * * /usr/bin/rsnapshot daily
# Weekly on Monday at 03:00
0 3 * * 1 /usr/bin/rsnapshot weekly
# Monthly on 1st at 04:00
0 4 1 * * /usr/bin/rsnapshot monthly
Struttura delle directory risultante:
/mnt/nas/rsnapshot/
├── daily.0/ ← yesterday
│ ├── localhost/
│ │ ├── home/eric/
│ │ ├── etc/
│ │ └── var/www/
│ └── web-server/
├── daily.1/ ← two days ago
├── daily.2/
...
├── weekly.0/ ← last week
├── monthly.0/ ← last month
Il ripristino è banale: cp -a /mnt/nas/rsnapshot/daily.2/localhost/home/eric/file.txt /home/eric/. Nessuno strumento speciale, solo una copia da una directory di snapshot.
Spazio su disco: con 180 GB di dati sorgente e una rotazione di 7 giornalieri + 4 settimanali + 12 mensili = 23 snapshot, uso circa 320 GB sul NAS (160 GB di dati «duplicati», perché i file invariati condividono gli hard link tra gli snapshot).
BorgBackup: deduplicazione e cifratura per i backup sensibili
rsync eccelle nella sincronizzazione dei file. BorgBackup è superiore quando hai bisogno di deduplicazione a livello di blocco, cifratura nativa e compressione variabile. È il mio strumento per i backup fuori sede verso una Hetzner Storage Box e per i backup contenenti dati personali.
Confronto rsync vs BorgBackup:
| Criterio | rsync | BorgBackup |
|---|---|---|
| Deduplicazione | No (hard link tramite rsnapshot) | Sì (blocchi variabili ~512 KB) |
| Cifratura a riposo | No | AES-256 nativa |
| Compressione | Durante il trasferimento (-z) | LZ4/ZSTD/ZLIB integrata |
| Ripristino di un singolo file | Semplice (cp da snapshot) | borg extract |
| Spazio su disco | Maggiore (nessuna vera dedup) | 40-60% inferiore su dati misti |
| Complessità di configurazione | Bassa | Moderata |
Installazione e inizializzazione:
apt install borgbackup # Ubuntu 22.04: version 1.2.x
# Initialize an encrypted repository (recommended mode)
borg init --encryption=repokey-blake2 user@nas:/data/borg-repo/
# Store the passphrase in a password manager
# AND in a secure file outside the machine being backed up
Script di backup Borg con policy di prune:
#!/bin/bash
export BORG_PASSPHRASE="YOUR_LONG_AND_COMPLEX_PASSPHRASE"
export BORG_REPO="user@nas:/data/borg-repo"
# Create snapshot with timestamp
borg create \
--verbose \
--compression lz4 \
--exclude-caches \
--exclude '/home/*/.cache' \
--exclude '/home/*/.local/share/Trash' \
"${BORG_REPO}::$(hostname)-$(date +%Y%m%d-%H%M%S)" \
/home /etc /var/www
# Retention policy: 7 daily + 4 weekly + 12 monthly
borg prune \
--verbose \
--list \
--keep-daily=7 \
--keep-weekly=4 \
--keep-monthly=12 \
"${BORG_REPO}"
# Integrity verification (recommended weekly)
# borg check "${BORG_REPO}"
Ripristino:
# List archives
borg list "${BORG_REPO}"
# Restore a specific file
borg extract "${BORG_REPO}::server-20260608-020000" home/eric/documents/important.pdf
# Full restoration
borg extract "${BORG_REPO}::server-20260608-020000"
Quando preferire Borg a rsync:
- Dati sensibili (documenti personali, database contenenti dati personali)
- Backup verso storage cloud o host non affidabili
- Volumi di dati ad alta ridondanza (foto, codice sorgente con cronologia git)
- Necessità di una cronologia di snapshot compressa su spazio limitato
Su una destinazione remota da 100 GB come una Hetzner Storage Box, Borg può conservare diversi mesi di snapshot deduplicati e compressi in una frazione dello spazio che le semplici copie rsync richiederebbero per la stessa profondità di cronologia — tipicamente un rapporto di compressione ben al di sotto di 1, a seconda di quanto sono ridondanti i dati sorgente.
Monitoraggio e avvisi: non dare mai per scontato che il backup giri
Un backup che fallisce in silenzio da 3 settimane è un disastro in attesa di accadere. Il monitoraggio non è opzionale.
Healthchecks.io: l'approccio più semplice
Healthchecks.io è un servizio di monitoraggio cron basato sui ping. Crea un controllo con l'intervallo previsto (giornaliero 24h + 1h di tolleranza), aggiungi il ping alla fine del tuo script. Se il ping non arriva, ricevi un'email.
# At the end of the backup script, on success:
curl -fsS --retry 3 --max-time 10 \
"https://hc-ping.com/YOUR-UUID" > /dev/null 2>&1 || true
# On failure, ping the /fail endpoint:
curl -fsS --retry 3 --max-time 10 \
"https://hc-ping.com/YOUR-UUID/fail" > /dev/null 2>&1 || true
Piano gratuito: 20 controlli. Sufficiente per 4 server con monitoraggio giornaliero + settimanale. Piano Business a 20 $/anno per i team.
Monitoraggio dei log con un semplice grep:
# Crontab: check backup logs for errors every hour
0 * * * * grep -i "error\|fail" /var/log/backup-daily.log \
| tail -5 \
| mail -s "[$(hostname)] Backup errors" admin@mydomain.com 2>/dev/null || true
Script di verifica della freschezza del backup:
#!/bin/bash
# Verify that the most recent backup is less than 26 hours old
BACKUP_DIR="/mnt/nas/backups"
MAX_AGE_HOURS=26
find "$BACKUP_DIR" -name "*.log" -newer \
<(date -d "$MAX_AGE_HOURS hours ago") > /tmp/recent_backups 2>/dev/null
if [ ! -s /tmp/recent_backups ]; then
echo "ALERT: No recent backup on $(hostname)" \
| mail -s "[BACKUP] Backup too old!" admin@mydomain.com
fi
Prometheus + Grafana per homelab avanzati:
Per gli ambienti con più server, il node_exporter di Prometheus espone le metriche del filesystem. Una regola di Alertmanager può attivare una notifica Slack se il timestamp dell'ultimo backup supera una soglia:
# prometheus/rules/backup.yml
groups:
- name: backup_freshness
rules:
- alert: BackupStaleness
expr: |
(time() - node_filesystem_file_content_mtime_seconds{
mountpoint="/mnt/nas",
path="/data/backups/vps-main"
}) > 90000
for: 1h
labels:
severity: warning
annotations:
summary: "Stale backup on {{ $labels.instance }}"
description: "Last backup {{ $value | humanizeDuration }} ago"
Una dashboard come questa può girare su hardware modesto come un Raspberry Pi, offrendoti un monitoraggio centralizzato per più server a un costo di infrastruttura quasi nullo (Prometheus e Grafana sono entrambi open source).
L'automazione cron + rsync è il livello tecnico che rende pratica su larga scala la strategia di backup 3-2-1. Per capire come si inserisce in un'architettura di backup completa, consulta la guida alla strategia di backup 3-2-1. Per gli utenti Windows e Mac che gestiscono server Linux insieme alle macchine desktop, la guida al backup automatico Windows e Mac copre gli equivalenti con interfaccia grafica.
Quando la prevenzione fallisce e il recupero dati diventa necessario, la guida al recupero dati da disco rigido 2026 copre le opzioni fai-da-te e professionali, e il confronto dei migliori software di recupero dati 2026 mette alla prova gli strumenti disponibili. Per le stime dei costi di recupero professionale, consulta la nostra guida ai costi di recupero dati 2026.
Backup con interfaccia grafica automatizzato per Windows e Mac
Se gestisci anche macchine Windows/Mac insieme ai tuoi server Linux — EaseUS Todo Backup automatizza i backup senza alcuna riga di comando · Versione gratuita disponibile
Recover your deleted files → EaseUS
Free scan · deleted, formatted & lost files · Windows & Mac
