Skip to content

Transférer des fichiers (rsync)

rsync est l'outil de référence pour synchroniser des fichiers entre deux machines via SSH. C'est ce qu'on utilise pour:

  • Migrer des données entre serveurs (anciens => nouveaux)
  • Synchroniser des dossiers de manière incrémentale (ne re-transfère que les diffs)
  • Faire des backups rapides

Pourquoi pas scp / curl / gokapi ?

scp ne reprend pas les transferts interrompus. curl et gokapi ont un overhead HTTP énorme (testé: 7 Mb/s pour 250 GB, intenable). rsync over SSH sature le lien réel et reprend automatiquement où il s'est arrêté.

Syntaxe de base

rsync [options] SOURCE DESTINATION

Le premier argument est la source, le deuxième la destination. La direction se détermine en fonction d'où se trouve user@host: dans la commande.

CasCommandeRésultat
Pull (depuis remote vers local)rsync user@remote:/path/ /local/Récupère depuis remote
Push (depuis local vers remote)rsync /local/ user@remote:/path/Envoie vers remote
Local => localrsync /a/ /b/Copie sur la même machine

Le slash final est CRUCIAL

  • /path/source/ (avec slash) => copie le contenu de source/ dans la destination
  • /path/source (sans slash) => copie source comme un sous-dossier dans la destination
bash
# Bon
rsync /data/images/ remote:/backup/images/    # => /backup/images/<fichiers>

# Crée un niveau de trop
rsync /data/images remote:/backup/images/     # => /backup/images/images/<fichiers>

Options à toujours utiliser

OptionRôle
-aMode archive: préserve perms, timestamps, symlinks, ownership
-vVerbose, montre les fichiers transférés
--progressBarre de progression par fichier
--partialGarde les fichiers partiellement transférés en cas d'interruption
--inplaceÉcrit directement dans le fichier final (pas de copie temp)

Pourquoi --partial --inplace

Sans ces flags, un fichier interrompu est jeté à la poubelle, et la prochaine reprise recopie tout. Avec --inplace, rsync reprend au byte exact où il s'est arrêté. Indispensable pour les gros fichiers (vidéos, archives).

Pour les gros transferts (>10 GB)

bash
rsync -av --progress --partial --inplace \
  -e "ssh -T -o Compression=no" \
  /source/ user@remote:/dest/
Option SSHRôle
-TDésactive l'allocation tty (overhead inutile)
-o Compression=noNe compresse pas (les médias/zip sont déjà compressés, perte sèche CPU)

Compression utile dans certains cas

Si tu transfères du texte (logs, dumps SQL, code source) sur un lien lent, garde la compression SSH activée - voire ajoute -z à rsync. Mais pour des médias (jpg, png, mp4, pdf), la compression ralentit sans rien apporter.

Cas 1 - Sans jumphost (le plus simple)

Tu as accès SSH direct entre les deux machines (port 22 ouvert dans le SG, clé autorisée).

Pull (depuis le serveur de destination)

bash
rsync -av --progress --partial --inplace \
  -e "ssh -T -o Compression=no" \
  user@source-host:/data/source/ \
  /data/dest/

Push (depuis le serveur source)

bash
rsync -av --progress --partial --inplace \
  -e "ssh -T -o Compression=no" \
  /data/source/ \
  user@dest-host:/data/dest/

Pull ou push ?

  • Pull est généralement préféré: tu opères depuis le nouveau serveur où tu fais déjà ton setup.
  • Push est obligatoire quand le serveur source a une whitelist d'IP en entrée (ne peut pas être atteint depuis le nouveau).

Cas 2 - Avec jumphost (ProxyJump)

Quand le serveur destination est dans un sous-réseau privé accessible uniquement via un bastion SSH.

Architecture typique chez Spektrum

[Source] ──ssh──> [Bastion NGINX] ──ssh──> [Apps-Prod / DB-Prod / etc.]
                  IP publique               IP interne

Commande directe avec -J

bash
rsync -av --progress --partial --inplace \
  -e "ssh -T -J user@bastion-host -o Compression=no" \
  /data/source/ \
  user@dest-host:/data/dest/

Authentification sur les deux machines

Ta clé SSH doit être autorisée dans ~/.ssh/authorized_keys du bastion ET dans celui de la destination. Le bastion ne fait que router la connexion, il ne masque pas l'auth de la cible.

Cas 3 - Avec SSH config (recommandé pour usage régulier)

Plutôt que répéter -e "ssh -J ..." à chaque commande, déclare les hosts dans ~/.ssh/config:

Host bastion-prod
    HostName 83.228.201.182
    User migration
    IdentityFile ~/.ssh/id_ed25519

Host apps-prod
    HostName 89.47.48.11
    User migration
    IdentityFile ~/.ssh/id_ed25519
    ProxyJump bastion-prod

Host db-prod
    HostName 89.47.48.14
    User migration
    IdentityFile ~/.ssh/id_ed25519
    ProxyJump bastion-prod

Permissions du fichier

~/.ssh/config doit être en mode 600 et appartenir à ton user - sans sudo.

bash
chmod 600 ~/.ssh/config

Si tu fais sudo chmod, le fichier passe en owner root et SSH ne peut plus le lire. Pour réparer:

bash
sudo chown <user>:<user> ~/.ssh/config

Avec ça, rsync devient ultra simple:

bash
# Push vers apps-prod via bastion-prod (jumphost configuré dans le config)
rsync -av --progress --partial --inplace \
  -e "ssh -T -o Compression=no" \
  /data/source/ \
  apps-prod:/data/dest/

Tu peux aussi tester l'accès en une commande:

bash
ssh apps-prod 'echo OK && df -h /data'

Cas 4 - Migration avec clé temporaire

Pour une migration ponctuelle où tu ne veux pas exposer ta clé personnelle, génère une clé dédiée:

1. Générer la clé sur la source

bash
ssh-keygen -t ed25519 -f ~/.ssh/migration_tmp -N "" -C "migration-tmp-$(date +%Y%m%d)"
cat ~/.ssh/migration_tmp.pub

2. Autoriser la pubkey sur les deux machines

Sur le bastion et sur la destination, ajouter la pubkey à ~/.ssh/authorized_keys du user qui fera le transfert (migration typiquement).

bash
echo "<contenu de migration_tmp.pub>" >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys

Ou via ssh-copy-id chaîné:

bash
ssh-copy-id -i ~/.ssh/migration_tmp.pub user@bastion
ssh-copy-id -i ~/.ssh/migration_tmp.pub -o "ProxyJump user@bastion" user@dest

3. Tester avant le gros transfert

bash
ssh migration-apps-prod 'echo OK && df -h /data && touch /data/dest/.test && rm /data/dest/.test && echo writable'

4. Permissions destination

Le user de transfert doit pouvoir écrire dans le dossier cible. Depuis un user avec sudo sur la destination:

bash
sudo mkdir -p /data/<projet>/images
sudo chown -R migration:spek /data/<projet>/images

Groupe spek chez Spektrum

Le user migration n'a pas de groupe propre - utilise spek (groupe partagé) ou la syntaxe migration: (= groupe primaire). migration:migration échoue avec invalid group.

5. Cleanup après la migration

Sur la source:

bash
rm ~/.ssh/migration_tmp ~/.ssh/migration_tmp.pub
# nettoyer les blocs Host migration-* dans ~/.ssh/config

Sur le bastion + destination:

bash
sed -i '/migration-tmp-/d' ~/.ssh/authorized_keys

Sur la destination, repasser l'owner du dossier au user qui exploite l'app:

bash
sudo chown -R clement:spek /data/<projet>/images

Résister aux coupures de session - tmux

Une connexion SSH coupée ⇒ le rsync s'arrête. Pour des transferts de plusieurs heures, toujours lancer dans tmux:

bash
# 1. Créer une session
tmux new -s migration

# 2. Lancer rsync dedans (commande normale)
rsync -av --progress --partial --inplace -e "ssh -T -o Compression=no" /source/ apps-prod:/dest/

# 3. Détacher: Ctrl+B puis D
#    La session continue de tourner en background

# 4. Réattacher plus tard depuis n'importe quelle session SSH
tmux attach -t migration

Tabby intercepte Ctrl+B

Tabby (et certains autres terminaux) capturent Ctrl+B pour leur propre usage. Si Ctrl+B + D ne marche pas, détache depuis une autre session SSH:

bash
tmux detach-client -s migration   # -s = session, pas -t qui cherche un client

Ou rebind tmux dans ~/.tmux.conf:

unbind C-b
set -g prefix C-a
bind C-a send-prefix

Suivi du transfert détaché

Trois façons de regarder l'avancement sans interrompre:

Snapshot du pane tmux

bash
tmux capture-pane -t migration -p | tail -30

En continu (rafraîchi toutes les 5s):

bash
watch -n 5 'tmux capture-pane -t migration -p | tail -20'

Comparaison des tailles

bash
# Source
du -sh /data/source/

# Destination via SSH
ssh apps-prod 'du -sh /data/dest/'

Le ratio te donne le pourcentage transféré.

Bande passante temps réel

bash
sudo apt install -y iftop
sudo iftop -i eth0

Filtre par IP de destination si besoin pour ne voir que ce trafic.

Vérification d'intégrité après transfert

bash
# Compter les fichiers et tailles des deux côtés
find /data/source/ -type f | wc -l
du -sh /data/source/

ssh apps-prod 'find /data/dest/ -type f | wc -l && du -sh /data/dest/'

Les compteurs et tailles doivent correspondre. Si pas exact, relance simplement le rsync - il est idempotent et ne re-transfère que ce qui manque ou diffère.

Vérification de checksums

Pour une vérification stricte (lente), ajoute --checksum au rsync:

bash
rsync -avc --progress source/ dest/

Ça compare les hash de chaque fichier au lieu de juste taille+mtime. À utiliser avec parcimonie sur de gros volumes.

Options avancées

Exclure des fichiers / dossiers

bash
rsync -av --progress \
  --exclude '*.tmp' \
  --exclude 'cache/' \
  --exclude '.git/' \
  /source/ user@remote:/dest/

Ou via un fichier d'exclusion:

bash
echo -e "*.tmp\ncache/\n.git/" > /tmp/excludes.txt

rsync -av --exclude-from=/tmp/excludes.txt /source/ user@remote:/dest/

Synchronisation en miroir (--delete)

bash
rsync -av --delete /source/ user@remote:/dest/

--delete supprime côté destination les fichiers qui n'existent plus côté source. Dangereux - ne pas combiner avec un mauvais slash final, tu pourrais effacer trop.

Toujours tester avec --dry-run

Avant un rsync avec --delete, fais un essai à blanc:

bash
rsync -avn --delete /source/ user@remote:/dest/

Le -n (--dry-run) montre ce qui serait fait sans rien modifier.

Limiter la bande passante

Pour ne pas saturer le lien (utile en heures ouvrées sur lien partagé):

bash
rsync -av --bwlimit=10000 /source/ user@remote:/dest/

--bwlimit=10000 limite à ~10 MB/s.

Parallélisation pour saturer un gros lien

rsync est mono-thread. Pour saturer un lien 1 Gbps+, parallelise par sous-dossier:

bash
ssh source-host 'ls /data/source/' | \
  parallel -j 4 rsync -av --partial --inplace \
    -e "ssh -T -o Compression=no" \
    source-host:/data/source/{} /data/dest/{}

-j 4 = 4 transferts simultanés.

Contributors

The avatar of contributor named as Clément Favre Clément Favre

Changelog