Aller au contenu
English
Compte : non connecté, synchronisation possible Mon apprentissage
Selectionner le thème
English

Exécuter des commandes distantes via SSH

Par mis à jour le

Améliorer cette page

Une connexion SSH n'implique pas d'ouvrir un shell interactif. En passant une commande directement à ssh, vous pouvez interroger, modifier, redémarrer des services ou collecter des informations sur des dizaines de serveurs sans jamais ouvrir un terminal distant.

C'est la base de l'automatisation système, et la suite directe de l'objectif RHCSA « Access remote systems using SSH ». Les sorties de cette page ont été mesurées sur Debian 13 avec OpenSSH 10.0, trois alias d'hôtes pointant sur un sshd de test.

Cette technique s'utilise dès que vous devez agir sur un serveur distant sans ouvrir de session interactive : vérifier l'espace disque après un déploiement, redémarrer un service, collecter des métriques sur plusieurs machines à la fois, ou exécuter un script de provisionnement sans le copier au préalable. C'est aussi le fondement de l'automatisation avec des outils comme Ansible, qui ne fait rien d'autre qu'encapsuler des commandes SSH.


  • Lancer une commande sur un serveur sans session interactive
  • Combiner ssh avec des pipes pour traiter les données à la volée
  • Exécuter des commandes qui demandent sudo à distance
  • Écrire une boucle multi-serveurs pour des opérations batch
  • Passer des scripts locaux à un serveur distant via stdin
  • Diagnostiquer les cas où la commande distante échoue silencieusement

La syntaxe de base est ssh [options] user@hôte "commande". Dès qu'un argument suit le nom d'hôte, ssh cesse d'ouvrir un shell interactif : il exécute la commande, renvoie sa sortie et referme la connexion. Le shell distant est alors lancé en mode non interactif, ce qui a une conséquence pratique importante : ni ~/.bashrc ni le prompt ne sont chargés, et le PATH peut différer de celui d'une session ordinaire.

Fenêtre de terminal
# Version du noyau distant
ssh bob@192.168.1.10 "uname -r"
# Vérifier l'espace disque
ssh bob@192.168.1.10 "df -h /"
# Voir les processus nginx, sans que la recherche se compte elle-même
ssh bob@192.168.1.10 "pgrep -a nginx"

Le résultat s'affiche directement dans votre terminal local. Pas de session, pas de prompt distant.


ssh lit son entrée standard et écrit sur sa sortie standard comme n'importe quelle commande : il s'insère donc au milieu d'un pipeline shell ordinaire. Toute la difficulté tient à savoir de quel côté chaque morceau du pipeline s'exécute. Ce qui est à l'intérieur des guillemets tourne sur le serveur, ce qui est à l'extérieur tourne sur votre machine.

Le grep est ici placé après le pipe, donc hors des guillemets : il travaille sur votre machine, sur le flux renvoyé par le serveur.

Fenêtre de terminal
# Récupérer /etc/hosts distant et filtrer les lignes web
ssh bob@192.168.1.10 "cat /etc/hosts" | grep web

Le sens inverse fonctionne aussi : le - de tar czf - écrit l'archive sur la sortie standard, que ssh transmet à l'entrée standard du tar distant. Rien ne touche le disque intermédiaire, ce qui évite d'avoir à prévoir de l'espace pour un fichier temporaire.

Fenêtre de terminal
# Compresser localement et décompresser à distance (alternative à rsync)
tar czf - /data/backups | ssh bob@192.168.1.10 "tar xzf - -C /restore/"

Lire plusieurs fichiers distants avec des wildcards

Section intitulée « Lire plusieurs fichiers distants avec des wildcards »

Un motif comme *.log doit rester protégé par les guillemets : sinon votre shell local tente de le résoudre contre vos propres fichiers, et transmet au serveur soit un chemin faux, soit le motif brut selon les fichiers présents dans le répertoire courant.

Fenêtre de terminal
# Les wildcards doivent être entre guillemets pour être évalués à distance
ssh bob@192.168.1.10 "cat /var/log/nginx/*.log" | grep "500"

L'élévation de privilèges est le premier obstacle rencontré en exécution distante, parce qu'une commande passée à ssh ne dispose par défaut d'aucun terminal. Or beaucoup de configurations sudo refusent de lire un mot de passe autrement que sur un terminal, via l'option requiretty ou simplement parce qu'aucun autre canal de saisie n'existe. Deux réponses existent : allouer un pseudo-terminal, ou se passer complètement de mot de passe.

sudo sur certains systèmes exige un terminal (TTY). L'option -t alloue un pseudo-TTY :

Fenêtre de terminal
ssh -t bob@192.168.1.10 "sudo systemctl restart nginx"

Sans -t, sudo refuse de demander le mot de passe et répond (mesuré sur Debian 13) : sudo: a terminal is required to read the password; either use the -S option to read from standard input or configure an askpass helper.

Envoyer un script local à un bash privilégié ne marche qu'avec NOPASSWD. Quand l'entrée standard de ssh est un tube ou un fichier, elle ne peut pas servir en même temps de terminal : -t n'a plus d'effet, et sudo échoue sur le même message qu'au-dessus (mesuré avec cat script_local.sh | ssh -t ... "sudo bash"). Avec une règle NOPASSWD pour ce compte, la forme correcte est :

Fenêtre de terminal
# Le script local est lu par un bash lancé avec sudo, sans être copié
ssh bob@192.168.1.10 "sudo bash -s" < script_local.sh

Plutôt que de copier un script sur le serveur, passez-le directement via stdin. L'option -s de bash lui demande de lire le script sur son entrée standard au lieu d'attendre un nom de fichier, ce qui permet de conserver les arguments passés en ligne de commande. Aucun fichier n'est déposé sur la machine cible, donc aucun nettoyage à prévoir et aucune trace laissée dans /tmp.

Fenêtre de terminal
# Exécuter un script local sur le serveur distant
ssh bob@192.168.1.10 "bash -s" < mon_script.sh

Avec des arguments :

Fenêtre de terminal
ssh bob@192.168.1.10 "bash -s" < mon_script.sh -- --env production --dry-run

Une boucle shell autour de ssh suffit pour appliquer la même commande à un parc entier, sans installer le moindre outil supplémentaire. Le fonctionnement est séquentiel : chaque hôte attend que le précédent ait terminé, et un serveur injoignable fait patienter toute la boucle jusqu'à l'expiration du délai de connexion. C'est acceptable sur quelques machines, beaucoup moins au-delà.

La liste des hôtes est écrite en dur dans la boucle, et l'echo sert de séparateur visuel : sans lui, les sorties des différents serveurs se confondent.

Fenêtre de terminal
for host in web1 web2 web3 db1; do
echo "=== $host ==="
ssh bob@$host "uptime && df -h /"
done

Sortir la liste dans un fichier évite de modifier le script à chaque changement de parc. Le piège est systématique : ssh lit l'entrée standard de la boucle, donc le reste du fichier, même si la commande distante n'en a pas besoin. Mesuré avec trois hôtes dans servers.txt : sans option, seul le premier est traité ; avec ssh -n, qui branche l'entrée de ssh sur /dev/null, les trois le sont.

Fenêtre de terminal
# servers.txt contient un hôte par ligne
while read -r host; do
echo "=== $host ==="
ssh -n bob@$host "free -h | grep Mem"
done < servers.txt

Produire du CSV rend la sortie exploitable par un tableur ou un script d'analyse, à condition d'écrire une ligne par hôte. Le plus sûr est de construire la ligne entière sur le serveur avec un seul printf, entre guillemets simples : aucune variable n'est alors développée par votre shell local. La charge vient de /proc/loadavg, la mémoire de la colonne available de free, plus parlante que free.

Fenêtre de terminal
echo "HOST,LOAD_1MIN,MEM_AVAILABLE_MB,DISK_FREE"
for host in web1 web2 web3; do
ssh -n bob@$host 'printf "%s,%s,%s,%s\n" "$(hostname)" "$(cut -d" " -f1 /proc/loadavg)" "$(free -m | awk "/Mem/{print \$7}")" "$(df -h / | awk "NR==2{print \$4}")"'
done

Sortie mesurée, une ligne propre par serveur (les trois alias pointaient sur la même machine de test) :

HOST,LOAD_1MIN,MEM_AVAILABLE_MB,DISK_FREE
4161584e2fbc,1.94,24117,264G
4161584e2fbc,1.94,24116,264G

Une version plus courte qui enchaîne echo -n, uptime et free produit en réalité trois lignes par hôte, chaque commande terminant sa sortie par un saut de ligne : c'est le défaut classique de ce genre de collecte.


ssh transmet le code de retour de la commande distante, exploitable directement dans un test shell. Une nuance compte pour écrire des scripts fiables : ssh réserve le code 255 à ses propres erreurs (hôte injoignable, authentification refusée, clé rejetée). Un code 255 ne dit donc rien de la commande demandée, il signale que la connexion elle-même a échoué.

Fenêtre de terminal
if ssh bob@192.168.1.10 "systemctl is-active nginx" > /dev/null 2>&1; then
echo "nginx actif sur 192.168.1.10"
else
echo "ALERTE : nginx inactif sur 192.168.1.10"
fi

Quitter immédiatement si une commande échoue dans une boucle :

Fenêtre de terminal
set -e
for host in web1 web2 web3; do
ssh bob@$host "test -f /var/lock/deploy.lock && exit 1 || true"
done

Une commande distante s'exécute aussi sur une machine que vous ne joignez pas directement. L'option -J (ProxyJump) fait rebondir la connexion par un bastion : le trafic transite chiffré de bout en bout, et le bastion ne voit passer qu'un flux qu'il ne peut pas déchiffrer. Votre clé privée reste sur votre poste, elle n'est jamais copiée sur la machine intermédiaire.

Fenêtre de terminal
# Via ProxyJump en one-liner : commande sur un serveur de prod accessible via bastion
ssh -J bob@bastion.example.com bob@10.0.1.15 "systemctl status app"

Presque tous les échecs d'exécution distante viennent du même malentendu : la commande n'a pas été interprétée là où vous le pensiez, ou l'environnement du shell non interactif diffère de celui de votre session habituelle. Avant de chercher plus loin, vérifiez que la commande est bien entourée de guillemets et testez-la dans une session SSH ouverte à la main.

SymptômeCause probableAction
sudo: a terminal is requiredsudo exige un TTYAjouter -t à la commande ssh, ou NOPASSWD pour un script
La commande s'exécute localementGuillemets oubliés autour de la commandeEntourer la commande de "..."
Une variable a la valeur locale$ entre guillemets doubles est développé localementUtiliser des guillemets simples '...'
bash: commande: not found, hors sessionVariables PATH différentes en mode non-interactifUtiliser le chemin absolu ou bash -l -c "..."
Code de retour 255 inattendu255 est le code d'erreur propre à ssh : la connexion a échoué avant l'exécutionVérifier l'accès réseau, la clé et le nom d'hôte ; un vrai 255 distant est indiscernable
Une boucle while read s'arrête après le premier hôtessh consomme l'entrée standard de la boucleAjouter -n : ssh -n ...
Commande bloquée sans finProgramme distant attend une entréeAjouter -n pour désactiver stdin : ssh -n ...

  • ssh user@host "commande" exécute sans ouvrir de session, la commande doit être entre guillemets
  • Les guillemets doubles protègent | et *, pas $ : guillemets simples pour les variables distantes
  • -t alloue un pseudo-TTY nécessaire pour sudo interactif
  • bash -s < script.sh exécute un script local à distance sans le copier
  • Le code de retour de la commande distante est transmis par SSH, utilisez-le dans vos scripts ; 255 signale un échec de la connexion elle-même
  • Dans une boucle while read, ssh -n est obligatoire, sinon seul le premier hôte est traité
  • Pour du batch à grande échelle, ControlMaster ou Ansible sont plus adaptés que les boucles for

Vérifiez que l'essentiel de ce guide est acquis. Les questions portent uniquement sur ce qui vient d'être expliqué ici.

Contrôle de connaissances

Validez vos connaissances avec ce quiz interactif

6 questions
6 min.
70% requis

Informations

  • Le chronomètre démarre au clic sur Démarrer
  • Questions à choix multiples, vrai/faux et réponses courtes
  • Vous pouvez naviguer entre les questions
  • Les résultats détaillés sont affichés à la fin

Lance le quiz et démarre le chronomètre

Ce projet vous est utile ?

Les guides, formations, quiz et labs restent gratuits. Si vous les utilisez régulièrement, vous pouvez contribuer à leur maintenance et à leur développement.

Devenir membre de soutien (nouvel onglet) Soutenir une fois (nouvel onglet) En savoir plus

Suivez aussi le projet sur LinkedIn