Aller au contenu
Administration Linux medium

Exécuter des commandes distantes via SSH

30 min de lecture

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 un objectif LFCS (domaine Networking, "Use SSH in automation and scripting").

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
# Lire les 5 dernières lignes des logs d'authentification
ssh bob@192.168.1.10 "tail -5 /var/log/auth.log"
# Vérifier l'espace disque
ssh bob@192.168.1.10 "df -h /"
# Voir les processus qui tournent
ssh bob@192.168.1.10 "ps aux | grep 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, vous obtenez : sudo: a terminal is required to read the password

Le script local est envoyé à un bash privilégié qui le lit sur son entrée standard, sans jamais être écrit sur le disque distant.

Fenêtre de terminal
# Passer un script à exécuter avec sudo
cat script_local.sh | ssh -t bob@192.168.1.10 "sudo bash"

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. Attention toutefois : ssh consomme l'entrée standard de la boucle, ce qui interrompt la lecture du fichier dès le premier hôte. L'option -n de ssh, ou une redirection vers un autre descripteur, est nécessaire dès que la commande distante lit stdin.

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

Produire du CSV rend la sortie exploitable par un tableur ou un script d'analyse. Les \$ échappés sont indispensables : ils empêchent votre shell local de résoudre les variables awk avant l'envoi, alors qu'elles n'ont de sens que sur le serveur distant.

Fenêtre de terminal
echo "HOST,UPTIME,MEMORY_FREE,DISK_FREE"
for host in web1 web2 web3; do
ssh bob@$host "echo -n '${host},'; uptime | awk '{print \$(NF-2)}' | tr -d ','; echo -n ','; free -m | awk '/Mem/{print \$4}'; df -h / | awk 'NR==2{print \$4}'"
done

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
La commande s'exécute localementGuillemets oubliés autour de la commandeEntourer la commande de "..."
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
Commande bloquée sans finProgramme distant attend une entréeAjouter -n pour désactiver stdin : ssh -n ...

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

  • ssh user@host "commande" exécute sans ouvrir de session, la commande doit être entre guillemets
  • -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
  • Pour du batch à grande échelle, ControlMaster ou Ansible sont plus adaptés que les boucles for

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn