SRespaldos incrementales con rsync y scripts Bash
// syswork LOG · categoríaTutorialeslectura9 MIN

Respaldos incrementales con rsync y scripts Bash

Crea un sistema de backups incrementales con rsync y Bash que respalda archivos, conserva versiones históricas y se automatiza con cron.

Ilustración conceptual de respaldos incrementales con rsync y scripts Bash
Ilustración conceptual de respaldos incrementales con rsync y scripts Bash

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étodoPrimer backup 100 GBBackup diario (500 MB cambios)30 días de backups
cp -r100 GB, 30 min100 GB, 30 min3 TB
tar.gz100 GB, 45 min100 GB, 45 min3 TB (comprimido ~1 TB)
rsync incremental100 GB, 30 min500 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/
FlagFunción
-aArchive — preserva permisos, ownership, timestamps, symlinks
-vVerbose — muestra los archivos que transfiere
-zCompress — comprime durante la transferencia (útil en remoto)
--deleteElimina 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/

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

HerramientaMejor paraLimitación
rsyncArchivos, directorios, configuracionesNo deduplica entre archivos diferentes
BorgBackupDeduplicación avanzada, cifradoMás complejo de configurar
resticBackup cifrado a cloud (S3, B2)Restauración más lenta que rsync
VeeamVMs completas (Proxmox, VMware)Licencia comercial
Python scriptBases de datos + lógica customMá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.

Solicitar evaluación

// FAQ

Preguntas frecuentes

// Temas relacionados

#backup#rsync#bash#linux#servidores#tutorial

// Autor

Director de Tecnología

Rolando Delgado

Ingeniero y consultor tecnológico. Dirige la implementación de ERP, infraestructura IT y soluciones cloud para empresas en México.

¿Te fue útil? Compártelo

// Sigue leyendo

Artículos relacionados

Ver todos

// Consultoría gratuita

¿Necesitas una estrategia de backup profesional?

Diseñamos e implementamos tu plan de backup con rsync, Veeam o Proxmox Backup Server, con pruebas de restauración periódicas.

Solicitar evaluación