Una rotación cron mal configurada que sobreescribe los archivos en lugar de incrementarlos es una de las formas clásicas de perder semanas de logs de servidor sin darse cuenta. Automatizar correctamente los backups Linux con cron y rsync evita precisamente ese tipo de error. Esta guía documenta una configuración cron + rsync lista para producción para 2026, errores comunes incluidos.
La premisa es simple: la automatización no es opcional para los backups. Los humanos olvidan. Los scripts de cron, no. Un buen sistema automatizado se ejecuta a las 02:00 cada noche sin que nadie piense en ello - y es precisamente entonces cuando marca la diferencia, el día en que una base de datos se corrompe o alguien elimina por accidente un directorio de archivos.
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/
Un script como este, programado cada noche, solo transfiere los datos modificados hacia un destino remoto como un 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 un script de referencia listo para producción que puedes adaptar. 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
Como rsync solo transfiere lo que ha cambiado, una copia incremental de unos pocos cientos de GB suele tardar solo unos minutos por noche una vez hecha la sincronización inicial.
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 un destino remoto de 100 GB como un Storage Box de Hetzner, Borg puede conservar varios meses de snapshots deduplicados y comprimidos en una fracción del espacio que requerirían copias rsync en bruto para el mismo historial - normalmente una ratio de compresión claramente inferior a 1, según la redundancia de los datos fuente.
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 }}"
Un dashboard como este puede correr en hardware tan modesto como una Raspberry Pi, ofreciendo monitorización central para varios servidores a un coste de infraestructura casi nulo (Prometheus y Grafana son ambos open source).
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.
Backup automático con interfaz gráfica para Windows y Mac
Si 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
Recupera tus archivos borrados → EaseUS
Escaneo gratis · archivos borrados, formateados, perdidos · Windows y Mac
