El backup con Python que construimos antes es perfecto para bases de datos y archivos que necesitan lógica de procesamiento. Pero para respaldos de archivos puros — directorios de datos, uploads de usuarios, configuraciones de servidor — rsync es más eficiente: solo transfiere los bytes que cambiaron, maneja permisos y ownership correctamente, y con hard links puedes mantener versiones diarias que ocupan una fracción del espacio total.
¿Por qué rsync?
rsync compara los archivos entre origen y destino y solo transfiere las diferencias. Cuando combinas rsync con hard links, obtienes lo mejor de los dos mundos — backups rápidos (solo se copian los cambios) con restauración simple (cada backup es una carpeta completa que puedes navegar y copiar directamente).
| Método | Primer backup 100 GB | Backup diario (500 MB cambios) | 30 días de backups |
|---|---|---|---|
| cp -r | 100 GB, 30 min | 100 GB, 30 min | 3 TB |
| tar.gz | 100 GB, 45 min | 100 GB, 45 min | 3 TB (comprimido ~1 TB) |
| rsync incremental | 100 GB, 30 min | 500 MB, 30 seg | ~115 GB |
Requisitos previos
rsync viene preinstalado en Ubuntu. Verifica:
rsync --version
Si respaldar a un servidor remoto, necesitas acceso SSH con llaves (sin contraseña).
Paso 1: rsync básico
Antes de automatizar, entiende el comando base:
rsync -avz --delete /origen/ /destino/
| Flag | Función |
|---|---|
-a | Archive — preserva permisos, ownership, timestamps, symlinks |
-v | Verbose — muestra los archivos que transfiere |
-z | Compress — comprime durante la transferencia (útil en remoto) |
--delete | Elimina archivos en destino que ya no existen en origen |
La barra final importa
/origen/ (con barra) copia el contenido del directorio. /origen (sin barra) copia el directorio mismo. La diferencia: /datos/ → los archivos de datos van directo al destino. /datos → se crea una carpeta datos dentro del destino. Para backups, generalmente quieres la barra.
Ejemplo: backup local
# Respaldar /var/www a /backup
rsync -avz --delete /var/www/ /backup/www/
Ejemplo: backup a servidor remoto
# Respaldar /var/www al servidor de backup
rsync -avz --delete -e "ssh -p 2222" /var/www/ backup@10.0.1.50:/backups/web/
Paso 2: Backups incrementales con hard links
La técnica de hard links es la joya de rsync para backups. Cada backup diario es una carpeta completa (puedes navegar y restaurar cualquier archivo directamente), pero los archivos que no cambiaron son hard links al backup anterior — no ocupan espacio adicional.
rsync -avz --delete \
--link-dest=/backup/latest \
/datos/ \
/backup/2026-01-08/
--link-dest le dice a rsync: "antes de copiar un archivo, verifica si es idéntico al que está en /backup/latest. Si es idéntico, crea un hard link en vez de copiar." El resultado: solo los archivos que cambiaron se copian realmente.
/backup/
├── 2026-01-06/ ← Backup del 6 (completo)
│ ├── archivo1.txt (inode 12345)
│ ├── archivo2.txt (inode 12346)
│ └── archivo3.txt (inode 12347)
├── 2026-01-07/ ← Backup del 7 (solo archivo2 cambió)
│ ├── archivo1.txt (inode 12345) ← Hard link, 0 bytes extra
│ ├── archivo2.txt (inode 12400) ← Copia nueva, cambió
│ └── archivo3.txt (inode 12347) ← Hard link, 0 bytes extra
├── 2026-01-08/ ← Backup del 8 (solo archivo1 cambió)
│ ├── archivo1.txt (inode 12500) ← Copia nueva, cambió
│ ├── archivo2.txt (inode 12400) ← Hard link al del 7
│ └── archivo3.txt (inode 12347) ← Hard link al del 6
└── latest -> 2026-01-08/ ← Symlink al más reciente
Cada carpeta parece un backup completo, pero solo los archivos modificados ocupan espacio real.
Paso 3: Script de backup completo
#!/bin/bash
# backup-rsync.sh — Backup incremental con rsync y hard links
# Uso: sudo ./backup-rsync.sh
set -euo pipefail
# =====================
# CONFIGURACIÓN
# =====================
BACKUP_ROOT="/backup"
SOURCE_DIRS=(
"/var/www"
"/etc/nginx"
"/etc/postgresql"
"/opt/apps"
"/home"
)
RETENTION_DAYS=30
LOG_DIR="${BACKUP_ROOT}/logs"
DATE=$(date +%Y-%m-%d_%H%M%S)
BACKUP_DIR="${BACKUP_ROOT}/${DATE}"
LATEST_LINK="${BACKUP_ROOT}/latest"
LOG_FILE="${LOG_DIR}/backup-${DATE}.log"
# Servidor remoto (dejar vacío para backup local)
REMOTE_HOST=""
REMOTE_DIR=""
# REMOTE_HOST="backup@10.0.1.50"
# REMOTE_DIR="/backups/servidor-web"
# Notificación Slack (opcional)
SLACK_WEBHOOK="${SLACK_WEBHOOK:-}"
# =====================
# FUNCIONES
# =====================
mkdir -p "$LOG_DIR"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
notify_slack() {
local message="$1"
local color="$2"
if [[ -n "$SLACK_WEBHOOK" ]]; then
curl -s -X POST "$SLACK_WEBHOOK" \
-H "Content-Type: application/json" \
-d "{\"attachments\":[{\"color\":\"${color}\",\"text\":\"${message}\"}]}" \
> /dev/null 2>&1 || true
fi
}
cleanup_old_backups() {
log "Limpiando backups con más de ${RETENTION_DAYS} días..."
local count=0
local target_dir="${1:-$BACKUP_ROOT}"
find "$target_dir" -maxdepth 1 -type d -name "20*" -mtime +${RETENTION_DAYS} | sort | while read -r dir; do
log " Eliminando: $(basename "$dir")"
rm -rf "$dir"
((count++)) || true
done
log " Backups eliminados: ${count}"
}
# =====================
# EJECUCIÓN
# =====================
log "=========================================="
log "BACKUP INICIADO — ${DATE}"
log "=========================================="
START_TIME=$(date +%s)
TOTAL_SIZE=0
ERRORS=0
# Crear directorio de backup
mkdir -p "$BACKUP_DIR"
# Construir --link-dest
LINK_DEST_FLAG=""
if [[ -L "$LATEST_LINK" ]] && [[ -d "$LATEST_LINK" ]]; then
LINK_DEST_FLAG="--link-dest=${LATEST_LINK}"
log "Usando link-dest: $(readlink -f "$LATEST_LINK")"
else
log "Sin link-dest (primer backup completo)"
fi
# Respaldar cada directorio
for SRC in "${SOURCE_DIRS[@]}"; do
if [[ ! -d "$SRC" ]]; then
log " ⚠ ${SRC} no existe, saltando..."
continue
fi
DEST_NAME=$(echo "$SRC" | tr '/' '_' | sed 's/^_//')
DEST="${BACKUP_DIR}/${DEST_NAME}"
mkdir -p "$DEST"
log "Respaldando: ${SRC} → ${DEST_NAME}"
if rsync -a --delete $LINK_DEST_FLAG \
--exclude='.cache' \
--exclude='node_modules' \
--exclude='__pycache__' \
--exclude='*.log' \
--exclude='.git' \
"$SRC/" "$DEST/" >> "$LOG_FILE" 2>&1; then
DIR_SIZE=$(du -sh "$DEST" | cut -f1)
log " ✓ ${SRC} completado (${DIR_SIZE})"
else
log " ✗ ERROR en ${SRC}"
((ERRORS++)) || true
fi
done
# Actualizar symlink latest
rm -f "$LATEST_LINK"
ln -s "$BACKUP_DIR" "$LATEST_LINK"
# Backup remoto (opcional)
if [[ -n "$REMOTE_HOST" ]] && [[ -n "$REMOTE_DIR" ]]; then
log "Sincronizando a servidor remoto: ${REMOTE_HOST}:${REMOTE_DIR}"
if rsync -az --delete \
-e "ssh -o StrictHostKeyChecking=no" \
"${BACKUP_DIR}/" \
"${REMOTE_HOST}:${REMOTE_DIR}/${DATE}/" >> "$LOG_FILE" 2>&1; then
log " ✓ Sincronización remota completada"
else
log " ✗ ERROR en sincronización remota"
((ERRORS++)) || true
fi
fi
# Limpieza
cleanup_old_backups "$BACKUP_ROOT"
# Resumen
END_TIME=$(date +%s)
DURATION=$((END_TIME - START_TIME))
TOTAL_SIZE=$(du -sh "$BACKUP_DIR" | cut -f1)
DISK_FREE=$(df -h "$BACKUP_ROOT" | tail -1 | awk '{print $4}')
log "=========================================="
log "BACKUP COMPLETADO"
log " Duración: ${DURATION}s"
log " Tamaño: ${TOTAL_SIZE}"
log " Errores: ${ERRORS}"
log " Espacio libre: ${DISK_FREE}"
log "=========================================="
# Notificación
if [[ $ERRORS -eq 0 ]]; then
notify_slack "✅ Backup completado | ${TOTAL_SIZE} | ${DURATION}s | $(hostname)" "#36a64f"
else
notify_slack "🚨 Backup con ${ERRORS} error(es) | $(hostname) | Ver log: ${LOG_FILE}" "#ff0000"
fi
exit $ERRORS
Paso 4: Configurar permisos y probar
# Hacer ejecutable
chmod +x backup-rsync.sh
# Probar manualmente
sudo ./backup-rsync.sh
Verifica la estructura de backups:
ls -la /backup/
# Deberías ver el directorio con fecha y el symlink latest
# Verificar que latest apunta al backup reciente
readlink -f /backup/latest
# Ver tamaño real vs aparente (hard links)
du -sh /backup/2026-01-08/ # Tamaño aparente (completo)
du -sh --apparent-size /backup/ # Tamaño real en disco
Paso 5: Automatizar con cron
sudo crontab -e
# Backup diario a las 2:00 AM
0 2 * * * /opt/scripts/backup-rsync.sh >> /var/log/backup-rsync-cron.log 2>&1
Paso 6: Restaurar archivos
La ventaja de rsync con hard links es que restaurar es trivial — cada backup es una carpeta navegable:
# Restaurar un archivo específico del backup de ayer
cp /backup/2026-01-07/var_www/uploads/documento.pdf /var/www/uploads/
# Restaurar un directorio completo
rsync -av /backup/2026-01-07/var_www/ /var/www/
# Restaurar desde el backup más reciente
rsync -av /backup/latest/var_www/ /var/www/
No necesitas herramientas especiales ni procesos de extracción — solo cp o rsync.
Paso 7: Backup a servidor remoto
Para cumplir con la regla 3-2-1, envía los backups a otro servidor. Modifica las variables del script:
REMOTE_HOST="backup@10.0.1.50"
REMOTE_DIR="/backups/servidor-web"
O ejecuta rsync manualmente para enviar el último backup:
rsync -avz -e "ssh -p 2222" /backup/latest/ backup@10.0.1.50:/backups/servidor-web/latest/
Ancho de banda
rsync con -z comprime los datos durante la transferencia. Para backups iniciales grandes sobre internet, considera ejecutarlo con --bwlimit=10000 (10 MB/s) para no saturar tu enlace. Los incrementales diarios suelen ser pequeños y no necesitan limitación.
Monitoreo de backups
Verificar que el backup se ejecutó
# Verificar la fecha del último backup
stat -c %y /backup/latest
# Verificar con Zabbix o Prometheus
# Item: antigüedad del último backup en minutos
find /backup -maxdepth 1 -type d -name "20*" -mmin -1500 | wc -l
# Si el resultado es 0, no hubo backup en las últimas 25 horas → alerta
Verificar integridad
# Comparar el backup con el origen (dry-run, no copia nada)
rsync -avn --delete /var/www/ /backup/latest/var_www/
# Si la salida está vacía, el backup es idéntico al origen
rsync vs herramientas especializadas
| Herramienta | Mejor para | Limitación |
|---|---|---|
| rsync | Archivos, directorios, configuraciones | No deduplica entre archivos diferentes |
| BorgBackup | Deduplicación avanzada, cifrado | Más complejo de configurar |
| restic | Backup cifrado a cloud (S3, B2) | Restauración más lenta que rsync |
| Veeam | VMs completas (Proxmox, VMware) | Licencia comercial |
| Python script | Bases de datos + lógica custom | Más código que mantener |
Para archivos de servidor y directorios de datos, rsync con hard links es difícil de superar en simplicidad y eficiencia.
Siguientes pasos
Con backups incrementales funcionando:
- BorgBackup — deduplicación y cifrado para backups que van a destinos no confiables
- Backup de PostgreSQL — combina este script de rsync para archivos con el de Python para bases de datos
- RAID — protección del disco de backups contra fallo de hardware
- Monitoreo — alertas si el backup no se ejecutó o si el disco de backups está lleno
- Infraestructura de backup profesional — estrategia 3-2-1 completa con Veeam, Proxmox Backup Server y pruebas de restauración
Respaldos empresariales
¿Tu estrategia de backup está completa y probada?
Diseñamos tu plan de backup con rsync, Veeam o Proxmox Backup Server, con retención, cifrado y pruebas de restauración periódicas.



