Une sauvegarde dont on peut restaurer s'exécute selon un calendrier sans intervention, garde une copie hors site qu'un serveur compromis ne peut pas supprimer, capture les bases de données dans un état cohérent et a été restaurée et chronométrée récemment. Avec restic, cela signifie un dépôt chiffré sur un stockage compatible S3 protégé par object lock ou par un serveur en mode append-only, des dumps de bases avant chaque exécution, un timer systemd avec alertes en cas d'échec et un exercice de restauration régulier. Ce guide monte cette configuration pour un serveur Linux, et les mêmes briques conviennent à un poste de développeur.
Fixer le RPO et le RTO avant de choisir les outils
Le recovery point objective (RPO) indique la quantité de données que vous pouvez vous permettre de perdre, exprimée en temps. Le recovery time objective (RTO) indique combien de temps le service peut rester indisponible pendant une restauration. Le responsable d'un service peut répondre aux deux questions en langage simple : « nous pouvons perdre au plus une journée de commandes » et « la boutique doit être de retour en moins de quatre heures ».
La perte maximale d'une sauvegarde planifiée correspond à peu près à l'intervalle entre deux exécutions plus le temps nécessaire pour atteindre le stockage externe. Une tâche nocturne qui démarre à 02:00 et termine son envoi à 02:40 donne un RPO dans le pire cas d'environ 24 heures et 40 minutes. Si c'est trop pour une base de données, sauvegardez-la plus souvent ou ajoutez l'archivage WAL.
Le RTO dépend surtout du temps de transfert et de reconstruction. Le nombre de secondes de transfert est égal au volume en mégabits divisé par le débit du lien en Mbit/s. Une restauration de 200 Go représente 1 600 000 mégabits, donc à 100 Mbit/s elle prend 16 000 secondes, environ 4,4 heures, avant même la restauration de la base. Si cela dépasse déjà le RTO, un bucket distant seul ne suffit pas, et il faut une copie locale ou un système de secours.
La règle 3-2-1 et son extension 3-2-1-1-0
La règle 3-2-1 impose trois copies des données, sur deux types de stockage différents, dont une copie hors site. Sur un VPS, le disque de production est la première copie, un dump ou un dépôt local sur un stockage séparé est la deuxième, et un bucket chez un autre fournisseur ou dans un autre compte est la troisième. Une seconde partition sur le même disque meurt avec la première, placez donc la copie locale sur un autre périphérique (le guide d'achat des SSD NVMe aide à le choisir) ou sur un autre hôte.
Les éditeurs de solutions de sauvegarde ont popularisé l'extension 3-2-1-1-0. Le 1 supplémentaire désigne une copie hors ligne ou immuable, afin qu'un attaquant disposant du root sur le serveur et de ses identifiants cloud ne puisse toujours pas la supprimer. Le 0 signifie zéro erreur dans les contrôles d'intégrité et les tests de restauration. La plupart des installations oublient ce dernier chiffre, alors que c'est lui qui montre si les quatre autres sont réels.
Ce qu'il faut sauvegarder et ce qu'il faut exclure
Sauvegardez ce que vous ne pouvez pas recréer rapidement à partir d'une source publique : /etc, le code applicatif et les fichiers envoyés sous /srv ou /var/www, les répertoires personnels avec les clés SSH et GPG, les crontabs, le matériel TLS, les dumps de bases et la liste des paquets installés issue de apt-mark showmanual. Sur un poste de développeur, ce qui compte, ce sont le travail non poussé, les stashes, les dotfiles, les clés et les documents.
Excluez les données volumineuses ou reproductibles : caches, node_modules, environnements virtuels, artefacts de build, images de conteneurs, poids de modèles et swap. Excluez les répertoires de bases de données actives comme /var/lib/postgresql, car une copie prise sur un serveur en marche n'est pas une sauvegarde exploitable. Dans les fichiers d'exclusion de restic, un motif qui commence par / correspond à un chemin absolu, et un simple nom comme node_modules correspond à n'importe quelle profondeur :
/var/lib/postgresql
/var/lib/docker
/var/cache
/var/tmp
/swapfile
/home/*/.cache
node_modules
.venv
__pycache__
*.tmp
--exclude-caches ignore aussi les répertoires qui contiennent un fichier standard CACHEDIR.TAG, et --one-file-system empêche restic de passer dans d'autres systèmes de fichiers montés.
Les bases de restic : dépôt, chiffrement et mot de passe
restic stocke des snapshots dédupliqués dans un dépôt sur disque local, en SFTP, sur son propre serveur REST ou dans un stockage objet, et n'envoie que les nouveaux blocs. Le chiffrement ne peut pas être désactivé : selon la documentation de conception de restic, toutes les données sont chiffrées en AES-256 en mode compteur et authentifiées avec Poly1305-AES, et la clé est protégée par un mot de passe renforcé avec scrypt.
Gardez les paramètres dans un fichier d'environnement lisible par root seulement, afin que les commandes interactives et les tâches planifiées utilisent les mêmes valeurs. La documentation des dépôts liste les formats d'URL, par exemple s3:https://server:port/bucket pour les services compatibles S3.
# /etc/restic/restic.env, mode 600
RESTIC_REPOSITORY=s3:https://s3.example.com/web1-backups
RESTIC_PASSWORD_FILE=/etc/restic/password
RESTIC_CACHE_DIR=/var/cache/restic
AWS_ACCESS_KEY_ID=replace-me
AWS_SECRET_ACCESS_KEY=replace-me
HEARTBEAT_URL=https://monitor.example.net/ping/web1-backup
ALERT_WEBHOOK_URL=https://chat.example.net/hooks/backups
install -d -m 700 /etc/restic
(umask 077; openssl rand -base64 32 > /etc/restic/password)
set -a; . /etc/restic/restic.env; set +a
restic init
restic key add # second mot de passe pour une copie de secours hors ligne
restic backup --one-file-system --exclude-caches \
--exclude-file=/etc/restic/excludes.txt /etc /root /home /srv /var/www
restic snapshots
La documentation est sans ambiguïté : perdre le mot de passe signifie perdre les données de façon irrémédiable. Conservez le mot de passe, l'URL du dépôt et les identifiants du bucket dans un gestionnaire de mots de passe et dans une copie hors ligne scellée, jamais uniquement sur le serveur que vous protégez. Avec un gestionnaire de secrets, RESTIC_PASSWORD_COMMAND peut afficher le mot de passe au lieu de lire un fichier.
Rétention avec forget et prune, intégrité avec check
restic forget applique une politique de rétention à chaque groupe de snapshots (par hôte et chemins par défaut), et prune supprime les données qu'aucun snapshot ne référence plus. L'option --prune enchaîne les deux :
restic forget --prune --keep-daily 14 --keep-weekly 8 --keep-monthly 12 --keep-yearly 3
restic check --read-data-subset=10%
Cette politique conserve le snapshot le plus récent de chacun des 14 derniers jours qui en ont un, puis un par semaine sur 8 semaines, un par mois sur 12 mois et un par an sur 3 ans. Prévisualisez tout changement de politique en ajoutant --dry-run. La documentation de forget précise que le dépôt est verrouillé pendant prune, donc planifiez-le à distance des sauvegardes et donnez --retry-lock à la commande de sauvegarde.
restic check vérifie la structure et les métadonnées. --read-data télécharge chaque pack, ce qui coûte du temps et du trafic sortant, tandis que --read-data-subset en lit une partie : 10% choisit un sous-ensemble aléatoire à chaque exécution, et 3/12 lit un douzième fixe, comme le décrit le guide de maintenance des dépôts. Un contrôle réussi prouve la cohérence, pas votre capacité à remettre un service en ligne.
Un dépôt hors site qu'un rançongiciel ne peut pas effacer
Un attaquant root peut lire /etc/restic/restic.env. Si ces identifiants permettent de supprimer des objets, la copie hors site peut être supprimée elle aussi. Limitez les identifiants à un seul bucket, gardez les identifiants d'administration hors du serveur et appliquez les principes de moindre privilège du guide de durcissement d'un VPS Linux. Choisissez ensuite l'une des deux protections suivantes.
Serveur REST en mode append-only
Le rest-server de restic propose un mode --append-only qui accepte les nouvelles sauvegardes mais interdit de supprimer ou de modifier les existantes. La documentation de restic recommande d'exécuter forget et prune pour ces dépôts depuis une machine séparée et bien protégée, et d'utiliser --keep-within plutôt que des règles comme --keep-weekly, car un client compromis pourrait ajouter de faux snapshots aux horodatages choisis qui prendraient la place des vrais.
Object lock sur un stockage compatible S3
S3 Object Lock conserve les versions d'objets selon un modèle write-once-read-many. Il exige le versionnage, et en mode compliance personne, pas même l'utilisateur root du compte, ne peut supprimer une version verrouillée avant sa date de fin de rétention. Depuis la version 0.13, restic envoie les sommes de contrôle d'envoi exigées par ces buckets, il fonctionne donc avec une rétention par défaut du bucket (ajoutez --endpoint-url pour les autres fournisseurs) :
aws s3api create-bucket --bucket web1-backups --object-lock-enabled-for-bucket
aws s3api put-object-lock-configuration --bucket web1-backups \
--object-lock-configuration '{"ObjectLockEnabled":"Enabled","Rule":{"DefaultRetention":{"Mode":"COMPLIANCE","Days":30}}}'
Quand restic supprime un fichier dans un tel bucket, S3 ajoute un marqueur de suppression et la version verrouillée reste en place. Après une attaque, copiez le bucket tel qu'il était, par exemple avec rclone copy --s3-version-at "2026-09-20" offsite:web1-backups /srv/recovered-repo, puis restaurez depuis cette copie. Les données supprimées par prune continuent d'occuper de l'espace jusqu'à la fin de la rétention, donc faites expirer les versions non courantes avec une règle de cycle de vie qui n'intervient pas avant ce délai. Testez tout le cycle chez votre fournisseur, car les implémentations compatibles S3 diffèrent.
Des sauvegardes PostgreSQL cohérentes
La documentation PostgreSQL sur les sauvegardes au niveau du système de fichiers est explicite : le serveur doit être arrêté pour obtenir une copie exploitable de son répertoire de données, et bloquer les connexions ne suffit pas, car des outils comme tar ne prennent pas d'instantané atomique et le serveur garde des données en tampon. Un snapshot atomique d'un volume contenant tous les fichiers de données et le WAL fonctionne, car PostgreSQL rejoue le WAL comme après un crash.
Pour la plupart des serveurs, les dumps logiques restent le choix correct le plus simple. pg_dump produit un export cohérent pendant que la base est utilisée et ne bloque pas les autres utilisateurs. Le format directory permet des dumps et des restaurations en parallèle. Les rôles n'en font pas partie, il faut donc exporter les objets globaux séparément. Ce script s'exécute avant chaque sauvegarde et garde les derniers dumps sur le disque local :
#!/usr/bin/env bash
# /usr/local/sbin/pg-dump-for-backup
set -euo pipefail
out=/var/backups/postgresql
install -d -m 700 -o postgres -g postgres "$out"
runuser -u postgres -- pg_dumpall --globals-only --file="$out/globals.sql"
runuser -u postgres -- psql -Atc \
"SELECT datname FROM pg_database WHERE datallowconn AND NOT datistemplate" |
while read -r db; do
rm -rf "$out/$db.new"
runuser -u postgres -- pg_dump --format=directory --jobs=2 --file="$out/$db.new" "$db"
rm -rf "$out/$db"
mv "$out/$db.new" "$out/$db"
done
Utilisez pg_basebackup lorsque la restauration logique d'un gros cluster dépasse le RTO ou que vous avez besoin d'une restauration à un instant donné. Il copie l'ensemble du cluster dans un répertoire vide via le protocole de réplication, exige un rôle doté de REPLICATION et une entrée dans pg_hba.conf, et transmet le WAL nécessaire au démarrage de la copie. pg_verifybackup contrôle le résultat à l'aide du manifeste :
sudo -u postgres pg_basebackup --pgdata=/var/backups/pg-base --wal-method=stream --checkpoint=fast
sudo -u postgres pg_verifybackup /var/backups/pg-base
pg_restore reconstruit les index à partir de leurs définitions. Avec de gros index vectoriels, comme les index HNSW du guide de recherche hybride avec pgvector, cette reconstruction peut représenter l'essentiel du temps de restauration.
Planifier avec un service et un timer systemd
Un service de type oneshot lance d'abord le dump, puis la sauvegarde. Si le dump échoue, la sauvegarde ne démarre pas et l'unité passe en échec. Enregistrez les unités dans /etc/systemd/system/ :
# restic-backup.service
[Unit]
Description=restic backup to the off-site repository
Wants=network-online.target
After=network-online.target postgresql.service
OnFailure=backup-alert@%n.service
[Service]
Type=oneshot
EnvironmentFile=/etc/restic/restic.env
CacheDirectory=restic
Nice=10
IOSchedulingClass=idle
ExecStartPre=/usr/local/sbin/pg-dump-for-backup
ExecStart=/usr/local/bin/restic backup --retry-lock 30m \
--one-file-system --exclude-caches \
--exclude-file=/etc/restic/excludes.txt --tag scheduled \
/etc /root /home /srv /var/www /var/backups/postgresql
ExecStartPost=-/usr/bin/curl -fsS --max-time 10 --retry 3 ${HEARTBEAT_URL}
# restic-backup.timer
[Unit]
Description=Nightly restic backup
[Timer]
OnCalendar=*-*-* 02:15:00
RandomizedDelaySec=30m
Persistent=true
[Install]
WantedBy=timers.target
Activez-le avec systemctl enable --now restic-backup.timer et vérifiez-le avec systemctl list-timers. D'après systemd.timer, Persistent=true rattrape une exécution manquée pendant que la machine était éteinte, et RandomizedDelaySec= étale dans le temps les hôtes qui partagent un bucket. Les services oneshot n'ont pas de délai de démarrage par défaut. Sur un portable Linux, la même paire fonctionne en unités utilisateur pour le répertoire personnel.
Un restic-maintenance.service hebdomadaire avec le même en-tête s'occupe de la rétention et des contrôles :
ExecStart=/usr/local/bin/restic forget --prune --retry-lock 2h \
--keep-daily 14 --keep-weekly 8 --keep-monthly 12 --keep-yearly 3
ExecStart=/usr/local/bin/restic check --read-data-subset=10%%
Écrivez %% pour obtenir un signe pourcentage littéral, car % introduit un spécificateur dans les fichiers d'unité.
Supervision et alertes en cas d'échec
Utilisez deux signaux indépendants, sinon un échec silencieux se découvre pendant l'incident. Le premier est OnFailure=. Les codes de sortie de restic sont précis : 1 signale une commande en échec, 3 des fichiers source illisibles, 10 un dépôt inexistant, 11 un verrouillage impossible et 12 un mauvais mot de passe. systemd considère tout code non nul comme un échec, ce qui est juste pour 3 : le snapshot existe, mais il comporte des trous.
# [email protected]
[Unit]
Description=Alert for failed %i
[Service]
Type=oneshot
EnvironmentFile=/etc/restic/restic.env
ExecStart=/usr/bin/curl -fsS --max-time 10 --retry 3 \
--data-urlencode "text=%H: %i failed, see journalctl -u %i" \
${ALERT_WEBHOOK_URL}
Un seul modèle sert toutes les tâches, et systemd lui transmet des variables comme MONITOR_EXIT_STATUS. Testez la chaîne avec systemctl start [email protected].
Le second signal est un dead man's switch : ExecStartPost= appelle une URL de heartbeat uniquement après un succès, et l'outil de supervision alerte si aucun appel n'arrive en, disons, 26 heures. Cela détecte un timer désactivé ou un hôte éteint. Pour une vue extérieure, lancez chaque jour restic snapshots --latest 1 --json --no-lock depuis une autre machine et alertez si le snapshot le plus récent est plus ancien que le RPO.
Mener un exercice de restauration chronométré
Un exercice transforme les hypothèses en chiffres. Faites-en un après la mise en place, après chaque changement important et au moins une fois par trimestre, sur une VM propre plutôt que sur l'hôte de production, avec seulement ce dont disposerait un ingénieur lors d'un vrai incident : l'entrée du gestionnaire de mots de passe et le runbook.
- Démarrez un chronomètre au début de l'incident simulé.
- Récupérez les identifiants, installez restic et listez les snapshots. L'âge du snapshot le plus récent est votre RPO réel.
- Restaurez les fichiers et les dumps, puis la base, en chronométrant chaque étape.
- Démarrez une copie de préproduction de l'application et déroulez un vrai parcours.
- Notez les durées et les problèmes, puis mettez à jour le runbook.
set -a; . ./restic.env; set +a
restic snapshots --latest 3
time restic restore latest --target /restore --verify
sudo -u postgres psql -f /restore/var/backups/postgresql/globals.sql
sudo -u postgres createdb app
time sudo -u postgres pg_restore --jobs=4 --dbname=app /restore/var/backups/postgresql/app
sudo -u postgres psql -d app -c "SELECT max(created_at) FROM orders;"
--verify relit les fichiers restaurés et les compare au snapshot. La dernière requête montre combien de données la restauration a réellement perdues. Les erreurs indiquant que certains rôles existent déjà sont attendues. Entraînez-vous aussi à restaurer un seul fichier avec --include /etc/nginx/nginx.conf, la demande réelle la plus fréquente.
Comparez le total au RTO. Les surprises habituelles sont des identifiants manquants, un mot de passe qui n'existait que sur le serveur perdu, des reconstructions d'index lentes et des fichiers oubliés comme les clés TLS ou un .env hors des chemins sauvegardés.
restic, BorgBackup ou snapshots de système de fichiers
Beaucoup d'installations combinent ces approches : un snapshot ZFS, Btrfs ou LVM pour un retour arrière local rapide, plus des sauvegardes de fichiers hors site. BorgBackup 1.4 est la branche stable actuelle.
| Propriété | restic | BorgBackup 1.4 | Snapshots de système de fichiers |
|---|---|---|---|
| Destinations | Local, SFTP, serveur REST, stockages compatibles S3 et autres stockages cloud | Local, ou SSH, idéalement avec Borg sur le serveur | Même pool ; le hors site exige une réplication comme zfs send |
| Chiffrement | Toujours actif | Choisi à la création du dépôt, facultatif | Dépend du stockage |
| Client compromis | rest-server --append-only, object lock sur le bucket |
borg serve --append-only |
Protégé seulement si répliqué vers un système administré séparément |
| Cohérence des bases | Dumps nécessaires | Dumps nécessaires | Cohérent après crash si données et WAL sont dans un même snapshot atomique |
| Restauration | Du fichier unique à l'arborescence complète, montage FUSE | Du fichier unique à l'arborescence complète, montage FUSE | Retour arrière du volume entier ou copie depuis un snapshot monté |
| Clients | Linux, macOS, Windows, BSD | Linux, macOS, BSD ; Windows seulement via WSL ou Cygwin, en expérimental | Dépend du système de fichiers |
Un snapshot sur le même pool protège contre un déploiement raté, pas contre la perte du pool ni la compromission de l'hôte : ce n'est donc pas à lui seul une copie au sens de la règle 3-2-1.
Liste de contrôle
- Le RPO et le RTO sont écrits et validés avec le responsable du service.
- Il existe trois copies, dont une hors site et une immuable ou append-only.
- Le mot de passe, l'URL du dépôt et les identifiants sont dans un gestionnaire de mots de passe et hors ligne.
- Les répertoires de bases actives sont exclus, et les dumps sont inclus.
- PostgreSQL est couvert par
pg_dumpavec les objets globaux, ou par unpg_basebackupvérifié. - Le timer utilise
Persistent=true, et le gestionnaireOnFailure=s'est déclenché au moins une fois en test. - Un heartbeat alerte en l'absence de succès, et un contrôle externe alerte sur des snapshots trop anciens.
forget --pruneetcheck --read-data-subsettournent chaque semaine.- Les identifiants du serveur ne peuvent pas supprimer les versions verrouillées.
- Un exercice de restauration chronométré a eu lieu ce trimestre, et le runbook reflète ses conclusions.