Introduction
Une sauvegarde n’a de valeur que si elle se lance à l’heure, s’exécute sans risque et se restaure rapidement. Cron sait planifier des tâches, mais en production, les sauvegardes exigent aussi un ordonnancement des dépendances, la reprise des exécutions manquées, une journalisation centralisée, des notifications d’échec et un cloisonnement strict. C’est précisément là que systemd excelle.
Principe important : systemd n’est pas une application de sauvegarde. Il ne copie pas les données et ne gère pas les dépôts. Il orchestre vos flux de sauvegarde en coordonnant les outils en lesquels vous avez déjà confiance, comme rsync, Restic, BorgBackup, tar, les utilitaires de base de données, les outils de snapshot et vos propres scripts shell.
Les exemples ci-dessous montrent comment bâtir une automatisation de sauvegarde Linux fiable, auditée et sécurisée avec des services et des timers systemd.
1. Architecture systemd pour orchestrer les sauvegardes
Au PID 1, systemd est l’init et le gestionnaire de services. Les sauvegardes tirent profit de son modèle d’unités et de son suivi d’état.
- Unités : définitions typées des éléments gérés par systemd. Pour les sauvegardes, on utilise :
- Units de service : exécutent vos scripts et outils de sauvegarde
- Unit0s de timer : planifient l’activation des services
- Unit0s target : regroupent des services liés (ex. tous les jobs de sauvegarde)
- Dépendances et ordre : Requires=, Wants=, Before=, After= définissent le séquencement correct
- État et redémarrages : codes de sortie, Restart=, SuccessExitStatus= définissent la santé et les reprises
- Journalisation centrale : journald collecte stdout/stderr pour une analyse aisée
Schéma :
Application -> Fichiers/BD
^ |
| v
service systemd (backup.service)
^ |
| v
timer systemd (backup.timer)
|
journald + notifications
2. Cron vs timers systemd
| Fonctionnalité | cron | timers systemd |
|---|---|---|
| Syntaxe de planification | chaînes crontab | OnCalendar, monotoniques, événementielles |
| Exécutions manquées | Non géré | Persistent=true rejoue les runs manqués |
| Dépendances | Aucune | Requires=/After=/Before= |
| Conscience d’état | Aucune | Connaît l’état (en cours/échec) d’un service |
| Journalisation | Mail ou fichiers perso | journalctl par unité |
| Contrôle du parallélisme | flock manuel | One-shot + verrous systemd |
| Redémarrage/backoff | Manuel | Restart=, RestartSec= |
| Durcissement sécurité | Limité | Bacs à sable fins et granulaires |
3. Un service de sauvegarde prêt pour la production
Ce service exécute un script de sauvegarde rsync renforcé, sous un utilisateur non privilégié. Adaptez les chemins à votre environnement.
# /etc/systemd/system/backup.service
[Unit]
Description=Sauvegarde du système de fichiers (snapshots rsync)
Documentation=man:systemd.service(5)
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
User=backup
Group=backup
WorkingDirectory=/var/lib/backup
EnvironmentFile=/etc/backup/backup.env
ExecStart=/usr/local/bin/backup-rsync.sh
# rsync retourne 24 lorsque des fichiers disparaissent pendant le transfert ; considérer comme un succès
SuccessExitStatus=0 24
TimeoutStartSec=2h
Restart=no
# Réglages de ressources
Nice=10
IOSchedulingClass=best-effort
IOSchedulingPriority=6
# Durcissement
NoNewPrivileges=true
PrivateTmp=true
PrivateDevices=true
ProtectSystem=strict
ProtectHome=read-only
ReadWritePaths=/var/lib/backup /mnt/backup-target
CapabilityBoundingSet=
LockPersonality=true
RestrictRealtime=true
RestrictSUIDSGID=true
SystemCallArchitectures=native
[Install]
WantedBy=backup.target
Pourquoi durcir les jobs de sauvegarde : les sauvegardes manipulent souvent de larges ensembles de données et des secrets. Un bac à sable réduit le rayon d’impact si un script ou un outil se comporte mal ou est compromis.
4. Un script rsync réaliste
Cet exemple crée des instantanés dédupliqués via hard links, gère le verrouillage, vérifie l’espace disque, journalise clairement et applique une rétention basique.
# /usr/local/bin/backup-rsync.sh
#!/usr/bin/env bash
set -euo pipefail
: "${RSYNC_SRC:?RSYNC_SRC not set}"
: "${RSYNC_DST:?RSYNC_DST not set}" # p. ex. /mnt/backup-target/host1
RETENTION_DAYS="${RETENTION_DAYS:-30}"
MIN_FREE_GB="${MIN_FREE_GB:-10}"
SNAP_ROOT="$RSYNC_DST/snapshots"
CUR="$SNAP_ROOT/current"
TS="$(date +%F-%H%M%S)"
NEW="$SNAP_ROOT/$TS"
log() { echo "[backup] $(date -Is) $*"; }
notify_err() { logger -t backup "$*" || true; }
mkdir -p "$SNAP_ROOT"
mkdir -p /run/lock
exec 9>/run/lock/backup-rsync.lock
if ! flock -n 9; then log "Une autre sauvegarde est en cours"; exit 0; fi
# Vérification de l’espace disque
avail_gb=$(df -P "$RSYNC_DST" | awk 'NR==2 {print int($4/1024/1024)}')
if (( avail_gb < MIN_FREE_GB )); then
notify_err "Espace disque insuffisant : ${avail_gb}GB < ${MIN_FREE_GB}GB"
exit 70
fi
log "Démarrage de l’instantané rsync vers $NEW"
mkdir -p "$NEW.incomplete"
trap 'rm -rf "$NEW.incomplete"' EXIT
link_dest=( )
[[ -e "$CUR" ]] && link_dest=(--link-dest="$CUR")
set +e
/usr/bin/rsync -aHAX --numeric-ids \
--delete-delay --partial --info=stats2,progress2 \
--one-file-system "${link_dest[@]}" \
"$RSYNC_SRC/" "$NEW.incomplete/"
rc=$?
set -e
if [[ $rc -ne 0 && $rc -ne 24 ]]; then
notify_err "rsync s’est terminé avec le code $rc"
exit $rc
fi
mv "$NEW.incomplete" "$NEW"
ln -sfn "$NEW" "$CUR"
# Rétention : supprimer les instantanés de plus de N jours
find "$SNAP_ROOT" -mindepth 1 -maxdepth 1 -type d -mtime +"$RETENTION_DAYS" -not -name current -exec rm -rf {} +
log "Sauvegarde terminée : $NEW"
exit 0
Exemple de fichier d’environnement :
# /etc/backup/backup.env
RSYNC_SRC=/srv/app-data
RSYNC_DST=/mnt/backup-target/host1
RETENTION_DAYS=30
MIN_FREE_GB=20
5. Planification avec des timers systemd
Une exécution quotidienne à 02:15, rattrapage après un arrêt, et gigue pour éviter l’effet de troupeau.
# /etc/systemd/system/backup.timer
[Unit]
Description=Planifier la sauvegarde du système de fichiers
[Timer]
OnCalendar=02:15
OnBootSec=15min
AccuracySec=1min
RandomizedDelaySec=20min
Persistent=true
Unit=backup.service
[Install]
WantedBy=timers.target
Autres exemples :
- Horaire horaire : OnCalendar=hourly
- Toutes les 6 heures : OnCalendar=--* 00,06,12,18:00:00
- Depuis la dernière activation : OnUnitActiveSec=24h
Activer et démarrer : systemctl enable --now backup.timer
6. Supervision et dépannage
- État et dernier run : systemctl status backup.service
- Voir les journaux : journalctl -u backup.service -n 200 --since=yesterday
- Lister les timers : systemctl list-timers --all
- Inspecter les échecs : systemctl --failed; journalctl -xeu backup.service
- Afficher le dernier code de sortie : systemctl show -p Result,ExecMainStatus backup.service
Si un run a été manqué pendant l’arrêt de l’hôte, Persistent=true le rejoue au démarrage.
7. Coordonner des sauvegardes applicatives
Certaines applications exigent une mise au repos ou un dump logique. Exemple : arrêter PostgreSQL, sauvegarder les données, redémarrer, valider.
# /etc/systemd/system/backup-pg.service
[Unit]
Description=Sauvegarde applicative : PostgreSQL + fichiers
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
User=backup
Group=backup
EnvironmentFile=/etc/backup/pg.env
ExecStartPre=/bin/systemctl stop postgresql.service
ExecStart=/usr/local/bin/backup-rsync.sh
ExecStartPost=/bin/systemctl start postgresql.service
ExecStartPost=/usr/bin/pg_isready -q -t 30
TimeoutStartSec=2h
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=read-only
ReadWritePaths=/var/lib/backup /mnt/backup-target
[Install]
WantedBy=backup.target
Remarque : privilégiez les dumps logiques (pg_dump/pg_basebackup) ou l’intégration snapshot pour zéro interruption. Cet exemple illustre l’ordonnancement via ExecStartPre/ExecStart/ExecStartPost.
8. Intégration Restic
Environnement et secrets :
# /etc/restic/backup.env (0600)
RESTIC_REPOSITORY=s3:s3.example.com/restic/host1
RESTIC_PASSWORD_FILE=/etc/restic/pass
RESTIC_CACHE_DIR=/var/cache/restic
RESTIC_TAGS=host1,data
Service et timer :
# /etc/systemd/system/restic-backup.service
[Unit]
Description=Sauvegarde Restic
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
EnvironmentFile=/etc/restic/backup.env
ExecStart=/usr/bin/restic backup /srv/app-data --tag ${RESTIC_TAGS} --one-file-system
ExecStartPost=/usr/bin/restic forget --prune --keep-daily 7 --keep-weekly 4 --keep-monthly 6
Nice=10
IOSchedulingClass=best-effort
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=read-only
ReadWritePaths=/var/cache/restic
[Install]
WantedBy=backup.target
# /etc/systemd/system/restic-backup.timer
[Unit]
Description=Planifier la sauvegarde Restic
[Timer]
OnCalendar=03:00
Persistent=true
Unit=restic-backup.service
[Install]
WantedBy=timers.target
Vérification et restauration :
- Intégrité : systemctl start restic-check.service avec ExecStart=/usr/bin/restic check
- Lister les snapshots : restic snapshots
- Restaurer le plus récent : restic restore latest --target /restore
9. Intégration BorgBackup
# /etc/systemd/system/borg-backup.service
[Unit]
Description=Job BorgBackup
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
Environment=BORG_REPO=/mnt/borg/host1 BORG_PASSCOMMAND="cat /etc/borg/pass"
ExecStart=/usr/bin/borg create --stats --compression zstd,6 ::host1-{now:%Y-%m-%d-%H%M} /srv/app-data
ExecStartPost=/usr/bin/borg prune --list --keep-daily 7 --keep-weekly 4 --keep-monthly 6
ExecStartPost=/usr/bin/borg compact
Nice=10
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=read-only
ReadWritePaths=/mnt/borg
[Install]
WantedBy=backup.target
Contrôle et restauration :
- borg list
- borg extract ::host1-2024-07-01-0215 path/in/archive -C /restore
- Montage pour parcourir : borg mount ::host1-latest /mnt/borgfs
10. Notifications et gestion OnFailure
Utilisez OnFailure= pour déclencher des alertes lorsqu’une unité échoue.
# /etc/systemd/system/backup.service (extrait)
OnFailure=notify-backup@%n.service
# /etc/systemd/system/[email protected]
[Unit]
Description=Notifier l’échec de %I
[Service]
Type=oneshot
Environment=WEBHOOK_URL=https://hooks.example/backup
ExecStart=/usr/bin/curl -fsS -X POST -H 'Content-Type: application/json' \
-d '{"text":"L’unité de sauvegarde %I a échoué sur %H"}' ${WEBHOOK_URL}
Remplacez curl par mailx, sendmail, ou des webhooks Slack/Discord selon vos besoins.
11. Essentiels de sécurité
- Exécuter avec des utilisateurs les moins privilégiés possible ; éviter root quand c’est possible
- ProtectSystem=strict et n’autoriser que des ReadWritePaths explicites
- ProtectHome=read-only ou yes
- PrivateDevices=true, PrivateTmp=true, NoNewPrivileges=true
- Stocker les secrets dans des fichiers 0600 appartenant à root (via EnvironmentFile=)
- Envisager DynamicUser=yes avec StateDirectory= pour des comptes éphémères
12. Vérifiez vos sauvegardes
Une sauvegarde sans restauration testée ne doit jamais être considérée comme fiable.
- Contrôles périodiques : restic check, borg check, vérifications ponctuelles avec rsync
- Restauration d’échantillons vers un chemin de staging et comparaison de hachages sur les fichiers critiques
- Démarrer une VM de test et valider l’application restaurée de bout en bout
13. Automatiser les restaurations
Coordonnez les restaurations avec systemd pour éviter des retours arrière partiels.
# /etc/systemd/system/[email protected]
[Unit]
Description=Restaurer les données applicatives pour %i
After=network-online.target
Requires=network-online.target
[Service]
Type=oneshot
EnvironmentFile=/etc/restic/backup.env
ExecStartPre=/bin/systemctl stop app@%i.service
ExecStart=/usr/bin/restic restore latest --target /restore/%i
ExecStartPost=/usr/bin/rsync -aHAX --delete /restore/%i/ /srv/%i/
ExecStartPost=/bin/systemctl start app@%i.service
ExecStartPost=/usr/bin/curl -fsS http://127.0.0.1:8080/healthz
NoNewPrivileges=true
ProtectSystem=strict
ReadWritePaths=/restore /srv/%i
14. Exemple de reprise après sinistre (DR)
- Approvisionner un nouveau serveur (OS, utilisateurs, réseau)
- Monter ou configurer l’accès au dépôt de sauvegarde (S3, NFS, disque)
- Installer les outils de sauvegarde et copier les fichiers d’identifiants
- Restaurer la configuration : configurations système, unit files, fichiers d’environnement
- Restaurer les données : restic restore ou borg extract vers un staging, puis rsync vers les chemins finaux
- Restaurer la base de données : utiliser les fichiers pg_dump ou les snapshots Borg/Restic de dumps
- Démarrer les services dans l’ordre ; valider via health checks et journaux
- Réorienter le DNS ou le load balancer vers le nœud
- Lancer des vérifications du dépôt et consigner un rapport DR dans vos outils (tickets/wikis)
15. Erreurs courantes
- Tout exécuter en root sans nécessité
- Oublier Persistent=true, causant des jobs manqués
- Laisser cron et systemd planifier le même script
- Absence de verrouillage, causant des exécutions qui se chevauchent
- Sauvegarder des bases actives sans mise au repos ni dumps
- Pas de politique de rétention ou de vérification
- Journaux non centralisés dans journald ou mal rotatés
16. Performance : points d’attention
- Nice et planification IO pour réduire l’impact : Nice=10, IOSchedulingClass=best-effort
- RandomizedDelaySec pour étaler la charge sur une flotte
- Limiter le parallélisme au niveau de la bande passante IO ; planifier les gros jobs en heures creuses
- Utiliser one-file-system pour éviter de traverser des montages par inadvertance
- Conserver les dépôts sur un stockage rapide et fiable ; surveiller l’espace de façon proactive
17. Dépannage express
- Le timer ne déclenche jamais : systemctl list-timers ; vérifier la syntaxe calendrier ; s’assurer que le timer est activé
- Le service se termine immédiatement : systemctl status ... ; vérifier ExecStart, le chemin et les permissions
- Permission refusée : revoir le bac à sable et ReadWritePaths
- Dépôt indisponible : tester réseau et identifiants ; ajouter Wants=network-online.target
- Échecs de montages : s’assurer que les montages sont Before= et RequiredBy= le service qui en dépend
- Expirations : augmenter TimeoutStartSec et vérifier disques lents ou deltas volumineux
18. Liste de bonnes pratiques
- Utilisateurs au moindre privilège et bacs à sable stricts
- Sauvegardes chiffrées avec clés protégées
- Stockage hors site ou hors hôte
- Restaurations testées régulièrement et contrôles d’intégrité
- Timers surveillés et notifications via OnFailure
- Runbooks documentés et exercices DR
19. Architecture de bout en bout
Application -> Données + BD
|
v
services systemd (backup, restic, borg)
|
scripts de sauvegarde -> outils (rsync/Restic/Borg)
|
Dépôt chiffré (disque/S3/NFS)
|
Stockage hors site
|
Serveur de restauration -> validation -> retour en prod
Conclusion
systemd apporte une orchestration fiable aux flux de sauvegarde Linux : planification via des timers, exécution sûre avec des services durcis, ordonnancement des dépendances, journalisation centralisée et supervision exploitable. Vos logiciels de sauvegarde protègent les données ; systemd coordonne quand et comment ils s’exécutent, comment les échecs remontent et comment les restaurations se déroulent. La meilleure sauvegarde est celle que vous avez restaurée et validée avec succès.