Una copia de seguridad que nunca se ha restaurado es solo una suposición. La única forma fiable de saber que puedes recuperarte de un desastre es restaurar los datos de verdad, de forma periódica y sin intervención manual, y comprobar que lo restaurado es completo y reciente. En este tutorial montarás en un servidor de pruebas con Ubuntu 24.04 una prueba automática que cada semana verifica el repositorio de restic, restaura los archivos de la web y el volcado de MariaDB, valida el resultado, mide el tiempo de recuperación y te avisa si algo falla.

Requisitos previos

Para seguir esta guía necesitas:

  • Un servidor de producción que ya haga copias con restic a un repositorio remoto (S3, SFTP o similar). En los ejemplos se respaldan /var/www y un volcado de base de datos en /var/backups/mysql/.
  • Un segundo servidor para las pruebas con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath, con espacio libre suficiente para una restauración completa. No hagas las pruebas en producción: una restauración mal dirigida puede sobrescribir datos reales.
  • Un usuario no root con privilegios sudo en ambos servidores.
  • Las credenciales del repositorio de restic (URL, contraseña y, si aplica, claves de acceso de S3).
  • Opcional: una cuenta en un servicio de monitorización por "ping", como Healthchecks.io, para recibir alertas cuando la prueba falle o no se ejecute.

Qué vas a probar

No todas las pruebas tienen el mismo coste. Un plan razonable combina varios niveles:

NivelFrecuenciaQué compruebaAutomatizable
Integridad del repositorioSemanalQue los datos del backup se pueden leer y no están corruptosSí
Restauración parcialSemanalQue los archivos y la base de datos se restauran y son recientesSí
Recuperación completa del servidorTrimestralQue puedes reconstruir un servidor entero siguiendo el runbookEn parte
Simulacro de conmutaciónAnualQue el equipo sabe ejecutar el plan con presión realNo

Esta guía automatiza los dos primeros niveles, que son los que detectan la mayoría de problemas: credenciales caducadas, backups que dejaron de ejecutarse, rutas que ya no se incluyen o volcados vacíos.

Paso 1: Añadir un archivo testigo en producción

Para saber si lo restaurado es reciente, necesitas un dato cuya fecha conozcas. Un archivo testigo que se actualiza justo antes de cada copia sirve para eso: si al restaurarlo tiene más de un día, el backup no se está ejecutando aunque el repositorio parezca sano.

En el servidor de producción, añade esta línea al principio del script o servicio que lanza tu backup de restic, antes de restic backup:

date --iso-8601=seconds | sudo tee /var/www/.dr-canary > /dev/null

Comprueba también cómo se genera el volcado de la base de datos. Para poder restaurarlo en una base de datos con otro nombre, no debe usar la opción --databases, que incluye sentencias CREATE DATABASE y USE. Un volcado adecuado tiene este aspecto:

sudo mysqldump --single-transaction --routines --triggers appdb | gzip > /var/backups/mysql/appdb.sql.gz

Tras la siguiente copia, y con las mismas variables de entorno que usa tu backup (RESTIC_REPOSITORY, RESTIC_PASSWORD_FILE...), confirma que ambos archivos están en la última instantánea:

restic ls latest | grep -E 'dr-canary|appdb.sql.gz'
/var/www/.dr-canary
/var/backups/mysql/appdb.sql.gz

Paso 2: Preparar el servidor de pruebas

En el servidor de pruebas, instala restic, MariaDB para restaurar el volcado, y curl para las alertas:

sudo apt update
sudo apt install restic mariadb-server curl

Crea un directorio de configuración accesible solo por root:

sudo install -d -m 0700 /etc/dr-test

Guarda la contraseña del repositorio en su propio archivo:

sudo nano /etc/dr-test/restic-password

Escribe solo la contraseña en una línea, guarda y restringe los permisos:

sudo chmod 600 /etc/dr-test/restic-password

Ahora crea el archivo de variables de entorno que usarán restic y el script de prueba:

sudo nano /etc/dr-test/dr-test.env

Ajusta los valores a tu repositorio. Este ejemplo usa un bucket compatible con S3:

RESTIC_REPOSITORY=s3:https://s3.example.com/your_bucket/restic
RESTIC_PASSWORD_FILE=/etc/dr-test/restic-password
AWS_ACCESS_KEY_ID=your_access_key
AWS_SECRET_ACCESS_KEY=your_secret_key

# Parámetros de la prueba
RESTORE_DIR=/srv/dr-test
DB_DUMP=/var/backups/mysql/appdb.sql.gz
CHECK_TABLE=orders
MAX_AGE_HOURS=26
HC_URL=https://hc-ping.com/your_check_uuid

CHECK_TABLE es una tabla que siempre debe tener filas en producción, y MAX_AGE_HOURS es la antigüedad máxima aceptable del testigo (26 horas deja margen para una copia diaria). Si no usas Healthchecks, deja HC_URL vacío.

sudo chmod 600 /etc/dr-test/dr-test.env

Comprueba que el servidor de pruebas llega al repositorio:

sudo bash -c 'set -a; . /etc/dr-test/dr-test.env; restic snapshots --latest 3'

Deberías ver las tres instantáneas más recientes. Si aparece wrong password or no key found o un error de acceso a S3, corrige las credenciales antes de seguir.

Paso 3: Escribir el script de prueba

El script ejecuta cuatro comprobaciones en orden y se detiene en la primera que falle:

  1. restic check --read-data-subset=5% verifica la estructura del repositorio y lee un 5 % aleatorio de los datos, así que en unas semanas habrá leído todo.
  2. Restaura la última instantánea de /var/www y del volcado en un directorio temporal.
  3. Comprueba que el testigo existe y no es más antiguo que MAX_AGE_HOURS.
  4. Importa el volcado en una base de datos desechable dr_test y comprueba que la tabla de control tiene filas.

Crea el script:

sudo nano /usr/local/sbin/dr-restore-test
#!/usr/bin/env bash
set -euo pipefail

: "${RESTORE_DIR:?}" "${DB_DUMP:?}" "${CHECK_TABLE:?}" "${MAX_AGE_HOURS:?}"
HC_URL="${HC_URL:-}"

log() { echo "[dr-test] $*"; }

fail() {
  log "FALLO: $*"
  if [[ -n "$HC_URL" ]]; then
    curl -fsS -m 10 --retry 3 --data-raw "$*" "$HC_URL/fail" > /dev/null || true
  fi
  exit 1
}

start=$(date +%s)
[[ -n "$HC_URL" ]] && curl -fsS -m 10 --retry 3 "$HC_URL/start" > /dev/null || true

log "Comprobando la integridad del repositorio"
restic check --read-data-subset=5% || fail "restic check ha fallado"

log "Restaurando la última instantánea en $RESTORE_DIR"
rm -rf -- "$RESTORE_DIR"
mkdir -p -- "$RESTORE_DIR"
restic restore latest --target "$RESTORE_DIR" \
  --include /var/www --include "$DB_DUMP" || fail "restic restore ha fallado"

files=$(find "$RESTORE_DIR/var/www" -type f | wc -l)
(( files > 0 )) || fail "no se ha restaurado ningún archivo de /var/www"
log "Archivos restaurados en /var/www: $files"

canary="$RESTORE_DIR/var/www/.dr-canary"
[[ -f "$canary" ]] || fail "falta el archivo testigo"
canary_ts=$(date -d "$(cat "$canary")" +%s) || fail "testigo ilegible"
age_h=$(( ($(date +%s) - canary_ts) / 3600 ))
(( age_h <= MAX_AGE_HOURS )) || fail "el backup tiene ${age_h} h (máximo ${MAX_AGE_HOURS} h)"
log "Antigüedad del backup: ${age_h} h"

dump="$RESTORE_DIR$DB_DUMP"
[[ -s "$dump" ]] || fail "el volcado $DB_DUMP no existe o está vacío"
gzip -t "$dump" || fail "el volcado está corrupto"

log "Importando el volcado en la base de datos dr_test"
mysql -e "DROP DATABASE IF EXISTS dr_test; CREATE DATABASE dr_test;"
zcat "$dump" | mysql dr_test || fail "la importación del volcado ha fallado"

rows=$(mysql -N -B -e "SELECT COUNT(*) FROM dr_test.\`$CHECK_TABLE\`") \
  || fail "no existe la tabla $CHECK_TABLE"
(( rows > 0 )) || fail "la tabla $CHECK_TABLE está vacía"
log "Filas en $CHECK_TABLE: $rows"

elapsed=$(( $(date +%s) - start ))
log "Prueba superada en ${elapsed} s (tiempo de recuperación medido)"

mysql -e "DROP DATABASE dr_test;"
rm -rf -- "$RESTORE_DIR"

if [[ -n "$HC_URL" ]]; then
  curl -fsS -m 10 --retry 3 --data-raw "OK ${elapsed}s, ${files} archivos, ${rows} filas" "$HC_URL" > /dev/null || true
fi

El script usa mysql como root sin contraseña, lo que funciona porque MariaDB en Ubuntu autentica al usuario root del sistema por socket Unix. Hazlo ejecutable:

sudo chmod 750 /usr/local/sbin/dr-restore-test

Paso 4: Ejecutar la prueba a mano

Antes de programarla, lanza la prueba una vez cargando el archivo de entorno:

sudo bash -c 'set -a; . /etc/dr-test/dr-test.env; /usr/local/sbin/dr-restore-test'

Una ejecución correcta termina así:

[dr-test] Comprobando la integridad del repositorio
...
no errors were found
[dr-test] Restaurando la última instantánea en /srv/dr-test
...
[dr-test] Archivos restaurados en /var/www: 18342
[dr-test] Antigüedad del backup: 7 h
[dr-test] Importando el volcado en la base de datos dr_test
[dr-test] Filas en orders: 120455
[dr-test] Prueba superada en 412 s (tiempo de recuperación medido)

Anota el tiempo total: es una medida real de tu RTO (tiempo objetivo de recuperación) para estos datos. Si supera lo que el negocio puede tolerar, tienes un problema que resolver antes de que ocurra un desastre.

Para comprobar que las alertas funcionan, fuerza un fallo temporal, por ejemplo con MAX_AGE_HOURS=0:

sudo bash -c 'set -a; . /etc/dr-test/dr-test.env; MAX_AGE_HOURS=0 /usr/local/sbin/dr-restore-test'

El script debe terminar con FALLO: el backup tiene ... h y, si configuraste HC_URL, el panel de Healthchecks debe marcar el check como caído.

Paso 5: Programar la prueba con un temporizador de systemd

Un temporizador de systemd es preferible a cron porque registra cada ejecución en el journal y permite limitar recursos. Crea el servicio:

sudo nano /etc/systemd/system/dr-restore-test.service
[Unit]
Description=Prueba automática de restauración de backups
Wants=network-online.target
After=network-online.target mariadb.service

[Service]
Type=oneshot
EnvironmentFile=/etc/dr-test/dr-test.env
ExecStart=/usr/local/sbin/dr-restore-test
Nice=10
IOSchedulingClass=idle

Crea el temporizador para que se ejecute los domingos a las 06:00, después de la copia nocturna:

sudo nano /etc/systemd/system/dr-restore-test.timer
[Unit]
Description=Ejecuta la prueba de restauración cada semana

[Timer]
OnCalendar=Sun *-*-* 06:00:00
RandomizedDelaySec=15min
Persistent=true

[Install]
WantedBy=timers.target

Persistent=true hace que, si el servidor estaba apagado a esa hora, la prueba se ejecute al arrancar. Activa el temporizador:

sudo systemctl daemon-reload
sudo systemctl enable --now dr-restore-test.timer

Comprueba la próxima ejecución:

systemctl list-timers dr-restore-test.timer

La columna NEXT debe mostrar el próximo domingo a partir de las 06:00, y ACTIVATES el servicio dr-restore-test.service.

Puedes lanzar el servicio en cualquier momento y revisar el resultado en el journal:

sudo systemctl start dr-restore-test.service
journalctl -u dr-restore-test.service -n 20 --no-pager

En Healthchecks, configura el periodo del check en 7 días con un margen de unas horas. Así recibirás una alerta tanto si la prueba falla como si deja de ejecutarse.

Paso 6: Documentar cada resultado

La prueba automática responde "sí o no", pero el valor de un programa de recuperación está en lo que aprendes. Mantén un registro breve de cada fallo con estas columnas: fecha, qué falló, causa raíz, corrección aplicada y tiempo de recuperación medido. Con el tiempo verás tendencias, como un RTO que crece a medida que aumentan los datos.

Una vez al trimestre, complementa la prueba automática con una recuperación completa: parte de un servidor vacío, sigue tu runbook al pie de la letra y anota cada paso que no estaba documentado.

Solución de problemas

restic check tarda demasiado o consume mucho ancho de banda. Baja el porcentaje, por ejemplo --read-data-subset=2%, o usa la forma --read-data-subset=1/12 y cambia la fracción cada semana. Con repositorios grandes, restaura solo un subconjunto representativo en lugar de todo /var/www.

Fatal: unable to open config file o errores 403 de S3. Las credenciales de /etc/dr-test/dr-test.env no son válidas o la clave no tiene permiso de lectura sobre el bucket. Usa una clave de solo lectura para el servidor de pruebas: no necesita escribir en el repositorio.

La importación falla con ERROR 1049 Unknown database o crea tablas en otra base de datos. El volcado se generó con --databases o --all-databases. Genera el volcado de producción sin esas opciones, como en el paso 1.

El testigo siempre es antiguo. La línea que actualiza /var/www/.dr-canary no se ejecuta antes de restic backup, o el backup de producción falla. Revisa su journal o log en el servidor de producción.

Conclusión

Ahora tienes una prueba semanal que demuestra, con datos restaurados de verdad, que tus copias son legibles, recientes y utilizables, y que te avisa en cuanto dejan de serlo. Como siguientes pasos, añade una prueba funcional de la aplicación restaurada (por ejemplo, arrancarla contra dr_test y pedir una URL con curl), extiende el mismo esquema a otros servidores y prepara un runbook de recuperación completa para los simulacros trimestrales.