[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
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
apt update && apt install rsync bzip2 -y
3.2 Créer l’utilisateur de backup
# 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.
nano /home/backup_user/bin/rsync-wrapper.sh
Coller :
#!/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
visudo
Ajouter à la fin :
# 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
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)
nano /home/backup_user/bin/backup_mysql_docker.sh
Coller :
#!/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)
nano /home/backup_user/bin/backup_postgresql_docker.sh
Coller :
#!/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)
nano /home/backup_user/bin/backup_sqlite_docker.sh
Coller :
#!/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é.
nano /home/backup_user/bin/backup_mysql_native.sh
Coller :
#!/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=
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 :
nano /etc/mysql/backup-credentials.cnf
Coller :
[client]
user=root
password=
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
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.
nano /home/backup_user/bin/docker-pre-backup.sh
Coller :
#!/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
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
/home/backup_user/bin/backup_mysql_docker.sh
Vérifier :
# 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
/home/backup_user/bin/backup_postgresql_docker.sh
Vérifier :
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
/home/backup_user/bin/backup_sqlite_docker.sh
Vérifier :
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)
sudo /home/backup_user/bin/backup_mysql_native.sh
Vérifier :
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 :
/home/backup_user/bin/docker-pre-backup.sh
cat /home/backup_user/backup.log
Le log doit afficher exit 0 pour chaque script :
----- 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
apt update && apt install rsnapshot rsync -y
6.2 Préparer le disque de backup
# 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
adduser backup_user --uid 400
Ajouter dans /etc/sudoers via visudo :
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)
# 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.
# 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 :
chmod 700 /volume1/NetBackup/.ssh chmod 600 /volume1/NetBackup/.ssh/id_ed25519_rsnapshot
Tester chaque connexion depuis le NAS :
# 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
# 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
nano /home/backup_user/bin/rsnapshot-wrapper.sh
Coller :
#!/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
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
nano /home/backup_user/rsync-exclude
Coller :
# ===========================================
# 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.
nano /home/backup_user/.ssh/config
Coller :
# 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
chmod 600 /home/backup_user/.ssh/config
Tester les alias :
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 :
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)
nano /etc/rsnapshot.conf
Rappel : chaque «TAB» ci-dessous représente
une vraie tabulation. Ne pas remplacer par des espaces.
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
# Vérifier la syntaxe
rsnapshot configtest
# Simulation (dry-run) — ne copie rien
rsnapshot -t daily
8) Automatiser avec cron
sudo crontab -e -u backup_user
Ajouter :
# 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.
apt install msmtp msmtp-mta -y
Configurer /etc/msmtprc :
# 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
# Sécuriser le fichier (contient le mot de passe SMTP)
chmod 600 /etc/msmtprc
Modifier le cron pour envoyer un rapport :
# Copier l'utilitaire de rapport
cp /usr/share/doc/rsnapshot/examples/utils/rsnapreport.pl /usr/local/bin/
chmod +x /usr/local/bin/rsnapreport.pl
# 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 :
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
# 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
# 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
# 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
# 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)
# 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)
gunzip -c /tmp/akeneo-pim.*.sql.gz | \
docker exec -i akeneo-mysql mysql -u root -pakeneo_pim akeneo_pim
MySQL natif (PrestaShop, WordPress, Matomo…)
# 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)
gunzip -c /tmp/owncloud-all.*.sql.gz | \
docker exec -i owncloud-db psql -U admin_owncloud
PostgreSQL (Odoo 17)
gunzip -c /tmp/odoo17-all.*.sql.gz | \
docker exec -i odoo17-db psql -U odoo17
PostgreSQL (Odoo 8)
gunzip -c /tmp/odoo8-all.*.sql.gz | \
docker exec -i odoo8-db psql -U odoo8
SQLite (TeamSpeak 3)
# 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 :
rsync -av /media/hdd_backup/backup/daily.0/{{HOSTNAME_NOUVEAU}}/home/docker/pterodactyl/ \ /home/docker/pterodactyl/ - Restaurer les volumes Docker nommés si utilisés :
# Recréer le volume docker volume create pterodactyl_database # Restaurer les données du volume rsync -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 :
cd /home/docker/pterodactyl docker compose up -d - Ou restaurer la base depuis le dump SQL (plus fiable) :
# Attendre que MariaDB démarre, puis injecter le dump gunzip -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
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 :
# 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) :
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)
nano /volume1/NetBackup/conf/rsnapshot.conf
Coller (attention : séparateurs = tabulations, pas des espaces) :
config_version 1.2
snapshot_root /volume1/NetBackup/
# ===========================================
# Rétention
# ===========================================
retain hourly 7
retain daily 4
retain weekly 4
retain monthly 3
# ===========================================
# 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
# 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
# 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)
# 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)
# 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 :
# 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 :
# 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 :
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 :
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 :
# 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 :
# 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 {{IP_NAS}}
Correction immédiate (débannir l'IP) :
# Débannir depuis la chaîne récente (fail2ban ≥ 0.10)
fail2ban-client set sshd unbanip {{IP_NAS}}
# Débannir manuellement depuis l'ancienne chaîne si elle existe encore
iptables -D fail2ban-SSH -s {{IP_NAS}} -j REJECT
Correction permanente (whitelist dans fail2ban) :
# Éditer /etc/fail2ban/jail.local
nano /etc/fail2ban/jail.local
[DEFAULT]
ignoreip = 127.0.0.1/8 ::1 {{IP_NAS}}
# 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 : {{IP_NAS_CIDR}})
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 :
# Vérifier la directive existante
grep AllowUsers /etc/ssh/sshd_config
# Éditer sshd_config
nano /etc/ssh/sshd_config
# Avant
AllowUsers debian
# Après
AllowUsers debian backup_user
# 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 :
nano /etc/pam.d/sshd
# 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 :
nano /etc/ssh/sshd_config
# Ajouter à la fin du fichier :
Match User backup_user
AuthenticationMethods publickey
KbdInteractiveAuthentication no
# 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 :
# 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.
# 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 :
# 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.
# 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 :
# 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 :
# Avant (sans restriction)
ssh-ed25519 AAAAC3Nza... backup_synology
# Après (connexion autorisée uniquement depuis l'IP du NAS)
from="{{IP_NAS}}" ssh-ed25519 AAAAC3Nza... backup_synology
Tester depuis le conteneur rsnapshot :
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,
l'enchaînement automatique des niveaux rsnapshot dans le bon ordre,
la rotation des logs et la validation des paramètres.
Créer le fichier /volume1/NetBackup/rsnapshot_wrapper.sh :
#!/bin/sh
set -eu
# ===========================================================================
# rsnapshot_wrapper.sh — Wrapper complet pour Synology DSM 7
#
# Usage:
# ./rsnapshot_wrapper.sh auto <- une seule tâche planifiée (recommandé)
# ./rsnapshot_wrapper.sh hourly <- forcer un niveau spécifique
# ./rsnapshot_wrapper.sh daily
# ./rsnapshot_wrapper.sh weekly
# ./rsnapshot_wrapper.sh monthly
#
# En mode "auto", le script détermine automatiquement quels niveaux
# exécuter et les lance dans le bon ordre :
# monthly (si 1er du mois) -> weekly (si dimanche) -> daily -> hourly
# ===========================================================================
MODE="${1:-auto}"
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
}
# ===========================================================================
# Détection Hyper Backup — DSM 7 compatible
# Note : pgrep n'existe pas sur Synology DSM, on utilise ps + grep
# ===========================================================================
is_hyperbackup_running() {
# Processus réels observés sur DSM 7 :
# - dsmbackup --running-on-dev = tâche Hyper Backup active
# - synolocalbkp --backup = copie locale en cours
# - img_backup = backup d'image (DSM 6)
# On exclut synobackupd (daemon permanent, toujours actif)
HB_PS=$(ps aux 2>/dev/null || ps -ef 2>/dev/null || echo "")
if echo "$HB_PS" | grep -v grep | grep -q 'dsmbackup'; then
log "HB détecté: dsmbackup actif"
return 0
fi
if echo "$HB_PS" | grep -v grep | grep -q 'synolocalbkp.*--backup'; then
log "HB détecté: synolocalbkp actif"
return 0
fi
if echo "$HB_PS" | grep -v grep | grep -q 'img_backup'; then
log "HB détecté: img_backup actif"
return 0
fi
return 1
}
# ===========================================================================
# Vérification Hyper Backup — skip si actif
# ===========================================================================
check_hyperbackup() {
if is_hyperbackup_running; then
log "SKIP Hyper Backup en cours, rsnapshot reporté à demain"
exit 0
fi
}
# ===========================================================================
# Lock — évite 2 instances en parallèle
# ===========================================================================
LOCKDIR="/volume1/NetBackup/rsnapshot.lock"
LOCKPID="$LOCKDIR/pid"
acquire_lock() {
# Vérifier le lockfile interne de rsnapshot (prioritaire)
RSNAPSHOT_PID="/volume1/NetBackup/.rsnapshot.pid"
if [ -f "$RSNAPSHOT_PID" ]; then
RS_PID=$(cat "$RSNAPSHOT_PID" 2>/dev/null || echo "")
if [ -n "$RS_PID" ] && kill -0 "$RS_PID" 2>/dev/null; then
log "SKIP rsnapshot déjà en cours via .rsnapshot.pid (pid=$RS_PID)"
exit 0
else
log "WARN .rsnapshot.pid orphelin (pid=$RS_PID), nettoyage"
rm -f "$RSNAPSHOT_PID"
fi
fi
# Lock du wrapper
if ! mkdir "$LOCKDIR" 2>/dev/null; then
if [ -f "$LOCKPID" ] && kill -0 "$(cat "$LOCKPID")" 2>/dev/null; then
log "SKIP wrapper déjà en cours (pid=$(cat "$LOCKPID"))"
exit 0
else
log "WARN lock wrapper orphelin, nettoyage"
rm -rf "$LOCKDIR"
mkdir "$LOCKDIR"
fi
fi
echo $$ > "$LOCKPID"
trap 'rm -rf "$LOCKDIR" 2>/dev/null || true' EXIT
}
# ===========================================================================
# Vérification du conteneur Docker
# ===========================================================================
check_docker() {
if [ ! -x "$DOCKER" ]; then
log "ERROR docker introuvable: $DOCKER"
exit 1
fi
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é"
"$DOCKER" ps --format 'table {{.Names}}\t{{.Status}}' 2>/dev/null | tee -a "$LOG" || true
exit 1
fi
}
# ===========================================================================
# Exécution d'un niveau rsnapshot
# ===========================================================================
run_level() {
LEVEL="$1"
log "RUN rsnapshot $LEVEL"
START_TIME=$(date +%s)
nice -n 19 "$DOCKER" exec "$CONTAINER" rsnapshot "$LEVEL" >>"$LOG" 2>&1
RC=$?
END_TIME=$(date +%s)
DURATION=$(( END_TIME - START_TIME ))
DURATION_MIN=$(( DURATION / 60 ))
if [ "$RC" -eq 0 ]; then
log "OK rsnapshot $LEVEL terminé en ${DURATION_MIN}min (rc=$RC)"
else
log "ERROR rsnapshot $LEVEL échoué en ${DURATION_MIN}min (rc=$RC)"
fi
return "$RC"
}
# ===========================================================================
# Mode AUTO — enchaîne les niveaux dans le bon ordre
# Ordre obligatoire rsnapshot : monthly -> weekly -> daily -> hourly
# ===========================================================================
run_auto() {
DAY_OF_MONTH=$(date +%d)
DAY_OF_WEEK=$(date +%u) # 1=lundi ... 7=dimanche
ERRORS=0
log "AUTO jour_mois=$DAY_OF_MONTH jour_semaine=$DAY_OF_WEEK"
# 1. Monthly (1er du mois uniquement)
if [ "$DAY_OF_MONTH" = "01" ]; then
log "AUTO -> monthly (1er du mois)"
run_level monthly || ERRORS=$((ERRORS + 1))
fi
# 2. Weekly (dimanche uniquement)
if [ "$DAY_OF_WEEK" = "7" ]; then
log "AUTO -> weekly (dimanche)"
run_level weekly || ERRORS=$((ERRORS + 1))
fi
# 3. Daily (tous les jours)
log "AUTO -> daily"
run_level daily || ERRORS=$((ERRORS + 1))
# 4. Hourly — le vrai rsync (tous les jours)
log "AUTO -> hourly"
run_level hourly || ERRORS=$((ERRORS + 1))
return "$ERRORS"
}
# ===========================================================================
# Main
# ===========================================================================
case "$MODE" in
auto|hourly|daily|weekly|monthly) ;;
*)
log "ERROR mode invalide: '$MODE'"
exit 1
;;
esac
rotate_log
log "===== START mode=$MODE user=$(id -un) uid=$(id -u) ====="
check_docker
acquire_lock
check_hyperbackup
if [ "$MODE" = "auto" ]; then
run_auto
RC=$?
else
run_level "$MODE"
RC=$?
fi
log "===== END mode=$MODE rc=$RC ====="
exit "$RC"
Fonctionnalités du wrapper :
- Mode
auto— enchaîne automatiquement les niveaux dans le bon ordre
(monthly → weekly → daily → hourly) selon le jour - Skip Hyper Backup — détecte les processus DSM 7 (
dsmbackup,
synolocalbkp) viaps + grep(carpgrepn'existe pas sur DSM) - Double lock — vérifie le lockfile interne de rsnapshot (
.rsnapshot.pid)
et un lock wrapper, avec nettoyage des orphelins - Rotation du log — tronque à 10 000 lignes quand le fichier dépasse 50 000 lignes
- Chronomètre — affiche la durée de chaque niveau dans le log
- 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âche planifiée unique
DSM → Panneau de configuration → Planificateur de tâches →
créer une seule tâche planifiée (Script défini par l'utilisateur) :
| Tâche | Fréquence | Commande |
|---|---|---|
| Rsnapshot Auto | Tous les jours à 01:00 | /bin/sh /volume1/NetBackup/rsnapshot_wrapper.sh auto |
Le mode auto détermine automatiquement quels niveaux exécuter :
- Tous les jours :
daily+hourly(le vrai rsync) - Le dimanche :
weekly+daily+hourly - Le 1er du mois :
monthly+ (weekly si dimanche) +daily+hourly
monthly → weekly → daily → hourly.Avec l'ancienne méthode (3 tâches séparées), l'ordre dépendait de la planification et pouvait causer
des rotations perdues. Le mode
auto élimine ce risque.
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 :
# 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 ou un autre volume), 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.
Ce que gère le wrapper :
- Si Hyper Backup est détecté au lancement de rsnapshot, celui-ci est
reporté au lendemain (SKIP Hyper Backup en coursdans le log) - Détection via
ps + grep(carpgrepn'existe pas sur DSM) sur les processus
dsmbackup,synolocalbkp --backupetimg_backup - Le daemon permanent
synobackupdest ignoré (toujours actif, même sans backup)
Ce que le wrapper ne gère pas :
- L'inverse : Hyper Backup ne sait pas que rsnapshot tourne. Il faut donc
planifier Hyper Backup avec une marge suffisante après rsnapshot.
Planification recommandée :
| Tâche | Fréquence | Heure |
|---|---|---|
| Rsnapshot Auto | Tous les jours | 01:00 |
| Hyper Backup | Samedi | 15:00 |
rsnapshot termine vers ~03:00. Hyper Backup démarre le samedi à 15:00 avec
12 heures de marge. Si le backup rsnapshot dure exceptionnellement longtemps,
le lock persistant empêche un double lancement le lendemain.
hardlinks de rsnapshot — chaque snapshot apparaît comme une copie complète.
Il est donc recommandé de configurer Hyper Backup pour sauvegarder
uniquement
hourly.0 (le snapshot le plus récent)et d'exclure les autres dossiers (
hourly.1-N, daily.*,weekly.*, monthly.*). Cela réduit considérablementla taille et la durée du backup Hyper Backup.
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