En 2017, perdí dos semanas de logs de servidor porque una rotación cron mal configurada sobreescribía los archivos en lugar de incrementarlos. Desde entonces, administro 4 servidores Linux personales — dos VPS en Hetzner, un NAS QNAP doméstico y una Raspberry Pi 4 como backup local — con un sistema de backup automatizado cron + rsync que lleva funcionando siete años sin interrupciones. Esta guía documenta exactamente lo que uso en 2026, errores incluidos.
La premisa es simple: la automatización no es opcional para los backups. Los humanos olvidan. Los scripts de cron, no. En mis 4 servidores, los backups se ejecutan a las 02:00 cada noche — no pienso en ellos desde 2018, pero he restaurado datos con éxito dos veces en 2024 (una corrupción de base de datos PostgreSQL y una eliminación accidental de 180 GB de archivos de fotos).
Por qué automatizar los backups
La respuesta corta: porque la memoria humana es un mal planificador de tareas recurrentes críticas.
Los humanos olvidan, los scripts no. La encuesta anual 2024 de Backblaze sobre hábitos de backup revela que el 67% de los usuarios que afirman hacer copias de seguridad "regularmente" lo hacen en realidad de forma irregular — con intervalos de 2 a 6 semanas entre backups. La regularidad percibida es sistemáticamente superior a la real. Un job cron diario a las 02:00 funciona sin excepción: durante las vacaciones, los fines de semana, las noches con cortes de luz (si el servidor tiene UPS).
El backup incremental hace viable la automatización. Sin rsync y sus transferencias delta, hacer backup de 500 GB de datos cada noche sería prohibitivo (50 a 100 GB de transferencia de red para un directorio de fotos estándar). Con rsync, solo se transfieren los bytes modificados desde el último backup. En mis backups nocturnos hacia el NAS LAN (180 GB de datos totales), la transferencia diaria oscila entre 200 MB y 2 GB según la actividad. El backup completo inicial tardó 4 horas. Cada backup posterior tarda entre 3 y 25 minutos.
Escalabilidad a múltiples máquinas. Pasar de 1 a 4 servidores convierte el backup manual en un procedimiento de 45 minutos que nunca se hace correctamente. Un script cron centralizado que extrae backups de todas las máquinas a un destino central requiere cero tiempo humano.
El monitoreo automático detecta fallos. Un backup que lleva 3 semanas fallando silenciosamente es más peligroso que uno nunca configurado — al menos, sabes que no existe. Con un ping a Healthchecks.io al final del script, recibo un email en el momento en que cualquier backup falla o no se ejecuta a la hora prevista.
rsync en 2026: sintaxis y opciones esenciales
rsync es una herramienta de sincronización incremental de archivos desarrollada por Andrew Tridgell en 1996. Su algoritmo delta-transfer transfiere únicamente los bloques modificados de un archivo en lugar del archivo completo — este es el fundamento de su eficiencia para backups diarios.
Sintaxis básica:
rsync -avz --delete ORIGEN DESTINO
Opciones explicadas:
-a(archive): preserva permisos, marcas de tiempo, enlaces simbólicos, propietario, grupo. Equivalente a-rlptgoD.-v(verbose): muestra los archivos transferidos.-z(compress): activa compresión durante la transferencia. Útil en WAN, inútil en LAN Gigabit.--delete: elimina en el destino los archivos que ya no existen en el origen.
Backup local al NAS:
rsync -av --delete /home/eric/ /mnt/nas/backup/eric-home/
Backup a servidor remoto vía SSH:
rsync -avz --delete -e "ssh -i /home/eric/.ssh/backup_key -p 22" \
/var/www/ \
backup@192.168.1.100:/data/backups/www/
Opciones avanzadas 2026:
# --mkpath: crea directorios destino si no existen (flag reciente, rsync 3.2.3+)
rsync -av --mkpath /origen/ user@host:/ruta/que/no/existe/
# --exclude: omitir patrones específicos
rsync -av --exclude='*.log' --exclude='tmp/' --exclude='.git/' /origen/ /dest/
# --bwlimit: limitar ancho de banda (en KB/s)
rsync -av --bwlimit=20000 /origen/ user@host:/dest/
# --checksum: forzar verificación por hash (más lento pero más fiable)
rsync -avc --checksum /origen/ /dest/
# Dry run: simular sin modificar nada
rsync -avn --delete /origen/ /dest/
Ejemplo completo: backup VPS a 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/
Este script lleva ejecutándose en mi VPS principal desde 2021. Transfiere una media de 800 MB por noche al Storage Box de Hetzner (precio 2026: 3,81 €/mes por 100 GB).
Cron: sintaxis completa y ejemplos prácticos
Cron es el planificador de tareas Unix estándar, presente en todas las distribuciones Linux. El comando crontab -e edita la tabla de tareas cron del usuario actual.
La sintaxis de cinco campos:
# ┌───── minuto (0 - 59)
# │ ┌───── hora (0 - 23)
# │ │ ┌───── día del mes (1 - 31)
# │ │ │ ┌───── mes (1 - 12)
# │ │ │ │ ┌───── día de la semana (0 - 7, 0 y 7 = domingo)
# │ │ │ │ │
# * * * * * comando
Ejemplos de horarios habituales:
# Cada minuto (pruebas / monitoreo)
* * * * * /usr/local/bin/monitor.sh
# Cada hora a H:00
0 * * * * /usr/local/bin/hourly-backup.sh
# Todos los días a las 02:00
0 2 * * * /usr/local/bin/daily-backup.sh
# Todos los domingos a las 03:00
0 3 * * 0 /usr/local/bin/weekly-backup.sh
# El día 1 de cada mes a las 04:00
0 4 1 * * /usr/local/bin/monthly-backup.sh
# Cada 6 horas
0 */6 * * * /usr/local/bin/incremental.sh
# Días laborables (lunes-viernes) a las 08:30
30 8 * * 1-5 /usr/local/bin/workday-sync.sh
Variables de entorno importantes en crontab:
# cron no hereda el PATH del usuario — siempre definirlo explícitamente
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=admin@midominio.com
# Zona horaria explícita (evita sorpresas de programación)
CRON_TZ=Europe/Madrid
0 2 * * * /usr/local/bin/daily-backup.sh >> /var/log/backup.log 2>&1
systemd-timers: la alternativa moderna
En sistemas con systemd (Ubuntu 16.04+, Debian 9+, CentOS 7+), los timers de systemd ofrecen mejor trazabilidad:
# /etc/systemd/system/backup-daily.timer
[Unit]
Description=Backup diario rsync
[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true # Recupera los jobs perdidos si el servidor estaba apagado
Unit=backup-daily.service
[Install]
WantedBy=timers.target
# /etc/systemd/system/backup-daily.service
[Unit]
Description=Backup diario rsync
After=network.target
[Service]
Type=oneshot
User=root
ExecStart=/usr/local/bin/daily-backup.sh
Nice=19
IOSchedulingClass=idle
# Activación
systemctl enable backup-daily.timer
systemctl start backup-daily.timer
systemctl list-timers # Verificar estado
journalctl -u backup-daily.service # Ver logs
La ventaja clave de Persistent=true: si el servidor estaba apagado a las 02:00, el job se ejecuta en el siguiente arranque. Cron estándar pierde los jobs si la máquina está fuera de línea.
Script de backup completo: logging, gestión de errores, alertas
Este es el script que uso en mis servidores desde 2019, mejorado iterativamente. Cubre logging estructurado, gestión de errores y notificaciones.
#!/bin/bash
# /usr/local/bin/daily-backup.sh
# Backup diario rsync con logging y alertas
set -euo pipefail
# ── Configuración ──────────────────────────────────────────────────────────────
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/TU-UUID-AQUI" # Healthchecks.io
NOTIFY_EMAIL="admin@midominio.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 — máximo 30 MB/s
# ── Funciones ──────────────────────────────────────────────────────────────────
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
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 rotado (umbral de tamaño superado)"
fi
}
cleanup() {
rm -f "$LOCK_FILE"
}
# ── Verificaciones previas ─────────────────────────────────────────────────────
if [ -f "$LOCK_FILE" ]; then
log "ERROR: backup ya en ejecución (lockfile presente). Abortando."
send_alert "[BACKUP] Conflicto de lockfile en $(hostname)" \
"El backup ya estaba en ejecución. Verifica el PID en $LOCK_FILE."
exit 1
fi
trap cleanup EXIT
echo $$ > "$LOCK_FILE"
rotate_log
log "═══ Inicio backup diario ═══"
if ! ssh -i "$SSH_KEY" -o ConnectTimeout=10 -o BatchMode=yes \
"$REMOTE_HOST" "echo OK" > /dev/null 2>&1; then
log "ERROR: no se puede alcanzar $REMOTE_HOST"
send_alert "[BACKUP] Destino inaccesible en $(hostname)" \
"SSH hacia $REMOTE_HOST falló. Revisa red/clave."
exit 2
fi
# ── Ejecución rsync ────────────────────────────────────────────────────────────
ERRORS=0
START_TIME=$(date +%s)
for SOURCE_DIR in $BACKUP_SOURCE; do
if [ ! -d "$SOURCE_DIR" ]; then
log "AVISO: directorio fuente ausente: $SOURCE_DIR"
continue
fi
DEST_DIR="${REMOTE_PATH}/$(basename "$SOURCE_DIR")"
log "Sincronizando: $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 sincronizado"
else
log "ERROR: rsync falló para $SOURCE_DIR (código salida: $?)"
ERRORS=$((ERRORS + 1))
fi
done
# ── Resultado final ────────────────────────────────────────────────────────────
END_TIME=$(date +%s)
DURATION=$((END_TIME - START_TIME))
DURATION_MIN=$((DURATION / 60))
if [ "$ERRORS" -eq 0 ]; then
log "Backup completado con éxito en ${DURATION_MIN} min"
curl -fsS --retry 3 --max-time 10 "$HEALTHCHECK_URL" > /dev/null 2>&1 || true
else
log "Backup completado con $ERRORS error(es) en ${DURATION_MIN} min"
send_alert "[BACKUP] $ERRORS error(es) en $(hostname)" \
"Backup terminado con $ERRORS error(es). Duración: ${DURATION_MIN} min. Ver $LOG_FILE."
fi
log "═══ Fin del backup ═══"
Añadir a 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
Este script corre en 3 de mis 4 servidores. La duración media es de 8 minutos para 180 GB de datos totales (aproximadamente 600 MB transferidos por noche).
rsnapshot: rotación automática de snapshots
rsnapshot es un wrapper de rsync que implementa automáticamente la rotación de snapshots con hard links — cada snapshot parece una copia completa pero solo almacena realmente los archivos nuevos o modificados.
Instalación:
apt install rsnapshot # Debian/Ubuntu
yum install rsnapshot # CentOS/RHEL
Configuración /etc/rsnapshot.conf (extracto):
# IMPORTANTE: tabs obligatorios entre campos (no espacios)
config_version 1.2
snapshot_root /mnt/nas/rsnapshot/
cmd_rsync /usr/bin/rsync
retain hourly 6
retain daily 7
retain weekly 4
retain monthly 12
rsync_short_args -az
rsync_long_args --delete --delete-excluded --numeric-ids
backup /home/eric/ localhost/
backup /etc/ localhost/
backup /var/www/ localhost/
backup root@192.168.1.10:/home/ web-server/
exclude *.log
exclude tmp/
exclude .cache/
Crontab de rsnapshot:
0 6-22 * * * /usr/bin/rsnapshot hourly
30 2 * * * /usr/bin/rsnapshot daily
0 3 * * 1 /usr/bin/rsnapshot weekly
0 4 1 * * /usr/bin/rsnapshot monthly
La restauración es trivial: cp -a /mnt/nas/rsnapshot/daily.2/localhost/home/eric/archivo.txt /home/eric/. Sin herramientas especiales, solo copiar desde un directorio de snapshot.
Con 180 GB de datos fuente y una rotación 7 daily + 4 weekly + 12 monthly = 23 snapshots, uso aproximadamente 320 GB en el NAS (los archivos sin cambios comparten hard links entre snapshots).
BorgBackup: deduplicación y cifrado para backups sensibles
rsync es excelente para sincronización de archivos. BorgBackup es superior cuando se necesita deduplicación a nivel de bloque, cifrado nativo y compresión variable. Es mi herramienta para backups offsite en Hetzner Storage Box y copias de seguridad con datos personales.
Comparativa rsync vs BorgBackup:
| Criterio | rsync | BorgBackup |
|---|---|---|
| Deduplicación | No (hard links vía rsnapshot) | Sí (bloques variables ~512 KB) |
| Cifrado en reposo | No | AES-256 nativo |
| Compresión | Durante transferencia (-z) | LZ4/ZSTD/ZLIB integrado |
| Restauración archivo único | Simple (cp desde snapshot) | borg extract |
| Espacio en disco | Mayor (sin dedup real) | 40-60% inferior en datos mixtos |
| Complejidad de configuración | Baja | Moderada |
Instalación e inicialización:
apt install borgbackup # Ubuntu 22.04: versión 1.2.x
# Inicializar repositorio cifrado (modo recomendado)
borg init --encryption=repokey-blake2 user@nas:/data/borg-repo/
Script backup Borg con política de prune:
#!/bin/bash
export BORG_PASSPHRASE="TU_PASSPHRASE_LARGA_Y_COMPLEJA"
export BORG_REPO="user@nas:/data/borg-repo"
# Crear snapshot con 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
# Política de retención: 7 daily + 4 weekly + 12 monthly
borg prune \
--verbose \
--list \
--keep-daily=7 \
--keep-weekly=4 \
--keep-monthly=12 \
"${BORG_REPO}"
Restauración:
# Listar archivos
borg list "${BORG_REPO}"
# Restaurar un archivo específico
borg extract "${BORG_REPO}::servidor-20260608-020000" home/eric/documentos/importante.pdf
# Restauración completa
borg extract "${BORG_REPO}::servidor-20260608-020000"
Cuándo preferir Borg sobre rsync:
- Datos sensibles (documentos personales, bases de datos con PII)
- Backups hacia almacenamiento cloud o hosts no controlados
- Volúmenes de datos con alta redundancia (fotos, código fuente con historial git)
- Necesidad de historial de snapshots comprimidos en espacio limitado
En mi Hetzner Storage Box de 100 GB, almaceno 4 meses de snapshots Borg de 85 GB de datos fuente, comprimidos a 52 GB — una ratio de 0,61. Con rsync puro, necesitaría 4 veces más espacio para el mismo historial.
Monitorización y alertas: nunca asumir que el backup corre
Un backup que lleva 3 semanas fallando silenciosamente es un desastre esperando ocurrir. La monitorización no es opcional.
Healthchecks.io: el enfoque más sencillo
Healthchecks.io es un servicio de monitorización cron por ping. Crea un check con el intervalo esperado (daily 24h + 1h de gracia), añade el ping al final de tu script. Si el ping no llega, recibes un email.
# Al final del script, si éxito:
curl -fsS --retry 3 --max-time 10 \
"https://hc-ping.com/TU-UUID" > /dev/null 2>&1 || true
# En caso de fallo, pingar el endpoint /fail:
curl -fsS --retry 3 --max-time 10 \
"https://hc-ping.com/TU-UUID/fail" > /dev/null 2>&1 || true
Plan gratuito: 20 checks. Suficiente para 4 servidores con monitorización daily + weekly.
Script de verificación de frescura del backup:
#!/bin/bash
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 "ALERTA: Ningún backup reciente en $(hostname)" \
| mail -s "[BACKUP] Backup demasiado antiguo!" admin@midominio.com
fi
Prometheus + Grafana para homelabs avanzados:
Para entornos con múltiples servidores, el node_exporter de Prometheus expone métricas del sistema de archivos. Una regla de Alertmanager puede disparar una notificación Slack si el timestamp del último backup supera un umbral:
# 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: "Backup obsoleto en {{ $labels.instance }}"
description: "Último backup hace {{ $value | humanizeDuration }}"
Este dashboard Grafana corre en mi Raspberry Pi 4 (8 GB RAM), que también sirve como monitorización central para mis 4 servidores. Coste total de la infraestructura de monitorización: 0 € (Prometheus + Grafana open source, alojados en el Pi).
La automatización con cron + rsync es la capa técnica que hace viable la estrategia de backup 3-2-1 a escala. Para entender cómo encaja en una arquitectura de backup completa, consulta la guía estrategia backup 3-2-1. Para máquinas Windows y Mac, la guía backup automático Windows y Mac cubre los equivalentes con interfaz gráfica.
Cuando la prevención falla y la recuperación de datos es necesaria, la guía recuperación de datos disco duro 2026 documenta las opciones DIY y profesionales, y la comparativa mejores software de recuperación de datos 2026 compara las herramientas del mercado. Para estimar el coste de una recuperación profesional, consulta nuestra guía de precios recuperación datos disco duro 2026.
★ Éditeur fondé en 2004 · ✓ Garantie 30 jours · Version gratuite jusqu'à 2 Go
Backup automático con interfaz gráfica para Windows y MacSi también administras máquinas Windows/Mac junto a tus servidores Linux — EaseUS Todo Backup automatiza las copias sin línea de comandos · Versión gratuita disponible→★ Éditeur fondé en 2004 · ✓ Garantie 30 jours · Version gratuite jusqu'à 2 Go
Probar EaseUS Data Recovery Wizard30 jours satisfait ou remboursé→