rsync + cron : synchronisation incrémentale entre serveurs

rsync + cron : synchronisation incrémentale entre serveurs

Synchronisez des fichiers entre serveurs avec rsync : copies incrémentales, sur SSH, avec exclusions, et planification par cron. Idéal pour répliquer un site, des médias, des sauvegardes entre VPS sans solution lourde.

Introduction

rsync est l'outil Unix de référence pour synchroniser des fichiers entre deux locations (local↔local, local↔remote, remote↔remote). Ses atouts :

  • Incrémental : ne transfère que ce qui a changé
  • Algorithme delta : calcule les différences à l'octet près
  • Préservation des permissions, timestamps, liens symboliques
  • SSH-friendly : sécurise les transferts entre serveurs
  • Battle-tested : présent depuis 25+ ans, ultra fiable

Cas d'usage typiques :

  • Copier /var/www d'un VPS web vers un VPS de secours toutes les heures
  • Sync /home vers un NAS de backup
  • Répliquer des médias entre serveurs CDN
  • Pousser un site statique vers un serveur de prod

Prérequis

  • 2 serveurs Linux (source + destination)
  • SSH actif et clé publique pré-installée sur la destination
  • rsync installé (présent partout par défaut)

Étape 1 : Vérifier rsync

rsync --version

Si manquant :

sudo apt install -y rsync

À installer sur les deux serveurs (la même version idéalement).

Étape 2 : Premier sync

Syntaxe générale

rsync [options] source destination

Sync local → distant

rsync -avz /var/www/site/ user@remote-server:/var/www/site/

Décomposé :

  • -a : mode archive (préserve permissions, timestamps, symlinks, etc.)
  • -v : verbose
  • -z : compression pendant le transfert (économise la bande passante)
  • source/ (avec slash final) : copie le contenu du dossier
  • source (sans slash) : copie le dossier lui-même

⚠️ Attention au slash final : c'est la source d'erreurs n°1 avec rsync.

# Ces 2 commandes ont des résultats DIFFÉRENTS
rsync -av /var/www/site/ /backup/    # copie le contenu de site/ dans /backup/
rsync -av /var/www/site  /backup/    # copie /backup/site/...

Sync distant → local

rsync -avz user@remote-server:/var/www/site/ /local-backup/

Étape 3 : Options essentielles

# Mode test (dry run) - simule sans transférer
rsync -avzn source/ destination/

# Supprimer côté destination ce qui n'existe plus en source
rsync -avz --delete source/ destination/

# Progress bar pour gros transferts
rsync -avzP source/ destination/   # --progress + --partial

# Limiter la bande passante (KB/s)
rsync -avz --bwlimit=10000 source/ destination/   # 10 MB/s

# Exclusions
rsync -avz --exclude='*.log' --exclude='cache/' source/ destination/

# Exclusions depuis un fichier
rsync -avz --exclude-from='/etc/rsync-exclude.txt' source/ destination/

# Conserver les liens hardlinks
rsync -avzH source/ destination/

# Spécifier un port SSH non standard
rsync -avz -e "ssh -p 22022" source/ user@host:dest/

Étape 4 : Fichier d'exclusions réutilisable

sudo nano /etc/rsync-exclude.txt
# Caches
*.cache
*~
.DS_Store
Thumbs.db
__pycache__/
node_modules/
.git/

# Logs
*.log
*.gz
*.bz2

# Dossiers à exclure
tmp/
cache/
sessions/

Utilisation :

rsync -avz --exclude-from=/etc/rsync-exclude.txt /var/www/ user@dst:/var/www/

Étape 5 : Sync via SSH avec clé

Vérifiez que SSH par clé fonctionne :

ssh user@remote-server "echo OK"
# Doit retourner OK sans demander de mot de passe

Si SSH demande un mot de passe, voir le tuto Configuration SSH par clé sur verycloud.fr.

Pour un cron, on utilise toujours une clé SSH dédiée sans passphrase (sinon le cron ne peut pas s'authentifier).

# Sur le source
sudo ssh-keygen -t ed25519 -N "" -f /root/.ssh/id_rsync

Copier la clé publique vers la destination :

sudo ssh-copy-id -i /root/.ssh/id_rsync.pub user@remote-server

Étape 6 : Restreindre la clé à rsync uniquement (sécurité)

Sur la destination, dans ~/.ssh/authorized_keys, préfixez la clé :

command="rsync --server -vlogDtprze.iLsfxC . /backup/",restrict ssh-ed25519 AAAA... rsync@source

Cette clé ne peut qu'exécuter rsync vers /backup/, rien d'autre. Si quelqu'un vole la clé, il ne peut pas obtenir un shell.

Pour générer la commande exacte qu'attend rsync :

# Sur la source, ajoutez -e "ssh -v" pour voir la commande lancée
rsync -avz -e "ssh -v -i /root/.ssh/id_rsync" /backup/ user@host:/backup/

Regardez Sending command:. Copiez-collez dans le command="...".

Étape 7 : Script de sync robuste

sudo nano /usr/local/bin/sync-prod-to-backup.sh
#!/bin/bash
set -e  # arrêter en cas d'erreur

# Configuration
SOURCE_DIR="/var/www/"
DEST_HOST="backup-vps"
DEST_USER="rsync"
DEST_DIR="/backup/www/"
SSH_KEY="/root/.ssh/id_rsync"
SSH_PORT="22"
LOG_FILE="/var/log/rsync-sync.log"
EXCLUDE_FILE="/etc/rsync-exclude.txt"

# Logging
exec >> "$LOG_FILE" 2>&1
echo "========================================"
echo "Sync démarré: $(date)"
echo "========================================"

# Lock pour éviter les overlaps
LOCKFILE="/tmp/rsync-sync.lock"
exec 200>$LOCKFILE
if ! flock -n 200; then
    echo "Sync déjà en cours, skip"
    exit 1
fi

# Sync
rsync -avz \
    --delete \
    --exclude-from="$EXCLUDE_FILE" \
    --bwlimit=20000 \
    -e "ssh -p $SSH_PORT -i $SSH_KEY -o StrictHostKeyChecking=accept-new" \
    "$SOURCE_DIR" \
    "$DEST_USER@$DEST_HOST:$DEST_DIR"

EXIT_CODE=$?

if [ $EXIT_CODE -eq 0 ]; then
    echo "Sync OK: $(date)"
else
    echo "Sync KO (exit $EXIT_CODE): $(date)"
    # Notification Discord en cas d'échec
    curl -s -X POST -H "Content-Type: application/json" \
        -d "{\"content\": \"❌ Sync $SOURCE_DIR échoué (exit $EXIT_CODE)\"}" \
        "https://discord.com/api/webhooks/VOTRE_WEBHOOK"
fi

echo ""
exit $EXIT_CODE
sudo chmod +x /usr/local/bin/sync-prod-to-backup.sh

Testez manuellement :

sudo /usr/local/bin/sync-prod-to-backup.sh

Vérifiez le log :

sudo tail -50 /var/log/rsync-sync.log

Étape 8 : Planifier via cron

sudo crontab -e

Exemples typiques :

# Toutes les heures à la minute 5
5 * * * * /usr/local/bin/sync-prod-to-backup.sh

# Toutes les 10 minutes
*/10 * * * * /usr/local/bin/sync-prod-to-backup.sh

# Tous les jours à 2h du matin
0 2 * * * /usr/local/bin/sync-prod-to-backup.sh

# Mon-Fri à 6h
0 6 * * 1-5 /usr/local/bin/sync-prod-to-backup.sh

Étape 9 : Rotation et versioning (rsync + hard links)

Pour garder N versions des sauvegardes sans dupliquer l'espace, utilisez --link-dest :

#!/bin/bash
TODAY=$(date +%Y-%m-%d)
YESTERDAY=$(date -d "yesterday" +%Y-%m-%d)
BACKUP_ROOT="/backup/www"

# Si backup d'hier existe, utilisez ses fichiers comme base hardlink
if [ -d "$BACKUP_ROOT/$YESTERDAY" ]; then
    LINK_OPT="--link-dest=$BACKUP_ROOT/$YESTERDAY"
else
    LINK_OPT=""
fi

rsync -avz --delete $LINK_OPT \
    -e "ssh -p 22 -i /root/.ssh/id_rsync" \
    rsync@prod-vps:/var/www/ \
    "$BACKUP_ROOT/$TODAY/"

# Rotation : garder 14 jours
find $BACKUP_ROOT -maxdepth 1 -type d -mtime +14 -exec rm -rf {} +

Avec --link-dest, les fichiers identiques entre 2026-05-15 et 2026-05-16 sont des hardlinks → un seul espace disque utilisé. Vous pouvez restaurer n'importe quelle date.

Étape 10 : Sync bidirectionnel ?

rsync est unidirectionnel par nature. Pour du bidirectionnel, utilisez Unison (mais plus complexe) ou Syncthing (mesh sync zero-config).

⚠️ Lancer rsync dans les deux sens via cron est dangereux : conflits, écrasements involontaires. À éviter.

Étape 11 : Optimisation perf

Désactiver la compression sur LAN gigabit

-z (compression) coûte du CPU. Sur un lien rapide, c'est contre-productif :

rsync -av --no-compress source/ dst/

Utiliser le chiffrement SSH le plus rapide

rsync -av -e "ssh -c [email protected]" source/ user@host:dst/

Sync depuis snapshot LVM (cohérence)

Si vous synchronisez une DB en cours d'écriture, vous risquez l'incohérence. Snapshot LVM :

sudo lvcreate -L 10G -s -n var-snapshot /dev/vg0/var
sudo mount /dev/vg0/var-snapshot /mnt/snapshot

rsync -av /mnt/snapshot/lib/mysql/ user@host:/backup/mysql/

sudo umount /mnt/snapshot
sudo lvremove -f /dev/vg0/var-snapshot

Parallel rsync (gros volumes)

Pour synchroniser un dossier avec 100k+ fichiers, lancer plusieurs rsync en parallèle est plus rapide :

# Listing premier
ls /var/www | xargs -I {} -P 4 rsync -avz /var/www/{}/ user@host:/var/www/{}/

Étape 12 : Test et monitoring

Vérifier ce qui changerait sans rien faire

sudo rsync -avzn --delete --exclude-from=/etc/rsync-exclude.txt /var/www/ user@host:/var/www/

Affiche tous les fichiers qui seraient transférés/supprimés. Utile pour valider une nouvelle config.

Mesurer le temps et la quantité

time rsync -avz --stats source/ dst/

--stats ajoute en fin :

Total bytes sent: 1.2M
Total bytes received: 154
Speed: 8.5MB/s
File list size: 12345
Reduction: 95% (delta efficacement)

Alerter si le sync ne tourne pas

Combinez avec healthchecks.io (gratuit) :

5 * * * * /usr/local/bin/sync-prod-to-backup.sh && curl -fsS https://hc-ping.com/VOTRE-UUID

Si le sync échoue ou si le cron n'est pas exécuté à temps, healthchecks.io vous alerte par mail/Discord/Slack.

Dépannage

"Permission denied (publickey)"

La clé SSH n'est pas reconnue. Vérifiez :

sudo ssh -i /root/.ssh/id_rsync user@remote-host echo OK

Si ça demande un mot de passe, la clé publique n'est pas dans authorized_keys de la destination.

"rsync: failed to set times: Operation not permitted"

L'user de destination n'a pas le droit de modifier les timestamps. Soit utilisez root, soit ajoutez --no-times à rsync.

Sync très lent

Causes possibles :

  • Bcp de petits fichiers → CPU saturé sur le file scan
  • Compression -z sur lien rapide
  • Lien réseau bas débit (utilisez --bwlimit plus haut)

Vérifiez le throughput :

rsync -avzP source/ dst/ | grep "speed"

"rsync error: protocol incompatibility"

Versions différentes côté source/destination. Mettez à jour rsync des deux côtés.

"vanished file" warning

Le fichier a disparu (été supprimé) pendant le sync. Pas dramatique sur des fichiers temporaires. Pour ignorer :

rsync ... 2>/dev/null || true

Commandes utiles

# Voir ce qui sera transféré (dry run)
rsync -avzn --stats source/ dst/

# Sync avec uniquement les fichiers récents
rsync -avz --max-age=30 source/ dst/

# Sync vers plusieurs destinations
for host in backup1 backup2 backup3; do
    rsync -avz source/ user@$host:dst/ &
done
wait

# Vérifier qu'un sync est complet (intégrité)
rsync -avc source/ dst/    # -c utilise le checksum, plus lent mais sûr

# Mesurer la différence avec --dry-run + --stats
rsync -avzn --stats source/ dst/ 2>&1 | grep -E "Total|sent|received"

# Voir le PID du rsync en cours
pgrep -af rsync

# Tuer un rsync coincé
pkill -f rsync

Conclusion

rsync + cron est la solution la plus simple et la plus fiable pour synchroniser des fichiers entre serveurs. Avantages :

  • Zéro dépendance (rsync + SSH, partout)
  • Incrémental → léger sur la bande passante
  • Reprise sur erreur native
  • Hardlinks pour rotation versionnée sans duplication

Pour aller plus loin :

  • Restic pour des backups chiffrés et déduplicatés (voir tuto dédié)
  • Syncthing pour du sync mesh bidirectionnel
  • lsyncd pour du sync en temps réel (inotify)
  • rclone pour syncer vers du cloud (B2, S3, OneDrive, etc.)

Ressources

Join our Discord community server

For any questions, suggestions, or just to chat with the community, join us on Discord!

900+Members