[{"data":1,"prerenderedAt":6},["ShallowReactive",2],{"article:fr:server-backups-restic":3},{"html":4,"lang":5},"\u003Ch2>Une sauvegarde qui n'a jamais été restaurée n'existe pas\u003C\u002Fh2>\n\u003Cp>Tout le monde « a des sauvegardes », jusqu'au jour où il faut restaurer : le dump est vide depuis trois mois, l'archive est sur le même disque que le serveur, ou personne ne connaît le mot de passe de chiffrement. Une bonne stratégie tient en une règle, la \u003Cstrong>règle 3-2-1\u003C\u002Fstrong> : 3 copies des données, sur 2 supports différents, dont 1 hors site. Et en une discipline : \u003Cstrong>tester la restauration\u003C\u002Fstrong>.\u003C\u002Fp>\n\u003Cp>Ce guide met en place, sur un serveur Linux qui héberge une application web :\u003C\u002Fp>\n\u003Cul>\n\u003Cli>un dump MySQL cohérent, sans bloquer l'application ;\u003C\u002Fli>\n\u003Cli>une sauvegarde \u003Cstrong>chiffrée, dédupliquée et incrémentale\u003C\u002Fstrong> avec restic vers un stockage objet S3 ;\u003C\u002Fli>\n\u003Cli>une politique de rétention, une planification systemd et une alerte en cas d'échec.\u003C\u002Fli>\n\u003C\u002Ful>\n\n\u003Ch3>1. Le dump MySQL\u003C\u002Fh3>\n\u003Cp>Copier les fichiers de \u003Ccode>\u002Fvar\u002Flib\u002Fmysql\u003C\u002Fcode> à chaud donne une sauvegarde incohérente. Utilisez \u003Ccode>mysqldump\u003C\u002Fcode> avec \u003Ccode>--single-transaction\u003C\u002Fcode> : pour les tables InnoDB, le dump est pris dans une transaction, donc cohérent, sans verrouiller les écritures.\u003C\u002Fp>\n\u003Cp>D'abord, un utilisateur dédié aux sauvegardes, avec le strict nécessaire :\u003C\u002Fp>\n\u003Cpre>\u003Ccode>CREATE USER 'backup'@'localhost' IDENTIFIED BY 'mot-de-passe-long';\nGRANT SELECT, SHOW VIEW, TRIGGER, EVENT, LOCK TABLES, PROCESS ON *.* TO 'backup'@'localhost';\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Ses identifiants vont dans un fichier lisible par root seulement, pour ne jamais apparaître dans la liste des processus :\u003C\u002Fp>\n\u003Cpre>\u003Ccode># \u002Froot\u002F.my-backup.cnf  (chmod 600)\n[client]\nuser=backup\npassword=mot-de-passe-long\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cpre>\u003Ccode>mysqldump --defaults-extra-file=\u002Froot\u002F.my-backup.cnf \\\n  --single-transaction --quick --routines --triggers --events \\\n  --databases app | gzip &gt; \u002Fvar\u002Fbackups\u002Fmysql\u002Fapp.sql.gz\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Ccode>--quick\u003C\u002Fcode> lit les lignes une par une au lieu de charger chaque table en mémoire : indispensable pour les grosses tables. Au-delà de quelques dizaines de Go, passez à une sauvegarde physique (Percona XtraBackup ou MySQL Enterprise Backup).\u003C\u002Fp>\n\n\u003Ch3>2. Pourquoi restic\u003C\u002Fh3>\n\u003Cp>Une archive \u003Ccode>tar.gz\u003C\u002Fcode> quotidienne recopie tout, chaque jour. \u003Cstrong>restic\u003C\u002Fstrong> découpe les fichiers en blocs, ne stocke chaque bloc qu'une fois, et chiffre tout côté client (AES-256) avant l'envoi. Résultat : des sauvegardes quotidiennes qui ne coûtent que la taille des changements, un stockage distant qui ne voit jamais vos données en clair, et une restauration possible à n'importe quelle date conservée.\u003C\u002Fp>\n\u003Cpre>\u003Ccode>sudo apt install -y restic\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Les paramètres du dépôt vont dans un fichier d'environnement protégé, \u003Ccode>\u002Fetc\u002Frestic\u002Fenv\u003C\u002Fcode> (\u003Ccode>chmod 600\u003C\u002Fcode>). N'importe quel stockage compatible S3 convient (AWS, Scaleway, OVH, Backblaze B2, Cloudflare R2…) :\u003C\u002Fp>\n\u003Cpre>\u003Ccode>RESTIC_REPOSITORY=s3:https:\u002F\u002Fs3.fr-par.scw.cloud\u002Fmon-bucket-backups\u002Fserveur-web\nRESTIC_PASSWORD_FILE=\u002Fetc\u002Frestic\u002Fpassword\nAWS_ACCESS_KEY_ID=xxxxxxxx\nAWS_SECRET_ACCESS_KEY=xxxxxxxx\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cpre>\u003Ccode>sudo sh -c 'openssl rand -base64 48 &gt; \u002Fetc\u002Frestic\u002Fpassword &amp;&amp; chmod 600 \u002Fetc\u002Frestic\u002Fpassword'\nsudo sh -c 'set -a; . \u002Fetc\u002Frestic\u002Fenv; restic init'\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cstrong>Conservez le mot de passe restic hors du serveur\u003C\u002Fstrong>, dans un gestionnaire de mots de passe. Sans lui, le dépôt est irrécupérable, et c'est voulu. Si le serveur brûle avec sa seule copie du mot de passe, vos sauvegardes brûlent avec lui.\u003C\u002Fp>\n\n\u003Ch3>3. Le script de sauvegarde\u003C\u002Fh3>\n\u003Cp>\u003Ccode>\u002Fusr\u002Flocal\u002Fbin\u002Fbackup.sh\u003C\u002Fcode> :\u003C\u002Fp>\n\u003Cpre>\u003Ccode>#!\u002Fusr\u002Fbin\u002Fenv bash\nset -euo pipefail\n\nset -a; . \u002Fetc\u002Frestic\u002Fenv; set +a\n\nDUMP_DIR=\u002Fvar\u002Fbackups\u002Fmysql\nmkdir -p \"$DUMP_DIR\"\n\n# 1. Dump MySQL (fichier temporaire puis renommage : jamais de dump à moitié écrit)\nmysqldump --defaults-extra-file=\u002Froot\u002F.my-backup.cnf \\\n  --single-transaction --quick --routines --triggers --events \\\n  --databases app | gzip &gt; \"$DUMP_DIR\u002Fapp.sql.gz.tmp\"\nmv \"$DUMP_DIR\u002Fapp.sql.gz.tmp\" \"$DUMP_DIR\u002Fapp.sql.gz\"\n\n# 2. Sauvegarde des dumps, des fichiers uploadés et de la configuration\nrestic backup \\\n  \"$DUMP_DIR\" \\\n  \u002Fvar\u002Fwww\u002Fapp\u002Fshared \\\n  \u002Fetc\u002Fnginx \u002Fetc\u002Fphp \\\n  --tag daily \\\n  --exclude-caches\n\n# 3. Rétention : 7 jours, 4 semaines, 6 mois\nrestic forget --tag daily --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune\n\n# 4. Vérification d'un échantillon des données\nrestic check --read-data-subset=5%\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Ccode>set -euo pipefail\u003C\u002Fcode> est crucial : sans \u003Ccode>pipefail\u003C\u002Fcode>, un \u003Ccode>mysqldump\u003C\u002Fcode> en échec suivi d'un \u003Ccode>gzip\u003C\u002Fcode> réussi passe inaperçu, et vous sauvegardez un fichier vide pendant des mois.\u003C\u002Fp>\n\u003Cpre>\u003Ccode>sudo chmod 700 \u002Fusr\u002Flocal\u002Fbin\u002Fbackup.sh\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Ch3>4. Planification avec un timer systemd\u003C\u002Fh3>\n\u003Cp>Un timer systemd a deux avantages sur cron : les logs sont dans journald, et \u003Ccode>Persistent=true\u003C\u002Fcode> rattrape une exécution manquée si le serveur était éteint.\u003C\u002Fp>\n\u003Cpre>\u003Ccode># \u002Fetc\u002Fsystemd\u002Fsystem\u002Fbackup.service\n[Unit]\nDescription=Sauvegarde MySQL + fichiers vers restic\nAfter=network-online.target mysql.service\nWants=network-online.target\n\n[Service]\nType=oneshot\nExecStart=\u002Fusr\u002Flocal\u002Fbin\u002Fbackup.sh\nNice=10\nIOSchedulingClass=idle\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cpre>\u003Ccode># \u002Fetc\u002Fsystemd\u002Fsystem\u002Fbackup.timer\n[Unit]\nDescription=Sauvegarde quotidienne\n\n[Timer]\nOnCalendar=*-*-* 03:00:00\nRandomizedDelaySec=15min\nPersistent=true\n\n[Install]\nWantedBy=timers.target\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cpre>\u003Ccode>sudo systemctl daemon-reload\nsudo systemctl enable --now backup.timer\nsystemctl list-timers backup.timer      # prochaine exécution\nsudo systemctl start backup.service     # premier lancement manuel\njournalctl -u backup.service -e         # logs\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Ch3>5. Être prévenu quand ça échoue\u003C\u002Fh3>\n\u003Cp>Une sauvegarde qui échoue en silence est le pire scénario. La solution la plus simple est un service de type « dead man's switch » (Healthchecks.io, Uptime Kuma, Better Stack…) : le script envoie un ping à chaque succès, et le service vous alerte s'il ne reçoit rien dans le délai prévu. Ajoutez à la fin de \u003Ccode>backup.sh\u003C\u002Fcode> :\u003C\u002Fp>\n\u003Cpre>\u003Ccode>curl -fsS -m 10 --retry 3 https:\u002F\u002Fhc-ping.com\u002Fvotre-uuid &gt; \u002Fdev\u002Fnull\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Grâce à \u003Ccode>set -e\u003C\u002Fcode>, le ping n'est envoyé que si toutes les étapes précédentes ont réussi.\u003C\u002Fp>\n\n\u003Ch3>6. Tester la restauration (vraiment)\u003C\u002Fh3>\n\u003Cp>Planifiez un test de restauration régulier, par exemple tous les mois, sur une autre machine :\u003C\u002Fp>\n\u003Cpre>\u003Ccode>set -a; . \u002Fetc\u002Frestic\u002Fenv; set +a\n\nrestic snapshots --tag daily                       # liste des sauvegardes\nrestic restore latest --target \u002Ftmp\u002Frestore        # dernière version\nrestic restore latest --target \u002Ftmp\u002Frestore --include \u002Fvar\u002Fwww\u002Fapp\u002Fshared\u002Fuploads\n\n# Restaurer la base dans une base de test\nmysql -e 'CREATE DATABASE app_restore_test'\nzcat \u002Ftmp\u002Frestore\u002Fvar\u002Fbackups\u002Fmysql\u002Fapp.sql.gz \\\n  | sed 's\u002F`app`\u002F`app_restore_test`\u002Fg' | mysql\nmysql -e 'SELECT COUNT(*) FROM app_restore_test.user'\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Chronométrez l'opération : c'est votre \u003Cstrong>RTO\u003C\u002Fstrong> réel (le temps pour revenir en service). L'ancienneté de la dernière sauvegarde réussie est votre \u003Cstrong>RPO\u003C\u002Fstrong> (la quantité de données que vous acceptez de perdre). Si ces deux valeurs ne conviennent pas au métier, c'est le moment de le découvrir, pas pendant un incident.\u003C\u002Fp>\n\n\u003Ch3>En résumé\u003C\u002Fh3>\n\u003Cul>\n\u003Cli>Dump cohérent avec \u003Ccode>--single-transaction\u003C\u002Fcode>, identifiants hors de la ligne de commande\u003C\u002Fli>\n\u003Cli>restic : chiffré, dédupliqué, hors site, avec une rétention claire\u003C\u002Fli>\n\u003Cli>Planification systemd avec \u003Ccode>Persistent=true\u003C\u002Fcode>, et une alerte si le ping n'arrive pas\u003C\u002Fli>\n\u003Cli>Mot de passe du dépôt conservé ailleurs que sur le serveur\u003C\u002Fli>\n\u003Cli>Restauration testée régulièrement et chronométrée\u003C\u002Fli>\n\u003C\u002Ful>\n","fr",1790543096552]