I already had backups with rsync and cron, but rsync copies files, not snapshots. If you accidentally delete a file and the backup syncs before you notice, you lose it. Restic solves that and adds something rsync will never provide: AES-256 encryption, deduplication, and snapshots with navigable history.
What makes Restic different#
| Feature | rsync | Restic |
|---|---|---|
| AES-256 encryption | No | Yes |
| Deduplication | No | Yes (block-level) |
| Navigable snapshots | No | Yes |
| Multiple backends | No | SFTP, S3, Backblaze, rclone… |
| Integrity checking | No | restic check |
| Retention policy | Manual | restic forget --prune |
Deduplication is especially useful for database backups and configuration directories that change little: a Restic repository with 6 months of daily backups usually takes up much less space than 180 full copies.
Installation#
On Ubuntu/Debian, the official repository version is usually outdated. Better to download the binary directly from the official GitHub releases:
# Descarga la última versión (ajusta la versión si hay una más reciente)
wget https://github.com/restic/restic/releases/download/v0.17.3/restic_0.17.3_linux_amd64.bz2
bunzip2 restic_0.17.3_linux_amd64.bz2
chmod +x restic_0.17.3_linux_amd64
sudo mv restic_0.17.3_linux_amd64 /usr/local/bin/restic
# Verificar
restic versionOr with system repositories if you don’t mind the version:
sudo apt install restic # Debian/Ubuntu
sudo dnf install restic # Fedora/RHELKey concepts before you start#
- Repository: the destination where Restic stores the backups. It can be a local folder, an SFTP server, an S3 bucket, etc.
- Snapshot: every time you run
restic backup, Restic saves a point-in-time snapshot. Snapshots share deduplicated blocks, so they don’t multiply the space used. - Password: the repository is encrypted with a password. Without it, the data is unreadable. Store it in a password manager or in a file separate from the backup.
Initialize a repository#
Option A: local repository#
restic init --repo /opt/backups/mi-servidorOption B: repository on remote server via SFTP#
restic init --repo sftp:usuario@servidor-backup:/opt/restic/mi-servidorOption C: repository on S3 (or compatible: Backblaze B2, MinIO…)#
export AWS_ACCESS_KEY_ID="TU_ACCESS_KEY_AQUI"
export AWS_SECRET_ACCESS_KEY="TU_SECRET_KEY_AQUI"
restic init --repo s3:s3.eu-west-1.amazonaws.com/mi-bucket-backups/mi-servidorIn all three cases, Restic will ask you for a password to encrypt the repository. Guard it well — without it you can’t restore anything.
Environment variables file#
To avoid typing the password with every Restic command, create a variables file:
# /etc/restic/env.sh (solo root puede leerlo)
sudo mkdir -p /etc/restic
sudo chmod 700 /etc/restic
sudo tee /etc/restic/env.sh > /dev/null <<'EOF'
export RESTIC_REPOSITORY="/opt/backups/mi-servidor"
export RESTIC_PASSWORD="TU_CONTRASENA_AQUI"
EOF
sudo chmod 600 /etc/restic/env.shFrom now on, before any Restic command:
source /etc/restic/env.shOr with environment variables inline for scripts:
env $(cat /etc/restic/env.sh | grep export | sed 's/export //') restic snapshotsFirst backup#
source /etc/restic/env.sh
# Backup de los datos de Docker y configuraciones
restic backup \
/opt/app/ \
/etc/nginx/ \
/home/usuario/ \
--exclude='/opt/app/logs' \
--exclude='*.tmp' \
--tag servidor-webThe output shows how many files it processed, how many are new, and the space saved by deduplication:
Files: 1234 new, 567 changed, 8901 unmodified
Dirs: 45 new, 12 changed, 89 unmodified
Added to the repo: 234.567 MiB
processed 10702 files, 1.234 GiB in 0:23
snapshot a1b2c3d4 savedView and navigate snapshots#
# Listar todos los snapshots
restic snapshots
# Listar ficheros dentro de un snapshot específico
restic ls a1b2c3d4
# Buscar un fichero en todos los snapshots
restic find --tag servidor-web nombre-fichero.confOutput of restic snapshots:
ID Time Host Tags Paths
────────────────────────────────────────────────────────────────────
a1b2c3d4 2026-05-11 03:00:02 mi-servidor servidor-web /opt/app, /etc/nginx, /home/usuario
e5f6a7b8 2026-05-10 03:00:01 mi-servidor servidor-web /opt/app, /etc/nginx, /home/usuarioRestore data#
Complete restoration of a snapshot#
# Restaura todo en /tmp/restauracion para revisar antes de mover
restic restore a1b2c3d4 --target /tmp/restauracionRestore only a file or directory#
# Restaura solo nginx.conf del snapshot más reciente
restic restore latest --target /tmp/restauracion \
--include /etc/nginx/nginx.confMount the repository as a filesystem (useful for exploring)#
# Requiere FUSE instalado: sudo apt install fuse
mkdir -p /mnt/restic-backups
restic mount /mnt/restic-backups &
# Ahora puedes navegar por todos los snapshots
ls /mnt/restic-backups/snapshots/
# → a1b2c3d4/ e5f6a7b8/ latest/
# Cuando acabes
fusermount -u /mnt/restic-backupsAutomatic retention policy#
Without a retention policy, the repository grows indefinitely. This is the configuration I use for daily backups:
# 7 días diarios, 4 semanas, 6 meses, 1 año
restic forget \
--keep-daily 7 \
--keep-weekly 4 \
--keep-monthly 6 \
--keep-yearly 1 \
--tag servidor-web \
--prune--prune physically removes unreferenced data. Without it, forget only removes the snapshot metadata but doesn’t free up space.
Verify repository integrity#
# Verificación rápida de metadatos
restic check
# Verificación completa leyendo todos los datos (lento, hazlo mensualmente)
restic check --read-dataIf restic check fails, the repository has corruption. That’s why it’s always recommended to have at least two repositories in different destinations (the well-known 3-2-1 rule).
Automate with systemd timer#
Restic integrates perfectly with systemd timers, which allow capturing output in journald and executing the task even if the server was powered off at the scheduled time.
/etc/systemd/system/restic-backup.service:
[Unit]
Description=Backup diario con Restic
After=network.target
[Service]
Type=oneshot
User=root
EnvironmentFile=/etc/restic/env.sh
ExecStart=/usr/local/bin/restic backup \
/opt/app/ \
/etc/nginx/ \
/home/usuario/ \
--exclude='/opt/app/logs' \
--tag servidor-web
ExecStartPost=/usr/local/bin/restic forget \
--keep-daily 7 \
--keep-weekly 4 \
--keep-monthly 6 \
--tag servidor-web \
--prune
StandardOutput=journal
StandardError=journal/etc/systemd/system/restic-backup.timer:
[Unit]
Description=Ejecuta backup Restic diariamente
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
RandomizedDelaySec=10min
[Install]
WantedBy=timers.targetActivate:
sudo systemctl daemon-reload
sudo systemctl enable --now restic-backup.timer
# Verificar que está activo
sudo systemctl list-timers restic-backup.timerRandomizedDelaySec=10min distributes backups in random windows to prevent all servers from hitting the SFTP or S3 destination at the same time.
Email notifications on completion#
By combining with the email notification system with msmtp, we can receive a backup summary. Modify ExecStart with a wrapper script:
/usr/local/bin/restic-backup.sh:
#!/bin/bash
set -euo pipefail
source /etc/restic/env.sh
LOG=$(mktemp)
STATUS=0
restic backup \
/opt/app/ \
/etc/nginx/ \
/home/usuario/ \
--exclude='/opt/app/logs' \
--tag servidor-web > "$LOG" 2>&1 || STATUS=$?
restic forget \
--keep-daily 7 \
--keep-weekly 4 \
--keep-monthly 6 \
--tag servidor-web \
--prune >> "$LOG" 2>&1 || STATUS=$?
SUBJECT="[Backup] $(hostname) - $(date '+%Y-%m-%d')"
[ $STATUS -ne 0 ] && SUBJECT="[ERROR Backup] $(hostname) - $(date '+%Y-%m-%d')"
cat "$LOG" | mail -s "$SUBJECT" usuario@ejemplo.com
rm -f "$LOG"
exit $STATUSchmod +x /usr/local/bin/restic-backup.shAnd in the .service, change ExecStart and ExecStartPost with a single line:
ExecStart=/usr/local/bin/restic-backup.shWeekly integrity check#
Add a second timer for the weekly verification, which is more expensive:
/etc/systemd/system/restic-check.service:
[Unit]
Description=Verificación semanal de integridad Restic
After=network.target
[Service]
Type=oneshot
User=root
EnvironmentFile=/etc/restic/env.sh
ExecStart=/usr/local/bin/restic check
StandardOutput=journal
StandardError=journal/etc/systemd/system/restic-check.timer:
[Unit]
Description=Verifica integridad del repositorio Restic cada semana
[Timer]
OnCalendar=Sun *-*-* 04:00:00
Persistent=true
[Install]
WantedBy=timers.targetsudo systemctl enable --now restic-check.timerView the status in the system log#
# Último backup
journalctl -u restic-backup.service -n 50
# Seguir en tiempo real durante la ejecución manual
journalctl -u restic-backup.service -f
# Historial de ejecuciones del timer
systemctl status restic-backup.timerConclusion#
Restic doesn’t completely replace rsync for live directory synchronization cases, but for backups with history, it’s clearly superior: built-in encryption, transparent deduplication, and browsable snapshots without additional scripts. Integration with systemd timers and email notifications closes the loop: you know the backup ran, when it failed, and you can restore any point in time in minutes.
The real key to any backup strategy is not the software you choose, but verifying that you can restore. Test restic restore in a temporary directory before you really need it.