Restic es una herramienta de copias de seguridad que cifra y deduplica los datos antes de enviarlos a un repositorio local, SFTP o compatible con S3. En este tutorial montarás una configuración de producción en Ubuntu 24.04: dos repositorios independientes (uno en almacenamiento S3 y otro en un servidor por SFTP) para cumplir la regla 3-2-1, exclusiones, política de retención, límite de ancho de banda, ejecución diaria con timers de systemd, aviso en caso de fallo y una restauración de prueba.

Requisitos previos

  • Un servidor con Ubuntu 24.04 LTS del que quieres hacer copias, por ejemplo un VPS de CubePath. En esta guía se llama web01.
  • Un usuario no root con privilegios sudo. Restic se ejecutará como root para poder leer todos los archivos.
  • Un bucket en un servicio compatible con S3 con su endpoint, clave de acceso y clave secreta.
  • Un segundo servidor accesible por SSH con espacio libre para el repositorio SFTP, en esta guía backup.your_domain, con un usuario restic.

Sustituye your_domain, el nombre del bucket y las credenciales de ejemplo por los tuyos.

Paso 1: Instalar Restic

Restic está en los repositorios de Ubuntu:

sudo apt update
sudo apt install -y restic

Comprueba la versión:

restic version
restic 0.16.4 compiled with go1.22.2 on linux/amd64

La versión 0.16 incluye todo lo que usa esta guía, incluida la compresión. El paquete de Ubuntu desactiva restic self-update; si necesitas una versión más reciente, descarga el binario desde la página de publicaciones de Restic en GitHub y colócalo en /usr/local/bin.

Paso 2: Guardar la contraseña y las credenciales

Restic cifra cada repositorio con una contraseña. Si la pierdes, los datos son irrecuperables, así que guárdala también fuera del servidor, por ejemplo en tu gestor de contraseñas.

Crea un directorio accesible solo por root y genera una contraseña aleatoria en un archivo:

sudo install -d -m 700 /etc/restic
sudo sh -c 'openssl rand -base64 32 > /etc/restic/password'
sudo chmod 600 /etc/restic/password

Crea un archivo de entorno para el repositorio S3:

sudo nano /etc/restic/s3.env
RESTIC_REPOSITORY=s3:https://s3.your_provider.com/your_bucket/web01
RESTIC_PASSWORD_FILE=/etc/restic/password
AWS_ACCESS_KEY_ID=your_access_key
AWS_SECRET_ACCESS_KEY=your_secret_key

El formato s3:https://endpoint/bucket/ruta funciona con AWS y con cualquier servicio compatible con S3; la ruta final permite guardar varios servidores en el mismo bucket. Crea el archivo del repositorio SFTP:

sudo nano /etc/restic/sftp.env
RESTIC_REPOSITORY=sftp:[email protected]_domain:/srv/restic/web01
RESTIC_PASSWORD_FILE=/etc/restic/password

Protege ambos archivos:

sudo chmod 600 /etc/restic/s3.env /etc/restic/sftp.env

Usar RESTIC_PASSWORD_FILE en lugar de RESTIC_PASSWORD evita que la contraseña aparezca en el entorno de cada proceso.

Paso 3: Preparar el acceso SFTP

Restic usa el cliente SSH del sistema, y como las copias se ejecutan como root, la clave debe estar en la cuenta de root. Genera una clave sin frase de paso dedicada a las copias:

sudo ssh-keygen -t ed25519 -f /root/.ssh/restic_backup -N "" -C "restic-web01"

Indica a SSH que use esa clave para el servidor de copias:

sudo nano /root/.ssh/config
Host backup.your_domain
    User restic
    IdentityFile /root/.ssh/restic_backup
    IdentitiesOnly yes

Muestra la clave pública y añádela a ~/.ssh/authorized_keys del usuario restic en el servidor de copias:

sudo cat /root/.ssh/restic_backup.pub

En el servidor de copias, crea el directorio de destino como el usuario restic:

mkdir -p /srv/restic

De vuelta en web01, comprueba la conexión. La primera vez acepta la huella del servidor:

sudo sftp backup.your_domain <<< 'ls /srv/restic'

Si el comando lista el directorio sin pedir contraseña, el acceso está listo.

Paso 4: Inicializar los repositorios

Los archivos de entorno se cargan con set -a, que exporta cada variable definida. Abre una shell de root para los comandos manuales de esta guía:

sudo -i

Inicializa el repositorio S3:

set -a; . /etc/restic/s3.env; set +a
restic init
created restic repository 3a1f9c0b2e at s3:https://s3.your_provider.com/your_bucket/web01

Inicializa el repositorio SFTP en la misma shell. Las variables del segundo archivo sustituyen a las del primero:

set -a; . /etc/restic/sftp.env; set +a
restic init

Ambos repositorios usan la misma contraseña para simplificar la gestión, pero son independientes: tienen claves de cifrado distintas y un fallo en uno no afecta al otro.

Paso 5: Definir qué se copia y qué no

Guarda la lista de rutas y de exclusiones en archivos para que el script, los comandos manuales y cualquier cambio futuro usen la misma definición:

nano /etc/restic/paths.txt
/etc
/home
/root
/var/www
/var/backups
nano /etc/restic/excludes.txt
/home/*/.cache
/root/.cache
/var/www/*/node_modules
/var/www/*/.next
*.tmp
*.swp

No copies los archivos de datos de una base de datos en ejecución (/var/lib/mysql, /var/lib/postgresql): la copia sería inconsistente. Exporta la base de datos a /var/backups con mysqldump o pg_dump antes de la copia y deja que Restic guarde ese volcado.

Haz una primera copia manual al repositorio SFTP, que todavía está cargado en tu shell:

restic backup \
  --files-from /etc/restic/paths.txt \
  --exclude-file /etc/restic/excludes.txt \
  --exclude-caches \
  --one-file-system \
  --tag diario
Files:        48213 new,     0 changed,     0 unmodified
Dirs:          6120 new,     0 changed,     0 unmodified
Added to the repository: 1.215 GiB (512.304 MiB stored)

processed 48213 files, 1.873 GiB in 0:41
snapshot 9b2e7c1d saved

--exclude-caches omite los directorios marcados con un archivo CACHEDIR.TAG y --one-file-system evita entrar en otros sistemas de archivos montados dentro de las rutas, como /proc o un disco de red. Lista las instantáneas:

restic snapshots
ID        Time                 Host        Tags        Paths
------------------------------------------------------------------
9b2e7c1d  2026-09-25 10:12:03  web01       diario      /etc
                                                       /home
                                                       /root
                                                       /var/backups
                                                       /var/www
------------------------------------------------------------------
1 snapshots

Paso 6: Probar la política de retención

restic forget decide qué instantáneas conservar y --prune elimina los datos que ya no usa ninguna. Antes de automatizarlo, simula la política con --dry-run para ver qué se borraría:

restic forget \
  --tag diario \
  --keep-daily 7 \
  --keep-weekly 4 \
  --keep-monthly 12 \
  --dry-run
Applying Policy: keep 7 daily, 4 weekly, 12 monthly snapshots
keep 1 snapshots:
ID        Time                 Host        Tags        Reasons           Paths
...

Con esta política conservas una copia por día durante una semana, una por semana durante un mes y una por mes durante un año. Las opciones --keep-* se combinan: una misma instantánea puede cumplir varias reglas y solo se borra si no cumple ninguna. Por defecto Restic agrupa por host y rutas, de modo que la retención de un servidor no afecta a otro que comparta repositorio.

El prune tiene que leer el índice del repositorio y, en S3, cada operación cuesta tiempo y peticiones. Por eso en el script se ejecuta con la copia diaria, pero la verificación completa de integridad se programa aparte.

Sal de la shell de root:

exit

Paso 7: Crear el script de copia

El script recorre los dos repositorios, hace la copia y aplica la retención en cada uno. Si un repositorio falla, sigue con el otro y termina con error al final para que systemd lo registre.

sudo nano /usr/local/sbin/restic-backup
#!/usr/bin/env bash
set -euo pipefail

REPOS=(/etc/restic/s3.env /etc/restic/sftp.env)
status=0

for env_file in "${REPOS[@]}"; do
    echo "==> Copia en ${env_file}"
    if ! (
        set -a
        . "$env_file"
        set +a

        restic backup \
            --files-from /etc/restic/paths.txt \
            --exclude-file /etc/restic/excludes.txt \
            --exclude-caches \
            --one-file-system \
            --tag diario \
            --limit-upload "${RESTIC_LIMIT_UPLOAD:-0}"

        restic forget \
            --tag diario \
            --keep-daily 7 \
            --keep-weekly 4 \
            --keep-monthly 12 \
            --prune
    ); then
        echo "ERROR: fallo en ${env_file}" >&2
        status=1
    fi
done

exit "$status"

Cada repositorio se procesa en una subshell ( ... ), así las variables de uno no se mezclan con las del siguiente. --limit-upload limita la subida en KiB/s; con el valor 0 no hay límite, y el valor real se define en la unidad de systemd. Hazlo ejecutable:

sudo chmod 750 /usr/local/sbin/restic-backup

Paso 8: Programar la copia con un timer de systemd

Un timer de systemd registra cada ejecución en el journal, evita que se solapen dos ejecuciones y permite bajar la prioridad del proceso. Crea el servicio:

sudo nano /etc/systemd/system/restic-backup.service
[Unit]
Description=Copia de seguridad con Restic
Wants=network-online.target
After=network-online.target
OnFailure=restic-notify@%n.service

[Service]
Type=oneshot
Environment=RESTIC_CACHE_DIR=/var/cache/restic
Environment=RESTIC_LIMIT_UPLOAD=20480
Nice=19
IOSchedulingClass=idle
ExecStart=/usr/local/sbin/restic-backup

RESTIC_LIMIT_UPLOAD=20480 limita la subida a 20 MiB/s para no saturar el enlace del servidor; ajústalo a tu caso. Nice=19 e IOSchedulingClass=idle hacen que la copia ceda CPU y disco a los servicios en producción. Crea el timer:

sudo nano /etc/systemd/system/restic-backup.timer
[Unit]
Description=Copia diaria con Restic

[Timer]
OnCalendar=*-*-* 02:30:00
RandomizedDelaySec=15min
Persistent=true

[Install]
WantedBy=timers.target

Persistent=true ejecuta la copia al arrancar si el servidor estaba apagado a la hora programada. RandomizedDelaySec reparte la carga cuando varios servidores escriben en el mismo almacenamiento.

Paso 9: Recibir un aviso cuando falla

La línea OnFailure= del servicio lanza otra unidad si la copia termina con error. Crea esa unidad como plantilla, de modo que reciba el nombre del servicio que falló. Este ejemplo envía el aviso a un tema de ntfy; sustituye la URL por la de tu servidor o tema:

sudo nano /etc/systemd/system/[email protected]
[Unit]
Description=Aviso de fallo en %i

[Service]
Type=oneshot
ExecStart=/usr/bin/curl -fsS -m 10 -H "Title: Fallo en %i (%H)" -H "Priority: high" -d "Revisa: journalctl -u %i" https://ntfy.sh/your_topic

%i es el nombre del servicio que falló y %H el nombre del host. Recarga systemd y activa el timer:

sudo systemctl daemon-reload
sudo systemctl enable --now restic-backup.timer

Lanza una ejecución manual para comprobar que todo funciona sin esperar a la noche:

sudo systemctl start restic-backup.service
sudo journalctl -u restic-backup.service -n 30 --no-pager

Deberías ver las dos secciones ==> Copia en ..., cada una con su línea snapshot ... saved. Comprueba la próxima ejecución programada:

systemctl list-timers restic-backup.timer
NEXT                        LEFT     LAST                        PASSED  UNIT                ACTIVATES
Fri 2026-09-26 02:38:12 UTC 16h left Thu 2026-09-25 10:20:41 UTC 1min ago restic-backup.timer restic-backup.service

Paso 10: Verificar la integridad cada semana

restic check comprueba la estructura del repositorio. Con --read-data-subset además descarga y verifica una parte de los datos; leer un porcentaje distinto cada semana detecta corrupción en el almacenamiento sin descargar todo el repositorio de golpe. Crea el servicio de verificación:

sudo nano /etc/systemd/system/restic-check.service
[Unit]
Description=Verificación de repositorios Restic
Wants=network-online.target
After=network-online.target
OnFailure=restic-notify@%n.service

[Service]
Type=oneshot
Environment=RESTIC_CACHE_DIR=/var/cache/restic
Nice=19
IOSchedulingClass=idle
ExecStart=/bin/bash -c 'set -euo pipefail; for f in /etc/restic/s3.env /etc/restic/sftp.env; do (set -a; . "$f"; set +a; restic check --read-data-subset=10%%); done'

En las unidades de systemd el carácter % se escribe %%. Crea el timer semanal:

sudo nano /etc/systemd/system/restic-check.timer
[Unit]
Description=Verificación semanal de Restic

[Timer]
OnCalendar=Sun *-*-* 05:00:00
Persistent=true

[Install]
WantedBy=timers.target

Actívalo y ejecútalo una vez:

sudo systemctl daemon-reload
sudo systemctl enable --now restic-check.timer
sudo systemctl start restic-check.service
sudo journalctl -u restic-check.service -n 20 --no-pager
using temporary cache in /var/cache/restic
check snapshots, trees and blobs
read 10.0% of data packs
no errors were found

Paso 11: Probar una restauración

Una copia que nunca se ha restaurado no está probada. Abre una shell de root y carga uno de los repositorios:

sudo -i
set -a; . /etc/restic/sftp.env; set +a

Busca en qué instantáneas está un archivo:

restic find /etc/nginx/nginx.conf

Restaura solo ese directorio de la última instantánea en una ruta temporal, sin tocar los archivos en uso:

restic restore latest --target /tmp/restore --include /etc/nginx

Compara el resultado con el original:

diff -r /etc/nginx /tmp/restore/etc/nginx && echo "Restauración correcta"
Restauración correcta

Para restauraciones desde S3 de mucho volumen, añade --limit-download (en KiB/s) si no quieres saturar el enlace. Cuando termines, borra la ruta temporal y sal de la shell de root:

rm -rf /tmp/restore
exit

Solución de problemas

unable to create lock in backend: repository is already locked. Una ejecución anterior se interrumpió y dejó un bloqueo. Comprueba que no hay ningún proceso de Restic en marcha (pgrep -a restic) y elimina los bloqueos obsoletos con restic unlock.

Fatal: wrong password or no key found. El archivo /etc/restic/password no coincide con el que se usó en restic init. Recupera la contraseña de tu copia externa; sin ella no es posible leer el repositorio.

restic check informa de errores en el índice. Reconstrúyelo con restic repair index y vuelve a ejecutar la verificación.

La copia SFTP pide contraseña o falla con Permission denied. Restic se ejecuta como root, así que la clave y ~/.ssh/config deben estar en /root/.ssh. Prueba con sudo sftp backup.your_domain.

Las copias tardan mucho en S3. Asegúrate de que RESTIC_CACHE_DIR apunta a un directorio persistente; sin caché local, Restic descarga los metadatos del repositorio en cada ejecución.

Conclusión

Tienes copias diarias cifradas en dos repositorios independientes, con retención automática, prioridad baja para no afectar a producción, verificación semanal de integridad, avisos de fallo y una restauración probada. Como siguientes pasos, guarda la contraseña del repositorio fuera del servidor, añade un volcado de tus bases de datos antes de la copia y repite la prueba de restauración de forma periódica, idealmente en un servidor distinto.