Un plan de recuperación ante desastres (DR) responde a una pregunta concreta: si este servidor desaparece ahora mismo, ¿cuánto tardas en volver a dar servicio y cuántos datos pierdes? En esta guía convertirás esa pregunta en un plan que funciona en la práctica: definirás objetivos RTO y RPO, automatizarás copias cifradas fuera del servidor con restic y systemd, harás un simulacro de restauración en una máquina limpia y dejarás documentado el procedimiento. El ejemplo usa un servidor web con Nginx y MySQL en Ubuntu 24.04.
Requisitos previos
- Un servidor con Ubuntu 24.04 LTS que quieras proteger, por ejemplo un VPS de CubePath. En la guía se llama
web01. - Un segundo servidor en otra ubicación que actúe como almacenamiento de copias (
backup_server_ip), con SSH y espacio suficiente. Debe estar en otro centro de datos o con otro proveedor: una copia en el mismo servidor no sirve para un desastre. - Un usuario no root con privilegios
sudoen ambos servidores. - Un tercer servidor temporal (puede ser un VPS que destruyes al acabar) para el simulacro de restauración del paso 7.
Paso 1: Definir RTO y RPO por servicio
Antes de tocar comandos, decide qué necesitas recuperar y con qué urgencia. Dos métricas guían todo lo demás:
- RTO (Recovery Time Objective): tiempo máximo que el servicio puede estar caído. Un RTO de 4 horas significa que en 4 horas debe estar funcionando otra vez.
- RPO (Recovery Point Objective): cantidad máxima de datos que aceptas perder, medida en tiempo. Un RPO de 24 horas significa que basta con una copia diaria; un RPO de 5 minutos exige replicación o copias muy frecuentes.
Haz un inventario de lo que ejecuta cada servidor y asigna objetivos realistas. Un ejemplo:
| Servicio | Datos críticos | RTO | RPO | Método |
|---|---|---|---|---|
| Web (Nginx + PHP) | /var/www, /etc/nginx, certificados | 4 h | 24 h | Copia diaria con restic |
| Base de datos MySQL | Todas las bases | 4 h | 24 h | mysqldump diario + restic |
| Configuración del sistema | /etc, paquetes instalados | 8 h | 24 h | Copia diaria con restic |
Si algún servicio necesita un RPO de minutos, las copias diarias no bastan: tendrás que añadir replicación a un segundo servidor. Esta guía cubre el caso más habitual, RPO de horas, que es la base sobre la que se construye cualquier otra estrategia.
Anota también las dependencias externas que no están en el servidor: registros DNS, claves de API, credenciales del proveedor, contactos. Las necesitarás en el paso 8.
Paso 2: Preparar el servidor de copias
Las copias deben estar fuera del servidor protegido y en otra ubicación, siguiendo la regla 3-2-1: tres copias de los datos, en dos soportes distintos, una de ellas fuera del sitio.
En el servidor de copias, crea un usuario dedicado y el directorio donde restic guardará el repositorio:
sudo adduser --disabled-password --gecos "" backup
sudo install -d -o backup -g backup -m 700 /srv/restic
En web01, las copias se ejecutarán como root, así que genera una clave SSH para root sin frase de paso:
sudo ssh-keygen -t ed25519 -f /root/.ssh/restic -N "" -C "restic@web01"
sudo cat /root/.ssh/restic.pub
Copia la clave pública que aparece en pantalla y añádela en el servidor de copias:
sudo install -d -o backup -g backup -m 700 /home/backup/.ssh
sudo nano /home/backup/.ssh/authorized_keys
Pega la clave, guarda el archivo y ajusta el propietario y los permisos:
sudo chown backup:backup /home/backup/.ssh/authorized_keys
sudo chmod 600 /home/backup/.ssh/authorized_keys
De vuelta en web01, crea un alias SSH para que restic sepa qué clave usar:
sudo nano /root/.ssh/config
Host backup-server
HostName backup_server_ip
User backup
IdentityFile /root/.ssh/restic
Comprueba la conexión. La primera vez acepta la huella del servidor escribiendo yes:
sudo ssh backup-server 'echo conexión correcta'
conexión correcta
Paso 3: Inicializar un repositorio restic cifrado
restic hace copias incrementales con deduplicación y cifra todo en el cliente, así que el servidor de copias nunca ve los datos en claro. Instálalo desde los repositorios de Ubuntu:
sudo apt update
sudo apt install restic
Genera una contraseña aleatoria para el repositorio y guárdala con permisos restrictivos:
sudo install -d -m 700 /etc/restic
openssl rand -base64 32 | sudo tee /etc/restic/password > /dev/null
sudo chmod 600 /etc/restic/password
Importantesin esta contraseña no se puede recuperar nada. Guarda una copia en tu gestor de contraseñas o en otro lugar fuera de web01. Si el servidor desaparece y la contraseña solo estaba en él, las copias son inútiles.
Inicializa el repositorio en el servidor de copias:
sudo restic -r sftp:backup-server:/srv/restic/web01 --password-file /etc/restic/password init
created restic repository 3f1c9a7b2e at sftp:backup-server:/srv/restic/web01
Please note that knowledge of your password is required to access
the repository. Losing your password means that your data is
irrecoverably lost.
Paso 4: Crear el script de copia
Copiar los archivos de datos de MySQL mientras el servicio está en marcha produce copias inconsistentes. Lo correcto es generar un volcado con mysqldump --single-transaction, que obtiene una vista coherente de las tablas InnoDB sin bloquearlas, y que restic copie ese volcado junto con el resto de archivos.
Crea el script:
sudo nano /usr/local/sbin/dr-backup
#!/usr/bin/env bash
set -euo pipefail
export RESTIC_REPOSITORY="sftp:backup-server:/srv/restic/web01"
export RESTIC_PASSWORD_FILE="/etc/restic/password"
DUMP_DIR="/var/backups/mysql"
install -d -m 700 "$DUMP_DIR"
# Volcado coherente de todas las bases de datos
mysqldump --single-transaction --routines --triggers --events --all-databases \
| gzip > "$DUMP_DIR/all-databases.sql.gz.tmp"
mv "$DUMP_DIR/all-databases.sql.gz.tmp" "$DUMP_DIR/all-databases.sql.gz"
# Lista de paquetes instalados a mano, para reconstruir el sistema
apt-mark showmanual > /var/backups/apt-manual.txt
restic backup --tag daily --exclude-caches \
/etc /var/www /home /root /usr/local /var/backups
# Retención: 7 diarias, 4 semanales y 6 mensuales
restic forget --tag daily --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune
En Ubuntu, root se autentica en MySQL mediante el socket local, por lo que mysqldump no necesita contraseña al ejecutarse como root. Si usas PostgreSQL, sustituye el volcado por sudo -u postgres pg_dumpall | gzip > ....
Hazlo ejecutable y lánzalo una vez a mano:
sudo chmod 750 /usr/local/sbin/dr-backup
sudo /usr/local/sbin/dr-backup
Al final verás un resumen parecido a este:
Files: 8214 new, 0 changed, 0 unmodified
Dirs: 1102 new, 0 changed, 0 unmodified
Added to the repository: 412.337 MiB (138.921 MiB stored)
processed 8214 files, 1.204 GiB in 0:41
snapshot 9b2e4c1a saved
Comprueba que la instantánea existe:
sudo restic -r sftp:backup-server:/srv/restic/web01 --password-file /etc/restic/password snapshots
Paso 5: Programar las copias con un temporizador de systemd
Un temporizador de systemd registra cada ejecución en el journal y, con Persistent=true, lanza la copia perdida si el servidor estaba apagado a la hora prevista.
Crea el servicio:
sudo nano /etc/systemd/system/dr-backup.service
[Unit]
Description=Copia de seguridad para recuperación ante desastres
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/dr-backup
Nice=10
IOSchedulingClass=idle
Crea el temporizador, que se ejecutará cada día a las 02:30 con un margen aleatorio de hasta 15 minutos:
sudo nano /etc/systemd/system/dr-backup.timer
[Unit]
Description=Copia diaria para recuperación ante desastres
[Timer]
OnCalendar=*-*-* 02:30:00
RandomizedDelaySec=15m
Persistent=true
[Install]
WantedBy=timers.target
Activa el temporizador y comprueba cuándo se ejecutará la próxima vez:
sudo systemctl daemon-reload
sudo systemctl enable --now dr-backup.timer
systemctl list-timers dr-backup.timer
NEXT LEFT LAST PASSED UNIT ACTIVATES
Fri 2026-09-26 02:37:12 UTC 11h left - - dr-backup.timer dr-backup.service
Tras la primera ejecución automática, revisa el resultado con:
sudo journalctl -u dr-backup.service --since today
Una copia que falla en silencio es peor que no tener copia. Integra el estado de dr-backup.service en tu monitorización: por ejemplo, alerta si systemctl is-failed dr-backup.service devuelve failed o si la última instantánea de restic snapshots --latest 1 tiene más de 26 horas.
Paso 6: Verificar la integridad del repositorio
restic check comprueba la estructura del repositorio y, con --read-data-subset, descarga y verifica una parte de los datos. Ejecútalo al menos una vez por semana:
sudo restic -r sftp:backup-server:/srv/restic/web01 --password-file /etc/restic/password check --read-data-subset=10%
using temporary cache in /tmp/restic-check-cache-1234567890
create exclusive lock for repository
load indexes
check all packs
check snapshots, trees and blobs
read 10.0% of data packs
[0:04] 100.00% 3 / 3 packs
no errors were found
Verificar el repositorio no es lo mismo que comprobar que puedes recuperar el servicio. Para eso está el simulacro.
Paso 7: Hacer un simulacro de restauración
El único modo de conocer tu RTO real es restaurar en un servidor limpio y cronometrarlo. Crea un servidor temporal con Ubuntu 24.04 y anota la hora de inicio.
En el servidor temporal, instala restic y el mismo software que tenía web01:
sudo apt update
sudo apt install restic nginx mysql-server
Copia a este servidor, por un canal seguro, la clave /root/.ssh/restic, el archivo /root/.ssh/config y la contraseña del repositorio (en un desastre real las sacarías de tu gestor de contraseñas). Colócalos en las mismas rutas, con permisos 600, y autoriza la clave en el servidor de copias si usas una distinta. Después conéctate una vez para aceptar la huella del servidor de copias, porque restic no puede responder a esa pregunta:
sudo ssh backup-server true
Lista las instantáneas disponibles:
sudo restic -r sftp:backup-server:/srv/restic/web01 --password-file /etc/restic/password snapshots
Restaura la última instantánea en un directorio aparte, no sobre /, para revisar antes de copiar:
sudo restic -r sftp:backup-server:/srv/restic/web01 --password-file /etc/restic/password \
restore latest --target /srv/restore
Recupera los datos de la web y la configuración de Nginx:
sudo rsync -a /srv/restore/var/www/ /var/www/
sudo rsync -a /srv/restore/etc/nginx/ /etc/nginx/
sudo nginx -t && sudo systemctl reload nginx
Importa las bases de datos:
gunzip -c /srv/restore/var/backups/mysql/all-databases.sql.gz | sudo mysql
Comprueba que la aplicación responde y que los datos son los esperados:
curl -I -H "Host: your_domain" http://127.0.0.1/
sudo mysql -e "SHOW DATABASES;"
HTTP/1.1 200 OK
Server: nginx/1.24.0 (Ubuntu)
Anota la hora de fin. La diferencia es tu RTO medido; compárala con el objetivo del paso 1. Si ha tardado demasiado, lo habitual es automatizar la instalación del sistema base (con cloud-init o Ansible) para que el simulacro se limite a restaurar datos. Cuando termines, destruye el servidor temporal.
Notael volcado incluye la base
mysqlcon los usuarios y sus permisos. En un servidor nuevo eso es lo que quieres; no importes este volcado sobre un servidor que ya tenga otros usuarios de MySQL que debas conservar.
Paso 8: Documentar el plan
Un plan que solo está en la cabeza de una persona no es un plan. Crea un documento corto (un runbook) y guárdalo fuera de la infraestructura que protege, por ejemplo en un repositorio Git o en la wiki del equipo. Esta plantilla cubre lo esencial:
# Plan de recuperación: web01
## Objetivos
- RTO: 4 h · RPO: 24 h
- Último simulacro: 2026-09-25, RTO medido: 1 h 20 min
## Qué se copia y dónde
- Rutas: /etc, /var/www, /home, /root, /usr/local, /var/backups
- Bases de datos: mysqldump diario en /var/backups/mysql
- Repositorio: sftp:backup-server:/srv/restic/web01 (otro centro de datos)
- Contraseña del repositorio y clave SSH: gestor de contraseñas, entrada "restic web01"
## Procedimiento de restauración
1. Crear servidor Ubuntu 24.04 e instalar restic, nginx, mysql-server
2. Recuperar clave SSH, config y contraseña del gestor de contraseñas
3. restic restore latest --target /srv/restore
4. Copiar /var/www y /etc/nginx, importar all-databases.sql.gz
5. Comprobar la web con curl y cambiar el registro DNS A a la nueva IP
## Dependencias externas
- DNS: proveedor, cuenta y registros afectados
- Certificados TLS: se reemiten con certbot tras el cambio de DNS
## Contactos
- Responsable técnico, suplente y proveedor
Actualiza el documento cada vez que cambie algo relevante (una ruta nueva, otra base de datos, un servicio más) y después de cada simulacro.
Solución de problemas
Fatal: unable to open config file: ... no such file or directory: la ruta del repositorio es incorrecta o el repositorio no se ha inicializado. Revisa la ruta en el script y en/root/.ssh/config.unable to create lock in backend: repository is already locked: una ejecución anterior se interrumpió. Comprueba que no hay otra copia en marcha y ejecutarestic unlockcon los mismos parámetros-ry--password-file.mysqldump: Got error: 1045: Access denied: el script no se está ejecutando como root o root usa contraseña en MySQL. Crea un usuario de copias con los privilegiosSELECT, SHOW VIEW, TRIGGER, LOCK TABLES, EVENT, PROCESSy guarda sus credenciales en/root/.my.cnfcon permisos600.- La copia tarda mucho o satura el disco: los parámetros
NiceeIOSchedulingClass=idledel servicio reducen el impacto. Excluye directorios que no necesitas con--exclude, por ejemplo cachés de la aplicación.
Conclusión
Tienes objetivos RTO y RPO definidos, copias cifradas y automáticas en otra ubicación, una verificación periódica del repositorio y un procedimiento de restauración probado y documentado. Repite el simulacro al menos cada trimestre: es la única forma de saber que el plan sigue funcionando. Como siguientes pasos, añade una segunda copia en otro proveedor (restic admite backends compatibles con S3), automatiza la instalación del servidor base con Ansible o cloud-init para reducir el RTO y, si algún servicio necesita un RPO de minutos, configura replicación de la base de datos hacia otro centro de datos.
