[UPDATE 2026] Backup total d’un serveur dédié Docker avec rsnapshot — bases de données, volumes et restauration
Backup total d’un serveur dédié avec rsnapshot
Sauvegardes incrémentales avec rsnapshot : bases de données Docker
(MariaDB, MySQL, PostgreSQL, SQLite), volumes, fichiers applicatifs
et configuration MyVestaCP. Serveur de backup dédié ou NAS Synology.
Paramètres à personnaliser
Remplacez chaque {{VARIABLE}} par vos propres valeurs avant d'utiliser ce guide.
| Variable | Description | Exemple |
|---|---|---|
{{IP_NOUVEAU}} |
IP du serveur principal | 198.51.100.20 |
{{IP_VPS}} |
IP du VPS secondaire | 203.0.113.50 |
{{HOSTNAME_NOUVEAU}} |
Hostname serveur principal | ns7654321 |
{{HOSTNAME_VPS}} |
Hostname du VPS | vps-abc12345 |
{{PORT_SSH_NOUVEAU}} |
Port SSH serveur principal | 22222 |
{{PORT_SSH_VPS}} |
Port SSH du VPS | 22223 |
{{DB_PASSWORD}} |
Mot de passe MariaDB | S3cur3P@ss! |
{{DOMAINE_GAMING}} |
Domaine gaming (email) | exemple-gaming.fr |
{{DOMAINE_SITE}} |
Domaine du site web | monsite.fr |
{{INSTANCE_1}} |
Instance Odoo 17 principale | moninstance |
{{INSTANCE_2}} |
Instance Odoo 17 secondaire | autreinstance |
{{DB_ODOO8}} |
Base Odoo 8 | mabase-odoo |
{{DB_PRESTASHOP}} |
Base PrestaShop | prestashop_monsite |
{{USER_VESTA}} |
Utilisateur MyVestaCP | admin |
Sommaire
- Pourquoi rsnapshot ?
- Architecture et prérequis
- Préparer le serveur de données
- Scripts de dump des bases de données Docker
- Script de pré-backup Docker
- Préparer le serveur de backup
- Configurer rsnapshot
- Automatiser avec cron
- Tester et valider
- Restauration
- Variante Synology (Container Manager)
1) Pourquoi rsnapshot ?
rsnapshot est un outil basé sur rsync qui crée des sauvegardes
incrémentales par snapshots. Les fichiers inchangés ne sont pas dupliqués :
ils sont reliés par des hard links, ce qui économise
considérablement l’espace disque tout en gardant plusieurs versions complètes du serveur.
Chaque snapshot est un répertoire autonome : on peut y naviguer, copier des fichiers,
ou restaurer un serveur complet sans outil spécial.
Docker change tout. Sur un serveur classique, rsnapshot peut copier
directement /var/lib/mysql/ ou /var/lib/postgresql/.
Avec Docker, les bases de données tournent dans des conteneurs isolés :
il faut les dumper via docker exec avant de les sauvegarder.
Copier un volume Docker pendant que le conteneur écrit dedans peut produire un
backup corrompu.
2) Architecture et prérequis
2.1 Schéma global
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
Serveur principal ({{HOSTNAME_NOUVEAU}} / {{IP_NOUVEAU}}, port SSH {{PORT_SSH_NOUVEAU}}) └─ MyVestaCP (sites web, crons, templates Nginx) └─ /home/docker/pterodactyl/ (Panel + Wings + MariaDB + Redis) └─ /home/docker/teamspeak3/ (TeamSpeak 3 + SQLite) └─ /home/docker/akeneo/ (PIM 7 + MySQL 8 + Elasticsearch) └─ /home/docker/owncloud/ (ownCloud 10 + PostgreSQL 13) └─ /home/docker/odoo17/ (Odoo 17 x4 instances + PostgreSQL 13) └─ /home/docker/odoo8/ (Odoo 8 + PostgreSQL 9.6) └─ /home/docker/emulinker/ (EmuLinker Kaillera) └─ /home/docker/znc/ (ZNC IRC bouncer) └─ /home/{{USER_VESTA}}/web/ (LGSL monitoring, sites web) │ │ SSH + rsync (port {{PORT_SSH_NOUVEAU}}) │ VPS secondaire ({{HOSTNAME_VPS}} / {{IP_VPS}}, port SSH {{PORT_SSH_VPS}}) └─ Services divers └─ /home/backup_user/bin/backup_mysql_ext.sh (dumps MySQL) │ │ SSH + rsync (port {{PORT_SSH_VPS}}) │ NAS Synology (serveur de backup) └─ rsnapshot (hourly / daily / weekly / monthly) └─ /volume1/NetBackup/ |
2.2 Inventaire des services Docker
| Service | Base de données | Conteneur BDD | Données |
|---|---|---|---|
| Pterodactyl | MariaDB 10.11 | pterodactyl-db |
Panel + 6 serveurs CS 1.6 |
| TeamSpeak 3 | SQLite | teamspeak3-server |
235 Go (fichiers, icônes, avatars) |
| Akeneo PIM 7 | MySQL 8.0.30 + Elasticsearch | akeneo-mysql, akeneo-es |
Code PIM + indices ES |
| ownCloud 10 | PostgreSQL 13 | owncloud-db |
568 Go (fichiers utilisateurs) |
| Odoo 17 (x4) | PostgreSQL 13 | odoo17-db |
Filestore + modules custom |
| Odoo 8 | PostgreSQL 9.6 | odoo8-db |
Filestore + modules custom |
| EmuLinker | — | — | Config + JAR (< 100 Mo) |
| ZNC | Config files | znc-server |
2,4 Go (logs IRC) |
| Sites web MyVestaCP | MySQL natif | Service mysql système |
PrestaShop, WordPress, Matomo, etc. |
| LGSL (monitoring) | — | — | Fichiers PHP dans /home/{{USER_VESTA}}/web/ |
LGSL (Live Game Server List) est un panel de monitoring des serveurs de jeu.
Ses fichiers sont dans /home/{{USER_VESTA}}/web/ et sont sauvegardés
automatiquement par rsnapshot via la copie du / complet.
Pas de base de données à dumper — tout est en fichiers plats et cache.
2.3 Prérequis
- Serveur de données : Debian 12, Docker, docker compose, bzip2
- Serveur de backup : rsnapshot, rsync, SSH, disque externe ext4 (≥ 2 To recommandé)
- Connectivité : SSH par clé entre backup → données
3) Préparer le serveur de données
3.1 Installer les paquets
|
1 |
apt update && apt install rsync bzip2 -y |
3.2 Créer l’utilisateur de backup
|
1 2 3 4 5 6 7 8 9 10 11 |
# Créer l'utilisateur avec un UID fixe adduser backup_user --uid 400 # Créer les répertoires mkdir -p /home/backup_user/.ssh chmod 700 /home/backup_user/.ssh mkdir -p /home/backup_user/bin mkdir -p /home/backup_user/files/{mysql,mysql_native,psql,sqlite,elasticsearch} # Permissions chown -R backup_user:backup_user /home/backup_user |
3.3 Script wrapper rsync (exécution root)
rsnapshot doit pouvoir lire tous les fichiers du serveur.
Le wrapper permet à backup_user d’exécuter rsync en root
via sudo, sans ouvrir un accès root SSH direct.
|
1 |
nano /home/backup_user/bin/rsync-wrapper.sh |
Coller :
|
1 2 3 4 5 6 |
#!/bin/bash LOG="/home/backup_user/backup.log" echo "rsync wrapper executed on $(date)" >> $LOG echo "options: $@" >> $LOG echo "---------------------------------------------" >> $LOG /usr/bin/sudo /usr/bin/rsync "$@" |
3.4 Droits sudo
|
1 |
visudo |
Ajouter à la fin :
|
1 2 3 4 5 6 7 8 9 10 |
# Backup - rsync en root backup_user ALL=NOPASSWD: /usr/bin/rsync # Backup - docker exec pour les dumps de bases de données backup_user ALL=NOPASSWD: /usr/bin/docker exec * backup_user ALL=NOPASSWD: /usr/bin/docker compose * backup_user ALL=NOPASSWD: /usr/bin/docker cp * # Backup - dump MySQL natif (MyVestaCP) backup_user ALL=NOPASSWD: /home/backup_user/bin/backup_mysql_native.sh |
Pourquoi sudo docker ? Les scripts de dump utilisent
docker exec pour accéder aux bases de données dans les conteneurs.
Sans cette ligne, backup_user ne pourrait pas dumper les bases.
3.5 Clé SSH
La clé SSH est générée sur le serveur de backup et copiée
vers le serveur de données. Voir section 6.
3.6 Rendre les scripts exécutables
|
1 2 |
chmod +x /home/backup_user/bin/* chown -R backup_user:backup_user /home/backup_user |
4) Scripts de dump des bases de données Docker
Chaque type de base de données a son propre script de dump.
Les dumps sont stockés dans /home/backup_user/files/
et seront récupérés par rsnapshot via rsync.
Différence avec un serveur classique : on n’utilise plus
mysqldump ou pg_dump directement.
Tout passe par sudo docker exec car les bases
tournent dans des conteneurs isolés.
4.1 Dump MariaDB / MySQL (Pterodactyl, Akeneo)
|
1 |
nano /home/backup_user/bin/backup_mysql_docker.sh |
Coller :
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
#!/bin/bash # ============================================================ # Dump MySQL / MariaDB depuis des conteneurs Docker # Utilisé par rsnapshot (appelé via SSH depuis le serveur de backup) # ============================================================ DUMP_DIR="/home/backup_user/files/mysql" mkdir -p "$DUMP_DIR" DATE=$(date +"%Y%m%d_%H%M%S") # Supprimer les dumps de plus de 5 jours find "$DUMP_DIR" -type f -name '*.sql.gz' -mtime +5 -delete # ---------------------------------------------------------- # 1. Pterodactyl - MariaDB 10.11 # Conteneur : pterodactyl-database-1 # Base : panel # Chemin compose : /home/docker/pterodactyl/ # ---------------------------------------------------------- echo "[$(date)] Dumping Pterodactyl (MariaDB)..." sudo docker exec pterodactyl-database-1 mariadb-dump \ --single-transaction --quick --lock-tables=false \ -u root -p{{DB_PASSWORD}} --all-databases \ | gzip > "$DUMP_DIR/pterodactyl-all.$DATE.sql.gz" # ---------------------------------------------------------- # 2. Akeneo PIM - MySQL 8.0.30 # Conteneur : akeneo-mysql # Base : akeneo_pim # Chemin compose : /home/docker/akeneo/ # ---------------------------------------------------------- echo "[$(date)] Dumping Akeneo PIM (MySQL)..." sudo docker exec akeneo-mysql mysqldump \ --single-transaction --quick --lock-tables=false \ -u root -pAkeneoR00tPwd! akeneo_pim \ | gzip > "$DUMP_DIR/akeneo-pim.$DATE.sql.gz" echo "[$(date)] MySQL/MariaDB dumps completed." ls -lh "$DUMP_DIR"/*.$DATE.sql.gz # ---------------------------------------------------------- # 3. Akeneo PIM - Elasticsearch (indices Akeneo) # Conteneur : akeneo-es # Les indices ES peuvent être recréés via `bin/console # akeneo:elasticsearch:reset-indexes` mais c'est long. # Un snapshot accélère la restauration. # ---------------------------------------------------------- echo "[$(date)] Dumping Akeneo Elasticsearch indices..." ES_DUMP_DIR="/home/backup_user/files/elasticsearch" mkdir -p "$ES_DUMP_DIR" # Créer un snapshot repository dans ES (idempotent) sudo docker exec akeneo-es curl -s -X PUT \ "http://localhost:9200/_snapshot/backup" \ -H "Content-Type: application/json" \ -d '{"type":"fs","settings":{"location":"/usr/share/elasticsearch/data/backup"}}' \ 2>/dev/null || true # Lancer le snapshot sudo docker exec akeneo-es curl -s -X PUT \ "http://localhost:9200/_snapshot/backup/snap_$DATE?wait_for_completion=true" \ 2>/dev/null # Copier le répertoire de snapshot hors du conteneur sudo docker cp akeneo-es:/usr/share/elasticsearch/data/backup \ "$ES_DUMP_DIR/akeneo-es-snap.$DATE" 2>/dev/null if [ -d "$ES_DUMP_DIR/akeneo-es-snap.$DATE" ]; then tar -czf "$ES_DUMP_DIR/akeneo-es.$DATE.tar.gz" \ -C "$ES_DUMP_DIR" "akeneo-es-snap.$DATE" rm -rf "$ES_DUMP_DIR/akeneo-es-snap.$DATE" echo "[$(date)] Elasticsearch snapshot saved." else echo "[$(date)] WARNING: Elasticsearch snapshot failed (ES may not be running or container name differs)." echo " Les indices peuvent être recréés avec: bin/console akeneo:elasticsearch:reset-indexes" fi # Nettoyer les vieux snapshots ES (> 7 jours) find "$ES_DUMP_DIR" -type f -name '*.tar.gz' -mtime +7 -delete |
--single-transaction permet de dumper sans verrouiller
les tables (InnoDB). Les conteneurs restent accessibles pendant le dump.
Elasticsearch : le snapshot ES est optionnel. Si le conteneur ES
n’est pas accessible ou si le nom diffère, le script affiche un warning
et continue. Les indices Akeneo peuvent toujours être recréés manuellement
avec bin/console akeneo:elasticsearch:reset-indexes (plus lent mais fonctionnel).
4.2 Dump PostgreSQL (ownCloud, Odoo 17, Odoo 8)
|
1 |
nano /home/backup_user/bin/backup_postgresql_docker.sh |
Coller :
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
#!/bin/bash # ============================================================ # Dump PostgreSQL depuis des conteneurs Docker # Utilisé par rsnapshot (appelé via SSH depuis le serveur de backup) # ============================================================ DUMP_DIR="/home/backup_user/files/psql" mkdir -p "$DUMP_DIR" DATE=$(date +"%Y%m%d_%H%M%S") # Supprimer les dumps de plus de 7 jours find "$DUMP_DIR" -type f -name '*.sql.gz' -mtime +7 -delete # ---------------------------------------------------------- # 1. ownCloud - PostgreSQL 13 # Conteneur : owncloud-db # User : admin_owncloud # Chemin compose : /home/docker/owncloud/ # ---------------------------------------------------------- echo "[$(date)] Dumping ownCloud (PostgreSQL)..." sudo docker exec owncloud-db pg_dumpall \ -U admin_owncloud --clean \ | gzip > "$DUMP_DIR/owncloud-all.$DATE.sql.gz" # ---------------------------------------------------------- # 2. Odoo 17 - PostgreSQL 13 (4 instances, 1 conteneur BDD) # Conteneur : odoo17-db # User : odoo17 # Bases : {{INSTANCE_1}}, {{INSTANCE_2}}, {{INSTANCE_1}}-test # Chemin compose : /home/docker/odoo17/ # ---------------------------------------------------------- echo "[$(date)] Dumping Odoo 17 (PostgreSQL)..." sudo docker exec odoo17-db pg_dumpall \ -U odoo17 --clean \ | gzip > "$DUMP_DIR/odoo17-all.$DATE.sql.gz" # ---------------------------------------------------------- # 3. Odoo 8 - PostgreSQL 9.6 # Conteneur : odoo8-db # User : odoo8 # Bases : {{DB_ODOO8}}, {{INSTANCE_1}}-odoo-fake, etc. # Chemin compose : /home/docker/odoo8/ # ---------------------------------------------------------- echo "[$(date)] Dumping Odoo 8 (PostgreSQL)..." sudo docker exec odoo8-db pg_dumpall \ -U odoo8 --clean \ | gzip > "$DUMP_DIR/odoo8-all.$DATE.sql.gz" echo "[$(date)] PostgreSQL dumps completed." ls -lh "$DUMP_DIR"/*.$DATE.sql.gz |
4.3 Backup SQLite (TeamSpeak 3)
|
1 |
nano /home/backup_user/bin/backup_sqlite_docker.sh |
Coller :
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
#!/bin/bash # ============================================================ # Backup SQLite TeamSpeak 3 # Copie directe de la DB + fichiers WAL pour cohérence # (sqlite3 n'est pas disponible dans le conteneur) # ============================================================ DUMP_DIR="/home/backup_user/files/sqlite" mkdir -p "$DUMP_DIR" DATE=$(date +"%Y%m%d_%H%M%S") # Supprimer les backups de plus de 7 jours find "$DUMP_DIR" -type f -name '*.tar.gz' -mtime +7 -delete echo "[$(date)] Dumping TeamSpeak 3 SQLite (copie directe + WAL)..." # Copier la DB + WAL + SHM hors du conteneur sudo docker cp teamspeak3-server:/var/ts3server/ts3server.sqlitedb \ "$DUMP_DIR/ts3server.sqlitedb" sudo docker cp teamspeak3-server:/var/ts3server/ts3server.sqlitedb-wal \ "$DUMP_DIR/ts3server.sqlitedb-wal" 2>/dev/null || true sudo docker cp teamspeak3-server:/var/ts3server/ts3server.sqlitedb-shm \ "$DUMP_DIR/ts3server.sqlitedb-shm" 2>/dev/null || true sudo docker cp teamspeak3-server:/var/ts3server/licensekey.dat \ "$DUMP_DIR/licensekey.dat" 2>/dev/null || true # Archiver tout (DB + WAL + SHM + licence) tar -czf "$DUMP_DIR/teamspeak3-data.$DATE.tar.gz" \ -C "$DUMP_DIR" ts3server.sqlitedb ts3server.sqlitedb-wal \ ts3server.sqlitedb-shm licensekey.dat 2>/dev/null rm -f "$DUMP_DIR/ts3server.sqlitedb" "$DUMP_DIR/ts3server.sqlitedb-wal" \ "$DUMP_DIR/ts3server.sqlitedb-shm" "$DUMP_DIR/licensekey.dat" echo "[$(date)] TeamSpeak 3 SQLite backup completed (no downtime)." ls -lh "$DUMP_DIR"/teamspeak3-data.$DATE.tar.gz |
Pas d’arrêt nécessaire : on copie la DB SQLite + ses fichiers
WAL (Write-Ahead Log) et SHM directement depuis le conteneur.
SQLite rejouera le WAL à l’ouverture pour garantir la cohérence des données.
Les utilisateurs ne sont pas déconnectés.
Pourquoi pas sqlite3 .backup ?
Le conteneur TeamSpeak officiel n’inclut pas l’utilitaire sqlite3.
La copie directe DB + WAL + SHM est une alternative fiable qui produit
un backup cohérent sans nécessiter d’outils supplémentaires.
Pourquoi ne pas backuper les 235 Go de fichiers TS ici ?
Les fichiers TeamSpeak (icônes, avatars, fichiers de channels) sont
en bind mount dans /home/docker/teamspeak3/data/.
Rsnapshot les copiera directement via rsync (section 7), pas besoin
de les archiver dans ce script.
4.4 Dump MySQL natif (MyVestaCP — PrestaShop, WordPress, Matomo…)
Ne pas confondre avec les bases Docker. MyVestaCP gère ses propres bases MySQL
(créées via le panel DB) qui tournent directement sur le système, pas dans un conteneur.
Si tu as des sites PrestaShop, WordPress, Matomo, etc. hébergés via VestaCP,
leurs bases sont natives et nécessitent un dump séparé.
|
1 |
nano /home/backup_user/bin/backup_mysql_native.sh |
Coller :
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
#!/bin/bash # ============================================================ # Dump MySQL natif (bases MyVestaCP : PrestaShop, WordPress, Matomo...) # Pas Docker ! Ce sont les bases gérées par le panel VestaCP # ============================================================ DUMP_DIR="/home/backup_user/files/mysql_native" mkdir -p "$DUMP_DIR" DATE=$(date +"%Y%m%d_%H%M%S") # Supprimer les dumps de plus de 5 jours find "$DUMP_DIR" -type f -name '*.sql.gz' -mtime +5 -delete # Fichier de credentials MySQL (pas de mot de passe en ligne de commande) # Créer /etc/mysql/backup-credentials.cnf avec : # [client] # user=root # password=<mot_de_passe_mysql_root> CRED="/etc/mysql/backup-credentials.cnf" echo "[$(date)] Dumping native MySQL databases (MyVestaCP)..." # Lister toutes les bases sauf les bases système DATABASES=$(mysql --defaults-extra-file="$CRED" -e "SHOW DATABASES;" \ | grep -Ev "(Database|information_schema|performance_schema|mysql|sys)") for DB in $DATABASES; do echo "[$(date)] Dumping $DB..." mysqldump --defaults-extra-file="$CRED" \ --single-transaction --quick --lock-tables=false \ --databases "$DB" \ | gzip > "$DUMP_DIR/${DB}.$DATE.sql.gz" done echo "[$(date)] Native MySQL dumps completed." ls -lh "$DUMP_DIR"/*.$DATE.sql.gz 2>/dev/null |
Créer le fichier de credentials MySQL :
|
1 |
nano /etc/mysql/backup-credentials.cnf |
Coller :
|
1 2 3 |
[client] user=root password=<mot_de_passe_mysql_root> |
|
1 |
chmod 600 /etc/mysql/backup-credentials.cnf |
Où trouver le mot de passe root MySQL ?
Sur un serveur MyVestaCP / HestiaCP, le mot de passe root MySQL est stocké dans :
/usr/local/vesta/conf/mysql.conf (MyVestaCP) ou
/usr/local/hestia/conf/mysql.conf (HestiaCP).
Afficher avec : cat /usr/local/vesta/conf/mysql.conf
Pourquoi un fichier de credentials ? Passer le mot de passe avec -p
sur la ligne de commande affiche un warning MySQL et le mot de passe apparaît dans
ps aux. Le fichier --defaults-extra-file est la méthode
recommandée par MySQL.
4.5 Rendre les scripts exécutables
|
1 2 |
chmod +x /home/backup_user/bin/backup_* chown backup_user:backup_user /home/backup_user/bin/backup_* |
5) Script de pré-backup Docker
Ce script orchestre tous les dumps dans l’ordre.
Il est appelé par rsnapshot (via SSH) avant la copie des fichiers.
|
1 |
nano /home/backup_user/bin/docker-pre-backup.sh |
Coller :
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
#!/bin/bash # ============================================================ # Script de pré-backup Docker # Appelé par rsnapshot via backup_script avant la copie rsync # Dumpe toutes les bases de données des conteneurs Docker # ============================================================ LOG="/home/backup_user/backup.log" echo "===== docker-pre-backup started: $(date) =====" >> "$LOG" # 1. Dumps MySQL / MariaDB Docker (Pterodactyl, Akeneo) /home/backup_user/bin/backup_mysql_docker.sh >> "$LOG" 2>&1 MYSQL_DOCKER=$? # 2. Dumps MySQL natif MyVestaCP (PrestaShop, WordPress, Matomo...) sudo /home/backup_user/bin/backup_mysql_native.sh >> "$LOG" 2>&1 MYSQL_NATIVE=$? # 3. Dumps PostgreSQL Docker (ownCloud, Odoo 17, Odoo 8) /home/backup_user/bin/backup_postgresql_docker.sh >> "$LOG" 2>&1 PG_STATUS=$? # 4. Backup SQLite (TeamSpeak 3 - copie directe + WAL, pas d'arrêt) /home/backup_user/bin/backup_sqlite_docker.sh >> "$LOG" 2>&1 SQLITE_STATUS=$? # Résumé echo "----- Pre-backup summary -----" >> "$LOG" echo "MySQL Docker: exit $MYSQL_DOCKER" >> "$LOG" echo "MySQL native: exit $MYSQL_NATIVE" >> "$LOG" echo "PostgreSQL: exit $PG_STATUS" >> "$LOG" echo "SQLite: exit $SQLITE_STATUS" >> "$LOG" echo "===== docker-pre-backup completed: $(date) =====" >> "$LOG" # Permissions pour que rsnapshot puisse lire les dumps chown -R backup_user:backup_user /home/backup_user/files/ chmod -R 640 /home/backup_user/files/ exit 0 |
|
1 |
chmod +x /home/backup_user/bin/docker-pre-backup.sh |
Ordre d’exécution : MySQL, PostgreSQL et SQLite peuvent tourner en parallèle
(conteneurs indépendants). Aucun arrêt de service n’est nécessaire grâce
à --single-transaction (MySQL/PG) et la copie directe DB+WAL (TS3).
5.1 Tester chaque script individuellement
Indispensable : tester chaque script un par un sur le serveur de données
avant de passer à la configuration du serveur de backup. Un dump vide (20 octets)
ou une erreur silencieuse ici = un backup inutile découvert trop tard.
Test 1 — MySQL / MariaDB Docker
|
1 |
/home/backup_user/bin/backup_mysql_docker.sh |
Vérifier :
|
1 2 3 4 5 |
# Les fichiers doivent faire plus que 20 octets (20 = dump vide gzippé) ls -lh /home/backup_user/files/mysql/ # Vérifier le contenu d'un dump zcat /home/backup_user/files/mysql/pterodactyl-all.*.sql.gz | head -20 zcat /home/backup_user/files/mysql/akeneo-pim.*.sql.gz | head -20 |
Erreurs fréquentes :
Error: No such container → vérifier le nom exact avec sudo docker ps --format "{{.Names}}".
Access denied → vérifier le mot de passe root dans le docker-compose.yml
(MYSQL_ROOT_PASSWORD).
Test 2 — PostgreSQL Docker
|
1 |
/home/backup_user/bin/backup_postgresql_docker.sh |
Vérifier :
|
1 2 3 4 |
ls -lh /home/backup_user/files/psql/ zcat /home/backup_user/files/psql/owncloud-all.*.sql.gz | head -20 zcat /home/backup_user/files/psql/odoo17-all.*.sql.gz | head -20 zcat /home/backup_user/files/psql/odoo8-all.*.sql.gz | head -20 |
Erreur fréquente :
FATAL: role "xxx" does not exist → vérifier le user PostgreSQL dans le
docker-compose.yml (POSTGRES_USER).
Pour trouver le bon user : sudo docker exec owncloud-db psql -U postgres -c "\du"
Test 3 — SQLite TeamSpeak
|
1 |
/home/backup_user/bin/backup_sqlite_docker.sh |
Vérifier :
|
1 2 3 |
ls -lh /home/backup_user/files/sqlite/ # L'archive doit contenir ts3server.sqlitedb (plusieurs Mo) tar -tzf /home/backup_user/files/sqlite/teamspeak3-data.*.tar.gz |
Vérification : l’archive doit contenir ts3server.sqlitedb (~4 Mo),
ts3server.sqlitedb-wal, ts3server.sqlitedb-shm et licensekey.dat.
Si la DB ne fait que quelques octets, vérifiez que le conteneur teamspeak3-server tourne.
Test 4 — MySQL natif (MyVestaCP)
|
1 |
sudo /home/backup_user/bin/backup_mysql_native.sh |
Vérifier :
|
1 2 3 |
ls -lh /home/backup_user/files/mysql_native/ # Chaque base doit avoir son dump zcat /home/backup_user/files/mysql_native/*.sql.gz | head -5 |
Erreur fréquente :
ERROR 1045 (28000): Access denied → le fichier
/etc/mysql/backup-credentials.cnf n’existe pas ou contient un mauvais mot de passe.
Trouver le mot de passe root : cat /usr/local/vesta/conf/mysql.conf
Test 5 — Script complet (pré-backup)
Une fois que les 4 scripts passent individuellement :
|
1 2 |
/home/backup_user/bin/docker-pre-backup.sh cat /home/backup_user/backup.log |
Le log doit afficher exit 0 pour chaque script :
|
1 2 3 4 5 |
----- Pre-backup summary ----- MySQL Docker: exit 0 MySQL native: exit 0 PostgreSQL: exit 0 SQLite: exit 0 |
Ne pas passer à la section suivante tant que tous les scripts ne retournent pas
exit 0 et que les dumps ne font pas une taille cohérente.
6) Préparer le serveur de backup
6.1 Installer les paquets
|
1 |
apt update && apt install rsnapshot rsync -y |
6.2 Préparer le disque de backup
|
1 2 3 4 5 6 7 8 9 10 11 |
# Formater en ext4 (une seule fois) # ATTENTION : remplacer /dev/sdX par le bon disque mkfs.ext4 /dev/sdX1 # Point de montage mkdir -p /media/hdd_backup mount /dev/sdX1 /media/hdd_backup # Répertoire de snapshots mkdir -p /media/hdd_backup/backup chmod 755 /media/hdd_backup/backup |
Espace requis : avec ownCloud (568 Go) + TeamSpeak (235 Go) + le reste,
le serveur fait environ 835 Go de données. Avec la rétention
(7 daily + 4 weekly + 3 monthly) et les hard links, prévoir un
disque de 2 To minimum.
6.3 Créer l’utilisateur de backup
|
1 |
adduser backup_user --uid 400 |
Ajouter dans /etc/sudoers via visudo :
|
1 2 |
backup_user ALL=NOPASSWD: /usr/bin/rsync backup_user ALL=NOPASSWD: /usr/bin/rsnapshot |
6.4 Générer et copier la clé SSH
Option A : depuis le serveur de backup (Linux)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# Se connecter en tant que backup_user su - backup_user # Générer la clé (une seule clé pour tous les serveurs) ssh-keygen -t ed25519 -N "" -f ~/.ssh/id_ed25519_rsnapshot # Copier la clé vers chaque serveur (adapter le port à chaque serveur) ssh-copy-id -i ~/.ssh/id_ed25519_rsnapshot.pub -p {{PORT_SSH_NOUVEAU}} backup_user@{{IP_NOUVEAU}} ssh-copy-id -i ~/.ssh/id_ed25519_rsnapshot.pub -p {{PORT_SSH_VPS}} backup_user@{{IP_VPS}} # Tester chaque connexion ssh -i ~/.ssh/id_ed25519_rsnapshot -p {{PORT_SSH_NOUVEAU}} backup_user@{{IP_NOUVEAU}} "echo OK" ssh -i ~/.ssh/id_ed25519_rsnapshot -p {{PORT_SSH_VPS}} backup_user@{{IP_VPS}} "echo OK" exit |
Option B : depuis Windows (PowerShell)
Si tu gères ton serveur depuis Windows (pas d’accès SSH direct),
tu peux générer la clé en PowerShell et la déployer via SFTP.
|
1 2 |
# Ouvrir PowerShell et générer la clé (ne pas mettre de passphrase) ssh-keygen -t ed25519 -f $env:USERPROFILE\.ssh\id_ed25519_rsnapshot |
Cela crée deux fichiers dans C:\Users\ :
id_ed25519_rsnapshot— clé privéeid_ed25519_rsnapshot.pub— clé publique
Ensuite :
- Ouvrir
id_ed25519_rsnapshot.pubavec un éditeur de texte et copier son contenu - Sur chaque serveur de données, ajouter ce contenu à la fin de
/home/backup_user/.ssh/authorized_keys(via SFTP ou le panel web) —
la même clé publique peut être autorisée sur plusieurs serveurs - Sur le NAS Synology, déposer la clé privée
id_ed25519_rsnapshotdans/volume1/NetBackup/.ssh/ - Sécuriser les permissions sur le NAS :
12chmod 700 /volume1/NetBackup/.sshchmod 600 /volume1/NetBackup/.ssh/id_ed25519_rsnapshot
Tester chaque connexion depuis le NAS :
|
1 2 3 4 5 |
# Serveur OVH dédié (port {{PORT_SSH_NOUVEAU}}) ssh -i /volume1/NetBackup/.ssh/id_ed25519_rsnapshot -p {{PORT_SSH_NOUVEAU}} backup_user@{{IP_NOUVEAU}} "echo OK" # VPS (port {{PORT_SSH_VPS}}) ssh -i /volume1/NetBackup/.ssh/id_ed25519_rsnapshot -p {{PORT_SSH_VPS}} backup_user@{{IP_VPS}} "echo OK" |
6.5 Tester rsync à distance
|
1 2 3 4 5 6 7 8 9 10 11 |
# Test sur le serveur OVH dédié (port {{PORT_SSH_NOUVEAU}}) sudo -u backup_user rsync -azv --dry-run \ -e "ssh -i /volume1/NetBackup/.ssh/id_ed25519_rsnapshot -p {{PORT_SSH_NOUVEAU}}" \ backup_user@{{IP_NOUVEAU}}:/home/docker/ \ /tmp/test_rsync/ # Test sur le VPS (port {{PORT_SSH_VPS}}) sudo -u backup_user rsync -azv --dry-run \ -e "ssh -i /volume1/NetBackup/.ssh/id_ed25519_rsnapshot -p {{PORT_SSH_VPS}}" \ backup_user@{{IP_VPS}}:/ \ /tmp/test_rsync_vps/ |
6.6 Script wrapper rsnapshot
|
1 |
nano /home/backup_user/bin/rsnapshot-wrapper.sh |
Coller :
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
#!/bin/bash STARTTIME=$(date +%s) LOG="/home/backup_user/backup.log" MAXLINES=7000 MINLINES=2000 LOGSIZE=$(wc -l < "$LOG" 2>/dev/null || echo 0) echo -e "\n\n+++++ rsnapshot started on $(date) +++++" >> "$LOG" echo "options: $@" >> "$LOG" /usr/bin/sudo /usr/bin/rsnapshot -v "$@" EXITSTATUS=$? ENDTIME=$(date +%s) INTERVAL=$((ENDTIME - STARTTIME)) RUNTIME="$(($INTERVAL / 60)) min. $(($INTERVAL % 60)) sec." echo "rsnapshot exited with status $EXITSTATUS" >> "$LOG" echo "+++++ rsnapshot ran $RUNTIME and completed on $(date) +++++" >> "$LOG" # Rotation du log if [ "$LOGSIZE" -ge "$MAXLINES" ]; then LINECUT=$((LOGSIZE - MINLINES)) sed -i "1,${LINECUT}d" "$LOG" sed -i "1i //////////////// File truncated on $(date) ////////////////" "$LOG" fi |
|
1 |
chmod +x /home/backup_user/bin/rsnapshot-wrapper.sh |
7) Configurer rsnapshot
Tabulations obligatoires : dans rsnapshot.conf,
les séparateurs entre les champs sont des tabulations (pas des espaces).
Une erreur fréquente est d’utiliser des espaces — rsnapshot refusera de démarrer.
7.1 Fichier d’exclusions
|
1 |
nano /home/backup_user/rsync-exclude |
Coller :
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
# =========================================== # Exclusions système # =========================================== - /proc - /sys - /dev - /run - /tmp - /mnt - /media - /cdrom - /lost+found - /var/lib/pacman/sync/* - /swapfile # =========================================== # Exclusions Docker engine # (images, layers, cache — se retelecharge) # =========================================== - /var/lib/docker/overlay2/ - /var/lib/docker/image/ - /var/lib/docker/buildkit/ - /var/lib/docker/containers/*/ - /var/lib/docker/tmp/ - /var/lib/docker/network/ - /var/lib/docker/plugins/ # =========================================== # Exclusions Docker applicatives # (logs, caches, fichiers temporaires) # =========================================== # Pterodactyl - /home/docker/pterodactyl/tmp/ - /home/docker/pterodactyl/logs/*.log # Akeneo PIM - /home/docker/akeneo/pim/var/cache/ - /home/docker/akeneo/pim/var/logs/ # ownCloud - /home/docker/owncloud/data/files/*/cache/ - /home/docker/owncloud/data/files/*/thumbnails/ # Générique - **/node_modules/ - **/__pycache__/ - **/*.pyc - **/.git/objects/ |
Pourquoi exclure /var/lib/docker/overlay2/ ?
Ce répertoire contient les couches d’images Docker
(plusieurs dizaines de Go). Elles sont téléchargeables depuis les registres
(Docker Hub, ghcr.io). Les données applicatives
sont dans les volumes (/var/lib/docker/volumes/)
et les bind mounts (/home/docker/*/),
qui eux sont bien sauvegardés.
7.2 Gestion de plusieurs serveurs avec des ports SSH différents
Dès que l’on sauvegarde plusieurs serveurs ayant des ports SSH différents,
le paramètre global ssh_args de rsnapshot ne peut pas convenir à tous les serveurs
à la fois. Deux approches propres existent :
Approche recommandée : fichier ~/.ssh/config avec des alias
On crée un fichier ~/.ssh/config (sur le serveur de backup) qui définit
un alias par serveur. rsnapshot utilise ensuite ces alias — plus besoin
d’indiquer l’IP ni le port dans rsnapshot.conf.
|
1 |
nano /home/backup_user/.ssh/config |
Coller :
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# Alias pour le serveur OVH dédié Host backup-ovh HostName {{IP_NOUVEAU}} Port {{PORT_SSH_NOUVEAU}} User backup_user IdentityFile /home/backup_user/.ssh/id_ed25519_rsnapshot StrictHostKeyChecking yes # Alias pour le VPS Host backup-vps HostName {{IP_VPS}} Port {{PORT_SSH_VPS}} User backup_user IdentityFile /home/backup_user/.ssh/id_ed25519_rsnapshot StrictHostKeyChecking yes |
|
1 |
chmod 600 /home/backup_user/.ssh/config |
Tester les alias :
|
1 2 |
ssh backup-ovh "echo OK" ssh backup-vps "echo OK" |
Approche alternative : ssh_args par ligne backup
Si l’on ne peut pas (ou ne veut pas) créer un fichier SSH config
(cas Synology avec Docker), on peut surcharger ssh_args
directement sur chaque ligne backup :
|
1 2 |
backup backup_user@{{IP_NOUVEAU}}:/ {{HOSTNAME_NOUVEAU}}/ ssh_args=-i /home/backup_user/.ssh/id_ed25519_rsnapshot -p {{PORT_SSH_NOUVEAU}} backup backup_user@{{IP_VPS}}:/ {{HOSTNAME_VPS}}/ ssh_args=-i /home/backup_user/.ssh/id_ed25519_rsnapshot -p {{PORT_SSH_VPS}} |
Important : les arguments per-ligne ssh_args= sur une ligne
backup remplacent complètement le ssh_args global.
Il faut donc répéter l’argument -i (clé) ET -p (port)
sur chaque ligne. Le ssh_args global ne sert alors qu’à définir
un défaut pour les connexions qui n’ont pas de surcharge.
7.3 Configuration principale (serveur de backup Linux)
|
1 |
nano /etc/rsnapshot.conf |
Rappel : chaque «TAB» ci-dessous représente
une vraie tabulation. Ne pas remplacer par des espaces.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
config_version 1.2 # =========================================== # Répertoire de stockage des snapshots # =========================================== snapshot_root /media/hdd_backup/backup/ # =========================================== # Rétention # =========================================== retain daily 7 retain weekly 4 retain monthly 3 # =========================================== # Commandes système # =========================================== cmd_cp /bin/cp cmd_rm /bin/rm cmd_rsync /usr/bin/rsync cmd_ssh /usr/bin/ssh cmd_logger /usr/bin/logger cmd_du /usr/bin/du # =========================================== # Options rsync # =========================================== rsync_short_args -aev rsync_long_args --rsync-path=/home/backup_user/bin/rsync-wrapper.sh --delete --numeric-ids --relative --delete-excluded --stats # =========================================== # SSH — clé commune, les ports sont dans ~/.ssh/config # =========================================== ssh_args -i /home/backup_user/.ssh/id_ed25519_rsnapshot # =========================================== # Lockfile et logs # =========================================== lockfile /var/run/rsnapshot.pid logfile /home/backup_user/rsnapshot.log verbose 3 link_dest 1 # =========================================== # Fichier d'exclusions # =========================================== exclude_file /home/backup_user/rsync-exclude # =========================================== # SERVEUR OVH DÉDIÉ ({{HOSTNAME_NOUVEAU}} / {{IP_NOUVEAU}} / port {{PORT_SSH_NOUVEAU}}) # L'alias "backup-ovh" est défini dans ~/.ssh/config # =========================================== # Dumps des bases de données Docker (avant la copie rsync) backup_script /usr/bin/ssh backup-ovh "/home/backup_user/bin/docker-pre-backup.sh" unused1/ # Système complet (/) du serveur OVH backup backup_user@backup-ovh:/ {{HOSTNAME_NOUVEAU}}/ exclude_file=/home/backup_user/rsync-exclude # =========================================== # VPS ({{HOSTNAME_VPS}} / {{IP_VPS}} / port {{PORT_SSH_VPS}}) # L'alias "backup-vps" est défini dans ~/.ssh/config # =========================================== # Dumps MySQL du VPS (avant la copie rsync) backup_script /usr/bin/ssh backup-vps "/home/backup_user/bin/backup_mysql_ext.sh" unused_vps/ # Système complet (/) du VPS backup backup_user@backup-vps:/ {{HOSTNAME_VPS}}/ exclude_file=/home/backup_user/rsync-exclude |
Tout en une seule ligne backup :
la ligne backup ... :/ copie tout le serveur
sauf les exclusions. Cela inclut automatiquement :
/home/docker/*/— tous les docker-compose.yml, configs, bind mounts/home/backup_user/files/— les dumps SQL générés par le pré-backup/home/{{USER_VESTA}}/web/— les sites MyVestaCP (PrestaShop, WordPress, Matomo)/usr/local/vesta/— la config VestaCP (templates Nginx, crons, users)/var/lib/docker/volumes/— les volumes Docker nommés (BDD, Redis, etc.)/etc/— configuration système, SSH, firewall
7.4 Vérifier la configuration
|
1 2 3 4 5 |
# Vérifier la syntaxe rsnapshot configtest # Simulation (dry-run) — ne copie rien rsnapshot -t daily |
8) Automatiser avec cron
|
1 |
sudo crontab -e -u backup_user |
Ajouter :
|
1 2 3 4 5 6 7 8 |
# Backup quotidien à 3h du matin (après les dumps SQL) 30 3 * * * /home/backup_user/bin/rsnapshot-wrapper.sh daily # Backup hebdomadaire le dimanche à 10h30 30 10 * * 0 /home/backup_user/bin/rsnapshot-wrapper.sh weekly # Backup mensuel le 1er du mois à 14h30 30 14 1 * * /home/backup_user/bin/rsnapshot-wrapper.sh monthly |
Pas de hourly : avec 835 Go de données
(ownCloud 568 Go + TeamSpeak 235 Go), un backup toutes les 4 heures serait trop lourd.
Un backup daily à 3h du matin suffit pour la plupart des usages.
Ajouter hourly uniquement si le serveur de backup et la bande passante le permettent.
8.1 Notifications email (optionnel)
ssmtp est obsolète (plus maintenu, retiré de Debian 12).
Utiliser msmtp à la place, qui est compatible en remplacement direct.
|
1 |
apt install msmtp msmtp-mta -y |
Configurer /etc/msmtprc :
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# Configuration globale defaults auth on tls on tls_trust_file /etc/ssl/certs/ca-certificates.crt logfile /var/log/msmtp.log # Compte par défaut account default host smtp_server port smtp_port from admin@{{DOMAINE_GAMING}} user admin@{{DOMAINE_GAMING}} password <mot_de_passe_smtp> |
|
1 2 |
# Sécuriser le fichier (contient le mot de passe SMTP) chmod 600 /etc/msmtprc |
Modifier le cron pour envoyer un rapport :
|
1 2 3 |
# Copier l'utilitaire de rapport cp /usr/share/doc/rsnapshot/examples/utils/rsnapreport.pl /usr/local/bin/ chmod +x /usr/local/bin/rsnapreport.pl |
|
1 2 3 4 |
# Cron avec notification email 30 3 * * * /home/backup_user/bin/rsnapshot-wrapper.sh daily 2>&1 | /usr/local/bin/rsnapreport.pl | msmtp admin@{{DOMAINE_GAMING}} 30 10 * * 0 /home/backup_user/bin/rsnapshot-wrapper.sh weekly 2>&1 | /usr/local/bin/rsnapreport.pl | msmtp admin@{{DOMAINE_GAMING}} 30 14 1 * * /home/backup_user/bin/rsnapshot-wrapper.sh monthly 2>&1 | /usr/local/bin/rsnapreport.pl | msmtp admin@{{DOMAINE_GAMING}} |
8.2 Limiter la bande passante (optionnel)
Si le serveur de backup est distant et la bande passante limitée,
ajouter dans rsnapshot.conf :
|
1 |
rsync_long_args --rsync-path=/home/backup_user/bin/rsync-wrapper.sh --delete --numeric-ids --relative --delete-excluded --stats --bwlimit=10MiB |
--bwlimit=10MiB limite le transfert à 10 Mo/s.
Ajuster selon la bande passante disponible.
9) Tester et valider
9.1 Tester les scripts de dump sur le serveur de données
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# Se connecter en tant que backup_user su - backup_user # Tester chaque script individuellement /home/backup_user/bin/backup_mysql_docker.sh /home/backup_user/bin/backup_postgresql_docker.sh /home/backup_user/bin/backup_sqlite_docker.sh # Vérifier que les dumps sont là ls -lh /home/backup_user/files/mysql/ ls -lh /home/backup_user/files/psql/ ls -lh /home/backup_user/files/sqlite/ # Tester le script orchestrateur /home/backup_user/bin/docker-pre-backup.sh |
9.2 Vérifier l’intégrité des dumps
|
1 2 3 4 5 6 7 8 |
# Vérifier qu'un dump MySQL est lisible gunzip -t /home/backup_user/files/mysql/pterodactyl-all.*.sql.gz # Vérifier qu'un dump PostgreSQL est lisible gunzip -t /home/backup_user/files/psql/odoo17-all.*.sql.gz # Vérifier l'archive TeamSpeak tar -tzf /home/backup_user/files/sqlite/teamspeak3-data.*.tar.gz |
9.3 Lancer un premier backup complet
|
1 2 3 4 5 6 7 8 9 10 11 12 |
# Depuis le serveur de backup, en tant que backup_user /home/backup_user/bin/rsnapshot-wrapper.sh daily # Vérifier le résultat ls -la /media/hdd_backup/backup/daily.0/{{HOSTNAME_NOUVEAU}}/ # Vérifier que les dumps SQL sont inclus ls -lh /media/hdd_backup/backup/daily.0/{{HOSTNAME_NOUVEAU}}/home/backup_user/files/mysql/ ls -lh /media/hdd_backup/backup/daily.0/{{HOSTNAME_NOUVEAU}}/home/backup_user/files/psql/ # Vérifier que les docker-compose sont inclus ls /media/hdd_backup/backup/daily.0/{{HOSTNAME_NOUVEAU}}/home/docker/*/docker-compose.yml |
Premier backup = copie complète.
Le premier daily va transférer la totalité des données (~835 Go).
Selon la bande passante, cela peut prendre plusieurs heures.
Les backups suivants seront incrémentaux et beaucoup plus rapides.
10) Restauration
Chaque snapshot rsnapshot est un répertoire autonome.
La restauration dépend de ce qu’on veut récupérer.
10.1 Restaurer un fichier ou répertoire
|
1 2 3 4 5 6 7 8 |
# Exemple : restaurer un docker-compose.yml d'il y a 3 jours cp /media/hdd_backup/backup/daily.3/{{HOSTNAME_NOUVEAU}}/home/docker/pterodactyl/docker-compose.yml \ /tmp/docker-compose.yml.restored # Exemple : restaurer un site web complet sur le serveur OVH (port {{PORT_SSH_NOUVEAU}}) rsync -av -e "ssh -p {{PORT_SSH_NOUVEAU}}" \ /media/hdd_backup/backup/daily.1/{{HOSTNAME_NOUVEAU}}/home/{{USER_VESTA}}/web/{{DOMAINE_SITE}}/ \ root@{{IP_NOUVEAU}}:/home/{{USER_VESTA}}/web/{{DOMAINE_SITE}}/ |
10.2 Restaurer une base de données Docker
MariaDB (Pterodactyl)
|
1 2 3 4 5 6 7 8 |
# Copier le dump sur le serveur OVH (port {{PORT_SSH_NOUVEAU}}) scp -P {{PORT_SSH_NOUVEAU}} \ /media/hdd_backup/backup/daily.0/{{HOSTNAME_NOUVEAU}}/home/backup_user/files/mysql/pterodactyl-all.*.sql.gz \ root@{{IP_NOUVEAU}}:/tmp/ # Sur le serveur de données : restaurer dans le conteneur gunzip -c /tmp/pterodactyl-all.*.sql.gz | \ docker exec -i pterodactyl-db mariadb -u root -p{{DB_PASSWORD}} |
MySQL (Akeneo PIM)
|
1 2 |
gunzip -c /tmp/akeneo-pim.*.sql.gz | \ docker exec -i akeneo-mysql mysql -u root -pakeneo_pim akeneo_pim |
MySQL natif (PrestaShop, WordPress, Matomo…)
|
1 2 3 |
# Restaurer une base native (ex: {{DB_PRESTASHOP}}) gunzip -c /tmp/{{DB_PRESTASHOP}}.*.sql.gz | \ mysql --defaults-extra-file=/etc/mysql/backup-credentials.cnf |
PostgreSQL (ownCloud)
|
1 2 |
gunzip -c /tmp/owncloud-all.*.sql.gz | \ docker exec -i owncloud-db psql -U admin_owncloud |
PostgreSQL (Odoo 17)
|
1 2 |
gunzip -c /tmp/odoo17-all.*.sql.gz | \ docker exec -i odoo17-db psql -U odoo17 |
PostgreSQL (Odoo 8)
|
1 2 |
gunzip -c /tmp/odoo8-all.*.sql.gz | \ docker exec -i odoo8-db psql -U odoo8 |
SQLite (TeamSpeak 3)
|
1 2 3 4 5 6 7 8 9 10 |
# Stopper TeamSpeak avant restauration cd /home/docker/teamspeak3 docker compose stop # Extraire le backup (DB + WAL + SHM) tar -xzf /tmp/teamspeak3-data.*.tar.gz -C /var/ts3server/ # Note : les fichiers WAL/SHM seront rejoués automatiquement au démarrage # Redémarrer docker compose start |
10.3 Restauration complète d’un service Docker
Pour restaurer un service Docker complet (ex: Pterodactyl après un crash) :
- Installer Docker et docker compose sur le nouveau serveur
- Restaurer le répertoire complet :
12rsync -av /media/hdd_backup/backup/daily.0/{{HOSTNAME_NOUVEAU}}/home/docker/pterodactyl/ \/home/docker/pterodactyl/ - Restaurer les volumes Docker nommés si utilisés :
123456# Recréer le volumedocker volume create pterodactyl_database# Restaurer les données du volumersync -av /media/hdd_backup/backup/daily.0/{{HOSTNAME_NOUVEAU}}/var/lib/docker/volumes/pterodactyl_database/_data/ \/var/lib/docker/volumes/pterodactyl_database/_data/ - Démarrer la stack :
12cd /home/docker/pterodactyldocker compose up -d - Ou restaurer la base depuis le dump SQL (plus fiable) :
123# Attendre que MariaDB démarre, puis injecter le dumpgunzip -c /home/backup_user/files/mysql/pterodactyl-all.*.sql.gz | \docker exec -i pterodactyl-db mariadb -u root -p{{DB_PASSWORD}}
Dumps SQL vs volumes bruts : pour les bases de données,
privilégier toujours la restauration via dump SQL
(méthode 5) plutôt que la copie des fichiers bruts du volume
(méthode 3). Les fichiers bruts peuvent être incohérents si le conteneur
écrivait au moment du snapshot rsnapshot.
10.4 Restauration complète du serveur
En cas de perte totale du serveur :
- Réinstaller Debian 12 + MyVestaCP
- Installer Docker et docker compose
- Restaurer
/etc/(SSH, firewall, crons) - Restaurer
/usr/local/vesta/(templates Nginx, config MyVestaCP) - Restaurer
/home/{{USER_VESTA}}/web/(sites web) - Restaurer
/home/docker/(tous les docker-compose.yml et bind mounts) - Restaurer les volumes Docker nommés depuis
/var/lib/docker/volumes/ - Démarrer chaque stack Docker et injecter les dumps SQL
- Vérifier les crons, les templates Nginx, les certificats SSL
11) Variante Synology (Container Manager)
Si le serveur de backup est un NAS Synology, on peut exécuter rsnapshot
via Container Manager (Docker) avec l’image linuxserver/rsnapshot.
11.1 Préparer les répertoires
|
1 2 |
mkdir -p /volume1/NetBackup/conf mkdir -p /volume1/NetBackup/.ssh |
11.2 Générer et déployer la clé SSH
Générer la clé depuis Windows (PowerShell) ou directement sur le NAS via SSH :
|
1 2 |
# Depuis Windows PowerShell (clé sans passphrase pour automation) ssh-keygen -t ed25519 -N "" -f $env:USERPROFILE\.ssh\id_ed25519_rsnapshot |
Copier les deux fichiers dans /volume1/NetBackup/.ssh/ sur le NAS :
id_ed25519_rsnapshot— clé privée (rester confidentiel)id_ed25519_rsnapshot.pub— clé publique
Sécuriser les permissions sur le NAS (via SSH ou le panneau DSM) :
|
1 2 |
chmod 700 /volume1/NetBackup/.ssh chmod 600 /volume1/NetBackup/.ssh/id_ed25519_rsnapshot |
Ajouter le contenu de id_ed25519_rsnapshot.pub dans
/home/backup_user/.ssh/authorized_keys sur chaque
serveur de données (la même clé publique peut être utilisée pour plusieurs serveurs).
Pourquoi un seul fichier de clé pour plusieurs serveurs ?
La clé SSH identifie le client (le NAS). Chaque serveur de données décide
indépendamment d’autoriser ou non cette clé dans son fichier
authorized_keys. Une seule paire de clés suffit pour n’importe
quel nombre de serveurs.
11.3 Gestion multi-serveurs : ports SSH différents sur Synology
Le NAS Synology exécute rsnapshot dans un conteneur Docker (linuxserver/rsnapshot),
ce qui rend difficile l’utilisation d’un fichier ~/.ssh/config
traditionnel avec des alias. La solution la plus fiable est d’utiliser des
arguments ssh_args par ligne backup pour surcharger le port
serveur par serveur.
Erreur courante : définir un ssh_args global avec
un port ne fonctionne que si tous vos serveurs utilisent le même port SSH.
Dès que vous avez deux serveurs avec des ports différents (ex: {{PORT_SSH_NOUVEAU}} et {{PORT_SSH_VPS}}),
le ssh_args global sera forcément faux pour l’un d’eux.
Il faut supprimer le port du ssh_args global et l’indiquer
sur chaque ligne backup et backup_script.
11.4 Configuration rsnapshot pour Synology (multi-serveurs)
|
1 |
nano /volume1/NetBackup/conf/rsnapshot.conf |
Coller (attention : séparateurs = tabulations, pas des espaces) :
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
config_version 1.2 snapshot_root /volume1/NetBackup/ # =========================================== # Rétention # =========================================== retain hourly 20 retain daily 7 retain weekly 4 retain monthly 1 # =========================================== # Commandes système # =========================================== # cmd_rsync — appel direct à rsync (le sudo côté serveur est géré par --rsync-path dans rsync_long_args) cmd_rsync /usr/bin/rsync cmd_cp /bin/cp cmd_rm /bin/rm cmd_logger /usr/bin/logger cmd_ssh /usr/bin/ssh cmd_du /usr/bin/du # =========================================== # Options rsync # =========================================== rsync_short_args -aev rsync_long_args --rsync-path=/home/backup_user/bin/rsync-wrapper.sh --delete --hard-links --numeric-ids --relative --delete-excluded --stats --bwlimit=5000 # =========================================== # SSH — clé commune, le port est spécifié par ligne # PAS de -p ici : chaque serveur a un port différent # =========================================== ssh_args -i /volume1/NetBackup/.ssh/id_ed25519_rsnapshot # =========================================== # Lockfile et logs # =========================================== lockfile /volume1/NetBackup/.rsnapshot.pid logfile /volume1/NetBackup/rsnapshot.log verbose 3 link_dest 1 # =========================================== # VPS ({{IP_VPS}} / port {{PORT_SSH_VPS}}) # Les ssh_args par ligne surchargent le ssh_args global # =========================================== # Dumps MySQL du VPS avant la copie rsync backup_script /usr/bin/ssh -i /volume1/NetBackup/.ssh/id_ed25519_rsnapshot -p {{PORT_SSH_VPS}} backup_user@{{IP_VPS}} "/home/backup_user/bin/backup_mysql_ext.sh" unused_{{HOSTNAME_VPS}}/ # Système complet du VPS backup backup_user@{{IP_VPS}}:/ {{HOSTNAME_VPS}}/ exclude_file=/volume1/NetBackup/rsnapshot.excludes,ssh_args=-i /volume1/NetBackup/.ssh/id_ed25519_rsnapshot -p {{PORT_SSH_VPS}} # =========================================== # Serveur OVH dédié ({{IP_NOUVEAU}} / port {{PORT_SSH_NOUVEAU}}) # =========================================== # Dumps des bases de données Docker avant la copie rsync backup_script /usr/bin/ssh -i /volume1/NetBackup/.ssh/id_ed25519_rsnapshot -p {{PORT_SSH_NOUVEAU}} backup_user@{{IP_NOUVEAU}} "/home/backup_user/bin/docker-pre-backup.sh" unused1/ # Système complet du serveur OVH backup backup_user@{{IP_NOUVEAU}}:/ {{HOSTNAME_NOUVEAU}}/ exclude_file=/volume1/NetBackup/conf/rsync-exclude,ssh_args=-i /volume1/NetBackup/.ssh/id_ed25519_rsnapshot -p {{PORT_SSH_NOUVEAU}} |
Pourquoi ssh_args sur chaque ligne backup ?
rsnapshot ne supporte qu’un seul ssh_args global.
La syntaxe ssh_args=... dans les options par ligne (4e champ d’une directive
backup) permet de le surcharger pour chaque serveur séparément.
Sans cette surcharge, rsnapshot utiliserait le ssh_args global
— qui ne contient pas le bon port — et la connexion échouerait silencieusement
ou se connecterait sur le mauvais port.
backup_script et les ports : la directive
backup_script exécute une commande shell libre.
Elle ne bénéficie pas du mécanisme de surcharge ssh_args.
Il faut donc indiquer explicitement -i et -p
dans la commande SSH de chaque backup_script.
11.5 Container Manager
Dans Container Manager, créer un conteneur avec l’image
linuxserver/rsnapshot et monter les volumes suivants :
/volume1/NetBackup/conf/→/config
(contientrsnapshot.conf)/volume1/NetBackup/→/volume1/NetBackup/
(même chemin pour que les chemins absolus dersnapshot.conffonctionnent)/volume1/NetBackup/.ssh/→/root/.ssh/ou
/home/backup_user/.ssh/selon l’utilisateur du conteneur
Important : le chemin /volume1/NetBackup/.ssh/
doit être monté avec les mêmes permissions que celles attendues par SSH
(700 pour le répertoire, 600 pour la clé privée). Si le conteneur tourne en root,
monter vers /root/.ssh/ et adapter le chemin de la clé dans
rsnapshot.conf en conséquence.
11.6 Tester la configuration dans le conteneur
Avant de planifier les tâches, il est essentiel de valider que tout fonctionne
correctement depuis l’intérieur du conteneur. Voici les étapes de test,
de la plus simple à la plus complète.
11.6.1 Vérifier la syntaxe du fichier de configuration
|
1 2 3 4 5 |
# Entrer dans le conteneur docker exec -it rsnapshot bash # Tester la syntaxe de rsnapshot.conf rsnapshot configtest |
Résultat attendu : Syntax OK. Si une erreur apparaît, c’est
souvent un problème de tabulations (rsnapshot exige des tabulations,
pas des espaces) ou un chemin manquant. L’erreur indiquera la ligne fautive.
11.6.2 Vérifier la connectivité SSH vers chaque serveur
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# Depuis l'intérieur du conteneur, tester chaque serveur # VPS (port {{PORT_SSH_VPS}}) ssh -i /volume1/NetBackup/.ssh/id_ed25519_rsnapshot \ -p {{PORT_SSH_VPS}} \ -o StrictHostKeyChecking=accept-new \ backup_user@{{IP_VPS}} "echo OK && hostname" # Serveur OVH (port {{PORT_SSH_NOUVEAU}}) ssh -i /volume1/NetBackup/.ssh/id_ed25519_rsnapshot \ -p {{PORT_SSH_NOUVEAU}} \ -o StrictHostKeyChecking=accept-new \ backup_user@{{IP_NOUVEAU}} "echo OK && hostname" |
Chaque commande doit afficher OK suivi du hostname du serveur.
Si ça échoue :
- Permission denied (publickey) → la clé publique n’est pas dans
authorized_keyscôté serveur, ou les permissions de la clé
privée ne sont pas en 600 - Connection refused → le port SSH est incorrect ou le
serveur est arrêté - Host key verification failed → première connexion,
l’option-o StrictHostKeyChecking=accept-newdevrait résoudre
ce problème en acceptant automatiquement la clé hôte la première fois
Première connexion : lors de la toute première connexion SSH
depuis le conteneur, le serveur distant n’est pas encore dans
known_hosts. L’option StrictHostKeyChecking=accept-new
accepte automatiquement la clé hôte si elle n’est pas encore connue, mais
refuse si elle a changé (protection contre les attaques man-in-the-middle).
C’est l’option idéale pour l’automation.
11.6.3 Tester les scripts de pre-backup (dump des bases)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
# Tester le dump MySQL du VPS ssh -i /volume1/NetBackup/.ssh/id_ed25519_rsnapshot \ -p {{PORT_SSH_VPS}} \ backup_user@{{IP_VPS}} "/home/backup_user/bin/backup_mysql_ext.sh" # Vérifier que les dumps ont été créés ssh -i /volume1/NetBackup/.ssh/id_ed25519_rsnapshot \ -p {{PORT_SSH_VPS}} \ backup_user@{{IP_VPS}} "ls -lh /home/backup_user/files/mysql/" # Tester le pre-backup Docker du serveur OVH ssh -i /volume1/NetBackup/.ssh/id_ed25519_rsnapshot \ -p {{PORT_SSH_NOUVEAU}} \ backup_user@{{IP_NOUVEAU}} "/home/backup_user/bin/docker-pre-backup.sh" # Vérifier que les dumps ont été créés ssh -i /volume1/NetBackup/.ssh/id_ed25519_rsnapshot \ -p {{PORT_SSH_NOUVEAU}} \ backup_user@{{IP_NOUVEAU}} "ls -lh /home/backup_user/files/mysql/ /home/backup_user/files/psql/ /home/backup_user/files/sqlite/ 2>/dev/null" |
Chaque script doit s’exécuter sans erreur et produire des fichiers
.sql.gz ou .tar.gz datés du jour.
11.6.4 Dry-run rsnapshot (simulation sans transfert)
|
1 2 3 |
# Toujours depuis l'intérieur du conteneur # Le -t (test) simule l'exécution sans rien copier rsnapshot -t hourly |
Cette commande affiche toutes les commandes rsync, ssh et cp que rsnapshot
exécuterait, sans rien faire réellement. Vérifier que :
- Chaque ligne
backupetbackup_scriptest bien listée - Les chemins source et destination sont corrects
- Les options
ssh_argscontiennent le bon port pour chaque serveur - Le
cmd_rsyncpointe vers/usr/bin/rsync
11.6.5 Premier backup réel (un seul serveur)
Si tous les tests ci-dessus sont concluants, lancer un vrai backup.
Pour limiter le risque, on peut tester avec un seul fichier d’abord :
|
1 2 3 4 5 6 7 8 9 |
# Tester rsync manuellement sur un petit répertoire /usr/bin/rsync -aev \ --rsync-path=/home/backup_user/bin/rsync-wrapper.sh \ -e "ssh -i /volume1/NetBackup/.ssh/id_ed25519_rsnapshot -p {{PORT_SSH_NOUVEAU}}" \ backup_user@{{IP_NOUVEAU}}:/etc/hostname \ /tmp/test_rsync/ # Si ça fonctionne, lancer le vrai hourly rsnapshot hourly |
Le premier hourly sera le plus long (transfert complet).
Les suivants seront rapides grâce aux hard links et au transfert incrémental.
Surveiller les logs en temps réel :
|
1 2 |
# Dans un second terminal (sur le NAS) tail -f /volume1/NetBackup/rsnapshot.log |
Ordre des tests : toujours valider dans cet ordre :
configtest → SSH → scripts de dump → dry-run → backup réel.
Ne jamais sauter directement au backup réel, car un échec silencieux
(ex: mauvais port SSH) produirait un snapshot vide ou incomplet
sans message d’erreur évident.
Problèmes fréquents rencontrés en pratique : si l’une des
étapes ci-dessus échoue, consulter la section
11.7 Dépannage Synology qui documente les six
problèmes les plus courants rencontrés lors du déploiement depuis un
conteneur Docker Synology : fichier d’exclusion manquant, fins de ligne
Windows dans authorized_keys, IP bannie par fail2ban,
backup_user absent de AllowUsers, blocage par
la double authentification (2FA / Google Authenticator), et permissions
de fichiers incorrectes dans le conteneur.
11.7 Dépannage Synology
Cette section documente les six problèmes concrets rencontrés lors de la
mise en service de rsnapshot depuis un conteneur Docker sur NAS Synology
vers un serveur OVH. Chaque problème est décrit avec son symptôme, son
explication et sa correction.
11.7.1 Fichier d’exclusion rsync manquant
Symptôme : rsnapshot échoue avec une erreur du type
exclude_file /volume1/NetBackup/conf/rsync-exclude does not exist
ou rsync refuse de démarrer.
Cause : la directive exclude_file dans
rsnapshot.conf pointe vers un fichier qui n’a pas encore
été créé.
Correction : créer le fichier avec les exclusions
Linux standard :
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
cat > /volume1/NetBackup/conf/rsync-exclude << 'EOF' /proc/ /sys/ /dev/ /tmp/ /run/ /var/run/ /var/lock/ /var/cache/apt/ /var/tmp/ /lost+found *.sock *.pid EOF |
Ce fichier est lu par rsync côté client (NAS). Les chemins sont relatifs
à la racine du serveur distant. Les entrées commençant par /
n’excluent que les répertoires à la racine du système de fichiers distant.
11.7.2 Fins de ligne Windows dans authorized_keys
Symptôme : SSH renvoie Permission denied (publickey)
même après avoir vérifié que la bonne clé publique est présente dans
~backup_user/.ssh/authorized_keys. La clé semble correcte
visuellement mais est systématiquement refusée.
Cause : le fichier a été créé ou édité sous Windows
et contient des fins de ligne \r\n (CRLF) au lieu de
\n (LF). SSH refuse silencieusement les clés avec des
caractères \r car ils font partie de la clé analysée.
On peut le détecter avec :
|
1 2 3 |
cat -A ~/.ssh/authorized_keys # Une ligne saine se termine par $ # Une ligne corrompue se termine par ^M$ |
Correction : recréer le fichier avec des fins de ligne
Unix propres. Depuis le serveur cible, en tant que root ou via sudo :
|
1 2 3 4 5 6 7 8 9 |
# Option 1 : convertir en place avec dos2unix dos2unix /home/backup_user/.ssh/authorized_keys # Option 2 : recréer le fichier (remplacer par la vraie clé publique) su - backup_user mkdir -p ~/.ssh chmod 700 ~/.ssh printf 'ssh-ed25519 AAAAC3Nza... backup_user@rsnapshot\n' > ~/.ssh/authorized_keys chmod 600 ~/.ssh/authorized_keys |
Important : SSH n’affiche aucun message d’erreur
spécifique pour ce problème — la sortie est identique à une clé
simplement absente. C’est l’une des causes les plus difficiles à
diagnostiquer sans penser à vérifier les fins de ligne.
11.7.3 IP bannie par fail2ban
Symptôme : les premières tentatives SSH depuis le
conteneur renvoient Connection refused ou ne reçoivent
pas de réponse du tout, même si le port SSH est correct et que le
serveur est accessible depuis d’autres machines.
Cause : les échecs SSH successifs (dus par exemple
aux problèmes de clé décrits ci-dessus) ont déclenché fail2ban, qui
a banni l’IP du NAS. Sur certains serveurs, il peut exister
deux chaînes iptables fail2ban distinctes : une
chaîne récente f2b-sshd et une ancienne chaîne héritée
fail2ban-SSH. L’IP peut être bannie dans l’une ou l’autre
(ou les deux).
Diagnostic :
|
1 2 3 4 5 6 |
# Vérifier les deux chaînes possibles (en root sur le serveur cible) iptables -L f2b-sshd -n --line-numbers iptables -L fail2ban-SSH -n --line-numbers # Ou chercher l'IP du NAS directement iptables -L -n | grep 82.64.175.106 |
Correction immédiate (débannir l’IP) :
|
1 2 3 4 5 |
# Débannir depuis la chaîne récente (fail2ban ≥ 0.10) fail2ban-client set sshd unbanip 82.64.175.106 # Débannir manuellement depuis l'ancienne chaîne si elle existe encore iptables -D fail2ban-SSH -s 82.64.175.106 -j REJECT |
Correction permanente (whitelist dans fail2ban) :
|
1 2 |
# Éditer /etc/fail2ban/jail.local nano /etc/fail2ban/jail.local |
|
1 2 |
[DEFAULT] ignoreip = 127.0.0.1/8 ::1 82.64.175.106 |
|
1 2 |
# Recharger fail2ban fail2ban-client reload |
L’IP du NAS Synology peut changer si elle est attribuée par DHCP.
Préférer une IP fixe ou une plage CIDR (ex : 82.64.175.0/24)
dans la whitelist pour éviter de devoir la mettre à jour.
11.7.4 backup_user absent de AllowUsers
Symptôme : SSH renvoie Permission denied
immédiatement, sans même demander la clé. Les logs du serveur
(/var/log/auth.log) affichent :
User backup_user from X.X.X.X not allowed because not listed in AllowUsers.
Cause : /etc/ssh/sshd_config contient
une directive AllowUsers qui liste explicitement les
utilisateurs autorisés à se connecter. Si backup_user
n’y figure pas, SSH le refuse systématiquement, quelle que soit la clé.
Correction :
|
1 2 |
# Vérifier la directive existante grep AllowUsers /etc/ssh/sshd_config |
|
1 2 |
# Éditer sshd_config nano /etc/ssh/sshd_config |
|
1 2 3 4 5 |
# Avant AllowUsers debian # Après AllowUsers debian backup_user |
|
1 2 |
# Recharger SSH (sans couper les sessions existantes) systemctl reload sshd |
11.7.5 Blocage par la double authentification (2FA / Google Authenticator)
Symptôme : SSH demande un code à usage unique
(Verification code:) alors qu’on s’attend à une connexion
entièrement automatique par clé. Ou bien la connexion échoue avec
Permission denied (publickey,keyboard-interactive) car
rsnapshot ne peut pas répondre interactivement à une invite.
Cause : le serveur impose une authentification
à double facteur (PAM + Google Authenticator) pour tous les
utilisateurs, y compris backup_user. La méthode
d’authentification requise est publickey,keyboard-interactive
ce qui rend l’automatisation impossible.
Correction en deux étapes :
Étape 1 — Exempter backup_user dans PAM
(/etc/pam.d/sshd) : ajouter la ligne d’exemption
avant la ligne pam_google_authenticator.so :
|
1 |
nano /etc/pam.d/sshd |
|
1 2 3 4 5 6 7 8 |
# Ajouter AVANT la ligne pam_google_authenticator.so : auth [success=done default=ignore] pam_succeed_if.so user = backup_user # Exemple de contexte dans le fichier : # auth required pam_google_authenticator.so nullok # devient (dans l'ordre) : auth [success=done default=ignore] pam_succeed_if.so user = backup_user auth required pam_google_authenticator.so nullok |
La directive success=done fait que PAM saute toutes les
lignes suivantes du groupe auth pour cet utilisateur,
court-circuitant ainsi Google Authenticator.
Étape 2 — Ajouter un bloc Match dans sshd_config :
|
1 |
nano /etc/ssh/sshd_config |
|
1 2 3 4 |
# Ajouter à la fin du fichier : Match User backup_user AuthenticationMethods publickey KbdInteractiveAuthentication no |
|
1 2 |
# Recharger SSH systemctl reload sshd |
Le bloc Match User remplace les directives globales
pour cet utilisateur spécifique : seule la clé publique est acceptée,
et l’authentification par clavier (qui déclenche le code OTP) est
désactivée.
Ordre obligatoire des modifications : modifier PAM
avant de recharger SSH. Sinon, une configuration SSH
incomplète peut verrouiller l’accès à tous les utilisateurs.
Toujours conserver une session SSH ouverte en parallèle lors de
ces modifications.
11.7.6 Permissions incorrectes sur les fichiers dans le conteneur Docker
Symptôme : rsnapshot échoue au démarrage ou SSH
refuse la clé privée avec un message du type
Permissions 0000 for '/volume1/NetBackup/.ssh/id_ed25519_rsnapshot' are too open
ou bad permissions.
Cause : les fichiers montés depuis le volume Synology
dans le conteneur Docker peuvent apparaître avec des permissions
---------- (0000) à l’intérieur du conteneur, indépendamment
de ce que DSM affiche. SSH et rsnapshot refusent de fonctionner
avec des clés dont les permissions sont trop ouvertes ou trop
fermées.
Correction : corriger les permissions depuis
l’intérieur du conteneur :
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# Entrer dans le conteneur docker exec -it rsnapshot bash # Corriger les permissions du répertoire et des clés SSH chmod 700 /volume1/NetBackup/.ssh chmod 600 /volume1/NetBackup/.ssh/id_ed25519_rsnapshot chmod 644 /volume1/NetBackup/.ssh/id_ed25519_rsnapshot.pub # Corriger les permissions du fichier de configuration rsnapshot chmod 600 /volume1/NetBackup/conf/rsnapshot.conf # Vérifier ls -la /volume1/NetBackup/.ssh/ |
Ces corrections doivent être refaites si le conteneur est recréé
(par exemple après une mise à jour de l’image). Pour éviter cela,
on peut ajouter un script de démarrage ou utiliser un
entrypoint personnalisé qui applique les permissions
au lancement du conteneur.
11.7.7 Le script de pre-backup échoue avec « Permission denied » sur backup.log
Symptôme : le backup_script (ex: docker-pre-backup.sh)
produit de nombreuses erreurs Permission denied sur
/home/backup_user/backup.log, même si le script lui-même
est exécutable.
Cause : le fichier backup.log a été créé par
root (lors d’un test manuel avec sudo) et appartient
à root:root. Quand backup_user exécute le script
via SSH, il ne peut pas écrire dans un fichier appartenant à root.
|
1 2 3 4 5 6 |
# Vérifier le propriétaire ls -la /home/backup_user/backup.log # -rw-r--r-- 1 root root ... ← problème # Corriger chown backup_user:backup_user /home/backup_user/backup.log |
Vérifier aussi les droits Docker : si le script
docker-pre-backup.sh exécute des commandes docker exec
pour dumper les bases de données, backup_user doit être
membre du groupe docker :
|
1 2 3 4 5 |
# Vérifier les groupes groups backup_user # Ajouter au groupe docker si nécessaire usermod -aG docker backup_user |
Principe général : tous les fichiers dans
/home/backup_user/ doivent appartenir à
backup_user:backup_user. Après un test manuel en root,
toujours vérifier avec chown -R backup_user:backup_user /home/backup_user/.
11.7.8 Permission denied sur les dossiers de dumps (bit execute manquant)
Symptôme : le script docker-pre-backup.sh échoue avec
Permission denied sur les dossiers
/home/backup_user/files/mysql/, psql/,
sqlite/, etc. — alors qu’ils appartiennent bien à
backup_user.
Cause : les dossiers ont le mode drw-r----- (640)
au lieu de drwxr-x--- (750). Sans le bit execute
(x) sur un dossier, il est impossible d’y accéder ou d’y créer
des fichiers, même en étant propriétaire. Cela arrive typiquement quand le
script contient chmod -R 640 /home/backup_user/files/ qui applique
640 aussi bien aux fichiers qu’aux dossiers.
|
1 2 3 4 5 6 7 8 |
# Vérifier (les dossiers doivent avoir le x) ls -la /home/backup_user/files/ # drw-r----- ← problème (pas de x) # drwxr-x--- ← correct # Corriger immédiatement sudo chmod 750 /home/backup_user/files/ sudo chmod 750 /home/backup_user/files/*/ |
Corriger le script pour éviter que le problème ne revienne.
Dans /home/backup_user/bin/docker-pre-backup.sh, remplacer :
|
1 2 3 4 5 |
# Avant (casse les dossiers) chmod -R 640 /home/backup_user/files/ # Après (X majuscule = execute uniquement sur les dossiers) chmod -R u+rwX /home/backup_user/files/ |
utilise
sudo chown / sudo chmod et que cescommandes ne figurent pas dans le fichier sudoers, elles échoueront
silencieusement lorsque le script est appelé via SSH par rsnapshot.
En pratique, cette erreur est cosmétique : les dumps
sont créés par
backup_user (ou via sudo rsyncdéjà autorisé) et les fichiers appartiennent déjà au bon utilisateur.
Il n’est pas nécessaire d’ajouter des entrées sudoers supplémentaires.
11.7.9 Restreindre backup_user à l’IP du NAS (sécurité)
Si backup_user est exempté de la double authentification (2FA),
il est fortement recommandé de restreindre sa connexion SSH à l’adresse IP
du NAS uniquement. Ainsi, même en cas de vol de la clé privée, celle-ci
sera inutilisable depuis une autre machine.
Méthode : directive from= dans authorized_keys
Sur chaque serveur distant, éditer le fichier
/home/backup_user/.ssh/authorized_keys et ajouter
la restriction from="IP_DU_NAS" devant la clé publique :
|
1 2 3 4 5 |
# Avant (sans restriction) ssh-ed25519 AAAAC3Nza... backup_synology # Après (connexion autorisée uniquement depuis l'IP du NAS) from="82.64.175.106" ssh-ed25519 AAAAC3Nza... backup_synology |
Tester depuis le conteneur rsnapshot :
|
1 2 |
ssh -i /volume1/NetBackup/.ssh/id_ed25519_rsnapshot -p {{PORT_SSH_NOUVEAU}} \ backup_user@{{IP_NOUVEAU}} "echo OK" |
(box sans IP fixe), le backup sera bloqué. Dans ce cas, penser à mettre
à jour l’
authorized_keys à chaque changement d’IP, ou utiliserun réseau VPN (WireGuard, Tailscale) pour obtenir une IP stable.
11.8 Planificateur de tâches Synology
11.8.1 Script wrapper (recommandé)
Plutôt que d’appeler docker exec directement, utiliser un script
wrapper qui gère les conflits avec Hyper Backup, le double lancement,
la rotation des logs et la validation des paramètres.
Créer le fichier /volume1/NetBackup/rsnapshot_wrapper.sh :
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
#!/bin/sh set -eu MODE="${1:-daily}" LOG="/volume1/NetBackup/rsnapshot_wrapper.log" DOCKER="/usr/local/bin/docker" CONTAINER="rsnapshot" # --- Fonctions utilitaires --- log() { echo "$(date '+%F %T') $*" | tee -a "$LOG"; } rotate_log() { if [ -f "$LOG" ] && [ "$(wc -l < "$LOG")" -gt 50000 ]; then tail -n 10000 "$LOG" > "${LOG}.tmp" && mv "${LOG}.tmp" "$LOG" log "INFO: log rotaté (> 50000 lignes)" fi } # --- Validations --- # Rotation du log en premier rotate_log # Valider le mode case "$MODE" in hourly|daily|weekly|monthly) ;; *) log "ERROR: MODE invalide: '$MODE' (attendu: hourly|daily|weekly|monthly)" exit 1 ;; esac log "START mode=$MODE user=$(id -un) uid=$(id -u)" # Docker présent ? if [ ! -x "$DOCKER" ]; then log "ERROR: docker introuvable: $DOCKER" exit 1 fi # Hyper Backup en cours ? if pgrep -f 'synobackup|img_backup|hyperbackup' >/dev/null 2>&1; then log "SKIP: Hyper Backup détecté" pgrep -af 'synobackup|img_backup|hyperbackup' 2>/dev/null | tee -a "$LOG" || true log "END skipped" exit 0 fi # --- Lock persistant avec détection orphelin --- LOCKDIR="/volume1/NetBackup/rsnapshot.lock" LOCKPID="$LOCKDIR/pid" if ! mkdir "$LOCKDIR" 2>/dev/null; then if [ -f "$LOCKPID" ] && kill -0 "$(cat "$LOCKPID")" 2>/dev/null; then log "SKIP: rsnapshot déjà en cours (pid=$(cat "$LOCKPID"))" exit 0 else log "WARN: lock orphelin détecté, nettoyage" rm -rf "$LOCKDIR" mkdir "$LOCKDIR" fi fi echo $$ > "$LOCKPID" trap 'rm -rf "$LOCKDIR" 2>/dev/null || true' EXIT # --- Vérification du conteneur --- if ! DOCKER_PS=$("$DOCKER" ps --format '{{.Names}}' 2>&1); then log "ERROR: docker daemon inaccessible: $DOCKER_PS" exit 1 fi if ! echo "$DOCKER_PS" | grep -qx "$CONTAINER"; then log "ERROR: conteneur '$CONTAINER' non démarré/inexistant" "$DOCKER" ps --format 'table {{.Names}}\t{{.Status}}' 2>/dev/null | tee -a "$LOG" || true exit 1 fi # --- Exécution --- log "RUN: $DOCKER exec $CONTAINER rsnapshot $MODE" nice -n 19 "$DOCKER" exec "$CONTAINER" rsnapshot "$MODE" >>"$LOG" 2>&1 RC=$? log "END rc=$RC" exit "$RC" |
Fonctionnalités du wrapper :
- Validation du mode — refuse les fautes de frappe
- Skip Hyper Backup — ne lance pas rsnapshot si Hyper Backup tourne
- Lock persistant — dans
/volume1/NetBackup/(survit aux reboots),
avec détection et nettoyage des locks orphelins (PID mort) - Rotation du log — tronque à 10 000 lignes quand le fichier dépasse 50 000 lignes
- Vérification Docker — distingue « daemon inaccessible » de « conteneur absent »
- nice -n 19 — priorité CPU basse pour ne pas impacter le NAS
11.8.2 Tâches planifiées
DSM → Panneau de configuration → Planificateur de tâches →
créer trois tâches planifiées (Script défini par l’utilisateur) :
| Tâche | Fréquence | Commande |
|---|---|---|
| rsnapshot hourly | Tous les jours à 01:00 | /bin/sh /volume1/NetBackup/rsnapshot_wrapper.sh hourly |
| rsnapshot daily | Dimanche à 01:00 | /bin/sh /volume1/NetBackup/rsnapshot_wrapper.sh daily |
| rsnapshot weekly | 1er du mois à 01:00 | /bin/sh /volume1/NetBackup/rsnapshot_wrapper.sh weekly |
hourly, daily, weeklysont des labels de rotation, pas des fréquences obligatoires.
Lancer
rsnapshot hourly une fois par jour est parfaitement valide —cela crée un nouveau snapshot dans la rotation « hourly ».
11.8.3 Hardlinks et espace disque
rsnapshot utilise des liens durs (hardlinks) : seuls les fichiers
modifiés sont réellement copiés. Les fichiers identiques entre deux
snapshots partagent le même espace disque. Exemple concret :
- Serveur de 50 Go, 500 Mo modifiés par jour
- 7 snapshots hourly = ~53 Go réels (pas 350 Go)
- 7 hourly + 4 daily + 3 weekly ≈ 57 Go
Chaque snapshot apparaît comme un backup complet (navigable normalement),
mais l’espace réel consommé est bien moindre. Vérifier avec :
|
1 2 3 4 5 |
# Espace apparent par snapshot du -sh /volume1/NetBackup/hourly.*/ # Espace réel total sur le disque df -h /volume1/NetBackup/ |
11.8.4 Coordination avec Hyper Backup
Si le NAS exécute aussi Synology Hyper Backup (sauvegarde vers
un disque USB externe par exemple), il faut éviter que les deux tournent
simultanément pour limiter la contention I/O et garantir la cohérence
des données capturées par Hyper Backup.
Le wrapper gère automatiquement ce cas : si Hyper Backup est détecté
(pgrep synobackup|img_backup|hyperbackup), rsnapshot est
reporté au prochain créneau planifié (SKIP: Hyper Backup détecté
dans le log).
Planification recommandée :
- rsnapshot : la nuit (ex. 01:00)
- Hyper Backup : le week-end en journée (ex. samedi 15:00)
(serveurs volumineux), le lock persistant empêche un double lancement le lendemain.
Le backup suivant sera automatiquement reporté jusqu’à la fin du backup en cours.
Hyper Backup, grâce à sa propre planification interne, n’est pas affecté.
En revanche, si rsnapshot tourne encore quand Hyper Backup démarre, Hyper Backup
capturera un état potentiellement en cours de synchronisation — ce qui reste
acceptable car les données ne sont jamais corrompues (cohérence à terme).
Récapitulatif : ce que rsnapshot sauvegarde
| Élément | Méthode | Chemin sauvegardé |
|---|---|---|
| Dumps MySQL / MariaDB | docker exec + gzip |
/home/backup_user/files/mysql/*.sql.gz |
| Dumps PostgreSQL | docker exec + gzip |
/home/backup_user/files/psql/*.sql.gz |
| Backup SQLite TS3 | docker cp DB+WAL + tar |
/home/backup_user/files/sqlite/*.tar.gz |
| Dumps MySQL natif (VestaCP) | mysqldump + gzip |
/home/backup_user/files/mysql_native/*.sql.gz |
| Snapshot Elasticsearch | ES snapshot API + tar | /home/backup_user/files/elasticsearch/*.tar.gz |
| docker-compose.yml | rsync direct | /home/docker/*/docker-compose.yml |
| Bind mounts Docker | rsync direct | /home/docker/*/data/, /home/docker/*/config/ |
| Volumes Docker nommés | rsync direct | /var/lib/docker/volumes/*/_data/ |
| Sites web MyVestaCP | rsync direct | /home/{{USER_VESTA}}/web/*/ |
| Templates Nginx custom | rsync direct | /usr/local/vesta/data/templates/web/nginx/ |
| Config système | rsync direct | /etc/ |
| ownCloud fichiers | rsync direct | /home/docker/owncloud/data/ (568 Go) |
| TeamSpeak fichiers | rsync direct | /home/docker/teamspeak3/data/ (235 Go) |
Ce qui n’est PAS sauvegardé (et c’est normal) :
les couches d’images Docker (overlay2/), les conteneurs eux-mêmes,
les caches applicatifs, /proc, /sys, /tmp.
Tout cela se recrée avec un docker compose up -d
et les images se retéléchargent automatiquement.
Sécurité : chiffrement des backups
Les backups contiennent des données sensibles (bases de données,
fichiers utilisateurs, configurations avec mots de passe).
Quelques recommandations :
- Transfert : déjà chiffré via SSH (rsync over SSH). OK.
- Stockage : si le disque de backup est un disque externe ou un NAS,
activer le chiffrement du volume :- Serveur dédié : utiliser
LUKSpour chiffrer la partition :
cryptsetup luksFormat /dev/sdX1 - Synology : activer le dossier partagé chiffré
dans DSM lors de la création du volume
- Serveur dédié : utiliser
- Dumps SQL : les fichiers
.sql.gzcontiennent les données
en clair (une fois décompressés). Le chiffrement du volume
les protège au repos.
Alternative simple : si LUKS est trop complexe à mettre en place,
s’assurer au minimum que le serveur de backup est isolé du réseau public
(pas de port ouvert, accès SSH uniquement par clé) et que le disque
n’est pas accessible physiquement par des tiers.
0 commentaire