Script de Rotación de Backups en Bash
Backups automatizados con políticas de retención usando bash y find.
Visión General
La rotación de backups mantiene una cantidad fija de backups y borra los viejos automáticamente. Este script usa tar para compresión y find para limpieza. Soporta políticas de retención diaria, semanal y mensual para que siempre tengas backups recientes e históricos sin llenar el disco.
Cuándo Usar
- Necesitas backups automatizados para una web app o base de datos
- Quieres mantener backups diarios por 7 días, semanales por 4 semanas, mensuales por 6 meses
- Estás configurando un cron job para backups periódicos
- Quieres archivos comprimidos con limpieza automática
Solución
Backup básico con timestamp
#!/bin/bash
SOURCE_DIR="/var/www/myapp"
BACKUP_DIR="/backups"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
tar -czf "${BACKUP_DIR}/backup_${TIMESTAMP}.tar.gz" -C "${SOURCE_DIR}" .
echo "Backup created: backup_${TIMESTAMP}.tar.gz"
Backup con política de retención
#!/bin/bash
SOURCE_DIR="/var/www/myapp"
BACKUP_DIR="/backups"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="${BACKUP_DIR}/backup_${TIMESTAMP}.tar.gz"
# Crear backup
tar -czf "${BACKUP_FILE}" -C "${SOURCE_DIR}" .
# Retención: mantener últimos 7 backups diarios
find "${BACKUP_DIR}" -name "backup_*.tar.gz" -type f -mtime +7 -delete
echo "Backup created: ${BACKUP_FILE}"
echo "Old backups cleaned (kept last 7 days)"
Retención escalonada (diario, semanal, mensual)
#!/bin/bash
SOURCE_DIR="/var/www/myapp"
BACKUP_DIR="/backups"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
DAY_OF_WEEK=$(date +%u) # 1=Lunes, 7=Domingo
DAY_OF_MONTH=$(date +%d)
# Crear backup diario
tar -czf "${BACKUP_DIR}/daily_${TIMESTAMP}.tar.gz" -C "${SOURCE_DIR}" .
# Backup semanal los domingos
if [ "$DAY_OF_WEEK" = "7" ]; then
cp "${BACKUP_DIR}/daily_${TIMESTAMP}.tar.gz" "${BACKUP_DIR}/weekly_${TIMESTAMP}.tar.gz"
fi
# Backup mensual el día 1
if [ "$DAY_OF_MONTH" = "01" ]; then
cp "${BACKUP_DIR}/daily_${TIMESTAMP}.tar.gz" "${BACKUP_DIR}/monthly_${TIMESTAMP}.tar.gz"
fi
# Limpieza: 7 diarios, 4 semanales, 6 mensuales
find "${BACKUP_DIR}" -name "daily_*.tar.gz" -type f -mtime +7 -delete
find "${BACKUP_DIR}" -name "weekly_*.tar.gz" -type f -mtime +28 -delete
find "${BACKUP_DIR}" -name "monthly_*.tar.gz" -type f -mtime +180 -delete
echo "Backup completed with tiered retention"
Backup de base de datos con rotación
#!/bin/bash
DB_NAME="myapp"
DB_USER="postgres"
BACKUP_DIR="/backups/db"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
mkdir -p "${BACKUP_DIR}"
# Dump de la base de datos
pg_dump -U "${DB_USER}" "${DB_NAME}" | gzip > "${BACKUP_DIR}/db_${TIMESTAMP}.sql.gz"
# Mantener últimos 14 días
find "${BACKUP_DIR}" -name "db_*.sql.gz" -type f -mtime +14 -delete
echo "Database backup created: db_${TIMESTAMP}.sql.gz"
Configuración con cron
# Ejecutar diario a las 2 AM
0 2 * * * /opt/scripts/backup.sh >> /var/log/backup.log 2>&1
# Ejecutar cada 6 horas
0 */6 * * * /opt/scripts/backup.sh >> /var/log/backup.log 2>&1
Backup con verificación de integridad
#!/bin/bash
SOURCE_DIR="/var/www/myapp"
BACKUP_DIR="/backups"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="${BACKUP_DIR}/backup_${TIMESTAMP}.tar.gz"
# Crear backup
tar -czf "${BACKUP_FILE}" -C "${SOURCE_DIR}" .
# Verificar integridad
if gzip -t "${BACKUP_FILE}" 2>/dev/null; then
echo "OK: ${BACKUP_FILE} verified"
find "${BACKUP_DIR}" -name "backup_*.tar.gz" -type f -mtime +7 -delete
else
echo "ERROR: ${BACKUP_FILE} is corrupt"
rm -f "${BACKUP_FILE}"
exit 1
fi
Explicación
El comando find con -mtime +N encuentra archivos más viejos que N días. -delete los elimina. Este es el mecanismo de retención más simple: cada vez que el script corre, crea un backup nuevo y limpia archivos más viejos que la ventana de retención.
La retención escalonada usa prefijos en los nombres de archivo (daily_, weekly_, monthly_) para aplicar diferentes períodos de retención. El backup semanal es una copia del backup diario del domingo. El mensual es una copia del día 1. Esto evita crear múltiples archives grandes el mismo día.
gzip -t prueba la integridad del archive sin extraerlo. Ejecútalo después de cada backup para detectar errores de disco o escrituras incompletas.
Variantes
| Enfoque | Herramienta | Compresión | Usar Cuando |
|---|---|---|---|
| tar + find | tar, gzip | gzip | Backups de filesystem |
| rsync + find | rsync | Ninguna (sync) | Backups incrementales |
| pg_dump + gzip | pg_dump | gzip | Backups de PostgreSQL |
| mysqldump + gzip | mysqldump | gzip | Backups de MySQL |
| restic | restic | Deduplicación | Backups a gran escala, encriptados |
Pautas
- Siempre testea la restauración de backups. Un backup que no puedes restaurar no sirve.
- Usa
gzip -tpara verificar la integridad del archive después de crearlo. - Guarda backups en un disco separado o servidor remoto. Un backup en el mismo disco falla cuando el disco falla.
- Define políticas de retención basadas en objetivos de punto de recuperación (RPO). 7 diarios + 4 semanales + 6 mensuales cubre la mayoría de las necesidades.
- Loguea las operaciones de backup. Redirige el output a un archivo de log vía cron.
Errores Comunes
- No testear restauraciones. Ejecuta un drill de restauración mensual para verificar que los backups funcionan.
- Guardar backups en el mismo disco que la fuente. El fallo del disco pierde todo.
- Usar
rmen vez defind -delete.find -deletees más seguro porque solo matchea el patrón. - No setear
mkdir -ppara el directorio de backup. El script falla si el directorio no existe. - Olvidar hacer el script ejecutable:
chmod +x backup.sh.
Tips de Rendimiento
- Usa compresión paralela.
pigzes un reemplazo multi-threaded de gzip:
# 4x más rápido en sistemas multi-core
tar -I pigz -cf "${BACKUP_FILE}" -C "${SOURCE_DIR}" .
- Excluye archivos innecesarios. Omite caches, logs y archivos temporales:
tar -czf "${BACKUP_FILE}" \
--exclude="*.log" \
--exclude="*.tmp" \
--exclude="cache/*" \
--exclude="node_modules/*" \
-C "${SOURCE_DIR}" .
- Usa
ionicepara I/O de baja prioridad. No dejes que los backups ahoguen a producción:
ionice -c 3 tar -czf "${BACKUP_FILE}" -C "${SOURCE_DIR}" . Preguntas frecuentes
¿Cómo encripto backups en bash?
Usa gpg después de crear el archive: gpg --symmetric --cipher-algo AES256 backup.tar.gz. Esto produce backup.tar.gz.gpg. Desencripta con gpg -d backup.tar.gz.gpg > backup.tar.gz.
¿Cómo sincronizo backups a almacenamiento remoto?
Usa rsync o rclone:
rsync -avz /backups/ user@remote:/remote_backups/
O para almacenamiento compatible con S3:
rclone sync /backups remote:backup-bucket/
¿Cómo monitoreo fallos de backup?
Revisa el exit code en tu cron job y envía una alerta:
0 2 * * * /opt/scripts/backup.sh || echo "Backup failed" | mail -s "Backup Alert" admin@example.com
¿Cuál es la diferencia entre -mtime y -mmin?
-mtime +7 encuentra archivos más viejos que 7 días. -mmin +60 encuentra archivos más viejos que 60 minutos. Usa -mmin para políticas de retención sub-diarias.
Backup Encriptado con GPG
#!/bin/bash
# encrypted-backup.sh
SOURCE_DIR="/var/www/myapp"
BACKUP_DIR="/backups/encrypted"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="${BACKUP_DIR}/backup_${TIMESTAMP}.tar.gz.gpg"
PASSPHRASE_FILE="/etc/backup/.gpg_passphrase"
mkdir -p "${BACKUP_DIR}"
# Crear y encriptar en un pipeline
tar -czf - -C "${SOURCE_DIR}" . | \
gpg --batch --passphrase-file "${PASSPHRASE_FILE}" \
--symmetric --cipher-algo AES256 \
-o "${BACKUP_FILE}"
# Verificar
if gpg --batch --passphrase-file "${PASSPHRASE_FILE}" --verify "${BACKUP_FILE}" 2>/dev/null; then
echo "OK: ${BACKUP_FILE} encriptado y verificado"
find "${BACKUP_DIR}" -name "backup_*.tar.gz.gpg" -type f -mtime +30 -delete
else
echo "ERROR: Encriptación falló para ${BACKUP_FILE}"
rm -f "${BACKUP_FILE}"
exit 1
fi
Script de Restauración
#!/bin/bash
# restore-backup.sh
BACKUP_FILE="$1"
RESTORE_DIR="/tmp/restore"
if [ -z "$BACKUP_FILE" ]; then
echo "Usage: $0 <backup_file>"
exit 1
fi
mkdir -p "${RESTORE_DIR}"
# Verificar si está encriptado
if [[ "$BACKUP_FILE" == *.gpg ]]; then
gpg -d "${BACKUP_FILE}" | tar -xzf - -C "${RESTORE_DIR}"
else
tar -xzf "${BACKUP_FILE}" -C "${RESTORE_DIR}"
fi
echo "Restaurado a ${RESTORE_DIR}"
ls -la "${RESTORE_DIR}"
Backup con Hooks Pre/Post
#!/bin/bash
# backup-with-hooks.sh
SOURCE_DIR="/var/www/myapp"
BACKUP_DIR="/backups"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="${BACKUP_DIR}/backup_${TIMESTAMP}.tar.gz"
# Hook pre-backup: flushear base de datos a disco
pre_backup() {
if command -v mysql &> /dev/null; then
mysql -u root -e "FLUSH TABLES WITH READ LOCK;"
fi
sync
}
# Hook post-backup: desbloquear y limpiar
post_backup() {
if command -v mysql &> /dev/null; then
mysql -u root -e "UNLOCK TABLES;"
fi
find "${BACKUP_DIR}" -name "backup_*.tar.gz" -type f -mtime +7 -delete
}
# Ejecutar
pre_backup
tar -czf "${BACKUP_FILE}" -C "${SOURCE_DIR}" .
post_backup
# Verificar
if gzip -t "${BACKUP_FILE}" 2>/dev/null; then
echo "OK: ${BACKUP_FILE}"
else
echo "ERROR: ${BACKUP_FILE} está corrupto"
rm -f "${BACKUP_FILE}"
exit 1
fi
Verificación de Espacio en Disco Antes del Backup
#!/bin/bash
# backup-with-space-check.sh
BACKUP_DIR="/backups"
SOURCE_DIR="/var/www/myapp"
MIN_SPACE_GB=5
available_kb=$(df "${BACKUP_DIR}" | awk 'NR==2 {print $4}')
available_gb=$((available_kb / 1024 / 1024))
if [ "$available_gb" -lt "$MIN_SPACE_GB" ]; then
echo "ERROR: Solo ${available_gb}GB libres en ${BACKUP_DIR}, necesitas ${MIN_SPACE_GB}GB"
exit 1
fi
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
tar -czf "${BACKUP_DIR}/backup_${TIMESTAMP}.tar.gz" -C "${SOURCE_DIR}" .
echo "Backup creado con ${available_gb}GB libres"
Recursos Relacionados
Scripting en Bash para Automatizacion DevOps y Tareas de
Como escribir scripts Bash robustos para automatizar despliegues, monitoreo de sistemas, rotacion de logs y tareas de mantenimiento rutinarias
DocPlantilla de Verificación de Backup y Restore
Una plantilla para documentar procedimientos de verificación de backups de bases de datos y archivos.
GuideGuía de Pipelines CI/CD
Una guía práctica para construir pipelines CI/CD con GitHub Actions, testing, estrategias de deployment y procedimientos de rollback.
RecipePlaybook de Ansible para Configuración de Servidores
Cómo escribir y ejecutar playbooks de Ansible para provisionar, configurar y gestionar servidores con tareas idempotentes, roles y archivos de inventario.
RecipeConfiguración de Pipelines CI/CD
Configura pipelines CI/CD automatizados para testing, building y deployment de aplicaciones con GitHub Actions y lo que funciona.
RecipeEjecutar Comandos Shell en Paralelo con Bash
Ejecuta múltiples comandos shell concurrentemente usando xargs, GNU parallel y background jobs.