En 1995, Tatu Ylönen était chercheur à l'université d'Helsinki quand un collègue s'est fait sniffer son mot de passe sur le réseau local. Tatu a décidé de régler le problème. Il a écrit SSH en quelques mois, l'a mis en ligne gratuitement, et s'est retrouvé avec 20 000 utilisateurs en deux semaines. Ce n'était pas prévu.
Aujourd'hui SSH tourne sur quasiment tous les serveurs Linux de la planète, protège des milliards de connexions par jour, et la plupart des gens l'utilisent exactement comme on utilisait Telnet avant - en tapant une commande, en croisant les doigts, et en se demandant pourquoi ça ne marche pas.
Ce guide ne va pas vous apprendre à faire ssh user@host. Vous savez déjà. On va parler de ce qu'on fait après.
La config ~/.ssh/config - le truc que personne ne fait
Levez la main si vous tapez ça régulièrement :
ssh -i ~/.ssh/ma_cle_privee -p 2222 -l cyril -L 5432:localhost:5432 mon.serveur.example.com
C'est illisible, impossible à mémoriser, et vous le retapez à chaque fois parce que vous avez oublié de le noter quelque part. Il existe une solution. Elle s'appelle ~/.ssh/config et elle existe depuis 1999.
# ~/.ssh/config
Host rudeops
HostName mon.serveur.example.com
User cyril
Port 2222
IdentityFile ~/.ssh/ma_cle_privee
ServerAliveInterval 60
ServerAliveCountMax 3
Host bastion
HostName bastion.corp.example.com
User ops
IdentityFile ~/.ssh/cle_corp
Host prod-db
HostName 10.0.1.42
User ubuntu
ProxyJump bastion
LocalForward 5432 localhost:5432
Maintenant :
ssh rudeops # connexion complète en 10 caractères
ssh prod-db # tunnel + saut de bastion automatique
ControlMaster et ControlPath - le multiplexage de connexions
SSH négocie TLS à chaque nouvelle connexion vers le même host. Sur un réseau lent ou une authentification à deux facteurs, c'est douloureux. Le multiplexage réutilise une connexion existante pour toutes les sessions suivantes vers le même host :
Host *
ControlMaster auto
ControlPath ~/.ssh/sockets/%r@%h-%p
ControlPersist 600
La première connexion vers un host ouvre le socket de contrôle. Toutes les suivantes - y compris scp, rsync, git push - réutilisent la même connexion chiffrée sans renégocier. ControlPersist 600 garde le socket ouvert 10 minutes après la fermeture du dernier terminal. Créez le dossier avant :
mkdir -p ~/.ssh/sockets
chmod 700 ~/.ssh/sockets
Le gain est immédiat sur des connexions répétées - git push sur une connexion lente passe de 3 secondes à 300ms.
IdentitiesOnly yes - forcer l'usage d'une clé spécifique
Host github-pro
HostName github.com
User git
IdentityFile ~/.ssh/id_github_pro
IdentitiesOnly yes
Sans IdentitiesOnly yes, SSH essaie toutes les clés chargées dans l'agent avant celle spécifiée dans IdentityFile. Avec GitHub qui limite le nombre de tentatives d'authentification, ça peut provoquer des Permission denied mystérieux si vous avez beaucoup de clés dans l'agent. IdentitiesOnly yes force SSH à n'utiliser que la clé explicitement définie.
StrictHostKeyChecking - et quand le désactiver sans se tirer une balle dans le pied
# Pour un host dont l'empreinte change souvent (VM éphémère)
Host *.staging.example.com
StrictHostKeyChecking no
UserKnownHostsFile /dev/null
StrictHostKeyChecking no accepte n'importe quelle empreinte sans demander. UserKnownHostsFile /dev/null ne sauvegarde pas l'empreinte. À réserver aux environnements éphémères où les VMs sont recréées régulièrement et où un man-in-the-middle est improbable. En prod, jamais.
HashKnownHosts yes - hasher les entrées de known_hosts
Host *
HashKnownHosts yes
Par défaut ~/.ssh/known_hosts stocke les noms d'hôtes en clair - un attaquant ayant accès à votre machine sait exactement à quels serveurs vous vous connectez. Avec HashKnownHosts yes, les noms sont hashés. Vous perdez la lisibilité humaine, vous gagnez en confidentialité.
Les permissions du fichier comptent
chmod 600 ~/.ssh/config
chmod 700 ~/.ssh/
Si les permissions sont mauvaises, SSH ignore silencieusement votre config et vous vous demandez pourquoi rien ne fonctionne.
Les clés - au-delà de la génération basique
Ed25519, pas RSA
# Ce qu'on faisait
ssh-keygen -t rsa -b 4096
# Ce qu'on fait maintenant
ssh-keygen -t ed25519 -C "cyril@rudeops.com"
Ed25519 est plus court, plus rapide, plus sûr que RSA 4096. Les clés font 68 caractères au lieu de plusieurs centaines. Si votre serveur est trop vieux pour supporter Ed25519, il est trop vieux pour être en prod.
La passphrase - ne pas la sauter
Quand ssh-keygen vous demande une passphrase, ne tapez pas Entrée. Une clé privée sans passphrase, c'est un mot de passe en clair sur votre disque. Si quelqu'un accède à votre machine - vol, malware, collègue curieux - il a accès à tout ce que cette clé déverrouille.
ssh-agent et ssh-add
eval "$(ssh-agent -s)"
ssh-add -t 4h ~/.ssh/id_ed25519
ssh-add -l
L'agent garde vos clés déchiffrées en mémoire. Vous tapez votre passphrase une fois, vous ne la retapez plus jusqu'au prochain redémarrage ou expiration.
Les certificats SSH - la PKI pour SSH
Les clés SSH sont un modèle pair à pair - vous copiez votre clé publique sur chaque serveur. Ça fonctionne à petite échelle, ça devient ingérable à partir de quelques dizaines de serveurs et d'utilisateurs.
Les certificats SSH sont une alternative : une autorité de certification (CA) signe les clés des utilisateurs et des serveurs. Les serveurs font confiance à la CA, pas aux clés individuellement. Quand un utilisateur arrive avec une clé signée par la CA, l'accès est accordé sans avoir jamais copié sa clé publique sur le serveur.
# Créer une CA SSH
ssh-keygen -t ed25519 -f ~/.ssh/ca -C "RudeOps SSH CA"
# Signer la clé d'un utilisateur (valide 1 semaine, identité "cyril")
ssh-keygen -s ~/.ssh/ca \
-I "cyril@rudeops.com" \
-n "cyril,ubuntu" \
-V +1w \
~/.ssh/id_ed25519.pub
Côté serveur, dans sshd_config :
TrustedUserCAKeys /etc/ssh/ca.pub
Maintenant tous les utilisateurs dont la clé est signée par la CA peuvent se connecter, sans authorized_keys individuel. Pour révoquer l'accès d'un utilisateur : vous ne renouvelez pas son certificat. Plus besoin de retirer sa clé de dizaines de serveurs.
Les certificats ont une date d'expiration - une clé compromise est automatiquement invalide après l'expiration. C'est la vraie différence avec les clés traditionnelles qui restent valides jusqu'à suppression manuelle.
YubiKey et SSH - les clés matérielles
Une YubiKey stocke votre clé privée dans un hardware sécurisé. La clé ne quitte jamais le device - les opérations cryptographiques se font sur la YubiKey elle-même. Vol de laptop, malware, accès non autorisé à votre home directory : votre clé SSH reste inaccessible.
# Lister les slots disponibles
ssh-keygen -D /usr/lib/x86_64-linux-gnu/libykcs11.so -e
# Utiliser la YubiKey avec ssh-agent
ssh-add -s /usr/lib/x86_64-linux-gnu/libykcs11.so
Depuis OpenSSH 8.2, le support FIDO2 est natif :
# Générer une clé résidente sur la YubiKey
ssh-keygen -t ed25519-sk -O resident -f ~/.ssh/id_yubikey
La clé privée réside sur la YubiKey. Une pression physique sur le bouton est requise pour chaque authentification. Vous ne pouvez pas vous faire phisher une clé que vous devez toucher physiquement.
Les tunnels SSH - local, remote, dynamic
Local forwarding - accéder à un service distant comme s'il était local
ssh -N -f -L 5432:localhost:5432 user@serveur.example.com
Accéder à une base de données de prod sans l'exposer :
# Tunnel vers PostgreSQL
ssh -N -f -L 5432:localhost:5432 prod-db
# Puis dans un autre terminal
psql -h localhost -p 5432 -U cyril -d mabase
La base n'est pas exposée sur internet. Le tunnel SSH chiffré fait le pont. Votre DBA vous remercie, votre RSSI aussi.
Accéder à un dashboard Grafana interne :
ssh -N -f -L 3000:localhost:3000 user@serveur-monitoring
Ouvrez http://localhost:3000 dans votre navigateur. Vous êtes sur le Grafana du serveur de monitoring, sans l'exposer sur internet, sans VPN.
Débugger un service dans un VPC AWS sans bastion manager :
# ~/.ssh/config
Host aws-bastion
HostName 1.2.3.4
User ec2-user
IdentityFile ~/.ssh/aws.pem
Host rds-prod
HostName rds-prod.abc123.eu-west-1.rds.amazonaws.com
User postgres
ProxyJump aws-bastion
LocalForward 5432 rds-prod.abc123.eu-west-1.rds.amazonaws.com:5432
# Connexion directe
ssh -N rds-prod
Accéder à l'API server Kubernetes sans exposer le kubeconfig :
ssh -N -f -L 6443:localhost:6443 user@master-node
# Dans votre kubeconfig, pointez vers localhost:6443
kubectl --server=https://localhost:6443 get pods
Remote forwarding - exposer un service local
ssh -R 8080:localhost:3000 user@serveur.example.com
Cas d'usage : recevoir des webhooks GitHub en local pendant le développement. Votre app tourne sur localhost:3000. Le serveur distant écoute sur 8080. Vous configurez votre webhook GitHub vers http://votre-serveur.com:8080, les events arrivent dans votre app locale.
Pour que le port soit accessible depuis l'extérieur du serveur :
# Dans sshd_config côté serveur
GatewayPorts yes
Dynamic forwarding - proxy SOCKS
ssh -D 1080 -N -f user@serveur.example.com
Configurez votre navigateur pour utiliser localhost:1080 comme proxy SOCKS5. Tout le trafic du navigateur passe par le serveur SSH.
Cas d'usage DevOps : accéder à une interface web interne (Kibana, Kubernetes dashboard, Consul UI) sans exposer de port et sans VPN. Le proxy SOCKS route sélectivement le trafic - uniquement ce que vous lui demandez, contrairement à un VPN qui route tout.
ProxyJump - sauter de bastion en bastion
# Un saut
ssh -J ops@bastion ubuntu@serveur-interne
# Plusieurs sauts
ssh -J ops@bastion1,ops@bastion2 ubuntu@serveur-final
Dans ~/.ssh/config :
Host bastion
HostName bastion.corp.example.com
User ops
ForwardAgent yes
Host serveur-interne
HostName 10.0.1.42
User ubuntu
ProxyJump bastion
Host serveur-profond
HostName 10.0.2.100
User ubuntu
ProxyJump bastion,serveur-interne
Agent forwarding - avec discernement. ForwardAgent yes permet d'utiliser votre clé locale depuis le bastion pour les connexions suivantes. Pratique, mais à restreindre aux bastions de confiance - si le bastion est compromis, l'attaquant peut utiliser votre agent pendant que votre session est ouverte.
Une alternative plus sûre : ProxyJump sans ForwardAgent. SSH crée un canal direct chiffré entre votre machine et la destination finale, en passant par le bastion comme relais sans lui donner accès à votre clé.
Git via SSH - plusieurs comptes sur la même machine
ssh-keygen -t ed25519 -f ~/.ssh/id_github_perso -C "cyril@gmail.com"
ssh-keygen -t ed25519 -f ~/.ssh/id_github_pro -C "cyril@entreprise.com"
Dans ~/.ssh/config :
Host github-perso
HostName github.com
User git
IdentityFile ~/.ssh/id_github_perso
IdentitiesOnly yes
Host github-pro
HostName github.com
User git
IdentityFile ~/.ssh/id_github_pro
IdentitiesOnly yes
git clone git@github-perso:cyril/mon-projet.git
git clone git@github-pro:entreprise/projet.git
# Vérifier
ssh -T git@github-perso # Hi cyril!
ssh -T git@github-pro # Hi cyril-pro!
scp vs rsync via SSH
scp - copie simple
# Copier un fichier local vers un serveur
scp fichier.txt user@serveur:/tmp/
# Copier depuis un serveur
scp user@serveur:/var/log/app.log ./
# Copier un dossier récursivement
scp -r ./dossier user@serveur:/opt/
scp est simple, disponible partout, parfait pour copier un fichier ponctuel. Ses limitations : il ne reprend pas les transferts interrompus, il copie tout même si rien n'a changé, et il est plus lent que rsync sur de gros volumes.
rsync via SSH - la vraie solution
# Synchroniser un dossier local vers un serveur
rsync -avz --progress ./deploy/ user@serveur:/opt/app/
# Avec suppression des fichiers supprimés localement
rsync -avz --delete ./deploy/ user@serveur:/opt/app/
# Simulation sans modification (dry-run)
rsync -avzn ./deploy/ user@serveur:/opt/app/
# Reprendre un transfert interrompu
rsync -avz --partial --progress fichier.tar.gz user@serveur:/tmp/
-a : mode archive (récursif + permissions + timestamps). -v : verbose. -z : compression pendant le transfert. --progress : progression par fichier.
rsync ne transfère que les différences. Sur un dossier de 10GB où 3 fichiers ont changé, il transfère 3 fichiers, pas 10GB. C'est la raison principale de l'utiliser à la place de scp.
Spécifier la clé SSH pour rsync :
rsync -avz -e "ssh -i ~/.ssh/ma_cle -p 2222" ./deploy/ user@serveur:/opt/app/
Ou via ~/.ssh/config - rsync utilise votre config SSH automatiquement :
rsync -avz ./deploy/ rudeops:/opt/app/
Déploiement sans downtime :
# Déployer dans un dossier temporaire
rsync -avz --delete ./dist/ user@serveur:/opt/app-new/
# Basculer atomiquement via symlink
ssh user@serveur "ln -sfn /opt/app-new /opt/app && systemctl reload nginx"
Le rechargement nginx pointe vers le nouveau dossier sans interruption de service. L'ancien dossier reste disponible pour rollback rapide.
SSH dans les pipelines CI/CD
ssh-keyscan - pré-peupler known_hosts
Dans un pipeline CI/CD, SSH refuse de se connecter à un host inconnu car il ne peut pas afficher de prompt interactif. La solution naïve (StrictHostKeyChecking no) désactive la vérification. La bonne solution :
# Récupérer l'empreinte du serveur et l'ajouter à known_hosts
ssh-keyscan -H mon.serveur.example.com >> ~/.ssh/known_hosts
# Ou pour un port non standard
ssh-keyscan -H -p 2222 mon.serveur.example.com >> ~/.ssh/known_hosts
Dans un pipeline GitHub Actions :
- name: Setup SSH
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
ssh-keyscan -H ${{ secrets.DEPLOY_HOST }} >> ~/.ssh/known_hosts
- name: Deploy
run: rsync -avz ./dist/ deploy@${{ secrets.DEPLOY_HOST }}:/opt/app/
L'empreinte est récupérée dynamiquement et vérifiée à chaque connexion. Pas de StrictHostKeyChecking no, pas de faille.
Ansible via SSH
Ansible utilise SSH pour toutes ses connexions. La config ~/.ssh/config s'applique automatiquement. Pour optimiser les déploiements Ansible sur de nombreux hosts :
# ansible.cfg
[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPath=/tmp/ansible-ssh-%h-%p-%r -o ControlPersist=60s
pipelining = True
ControlMaster + ControlPersist : une seule connexion SSH par host pour toute la durée du playbook. pipelining : plusieurs tâches Ansible s'exécutent dans la même connexion SSH sans en ouvrir une nouvelle à chaque fois. Sur un playbook de 50 tâches vers 20 hosts, le gain de temps est significatif.
Terraform via SSH
Terraform utilise SSH pour les provisioners et les connexions aux resources. Configuration dans le bloc connection :
resource "null_resource" "deploy" {
connection {
type = "ssh"
user = "ubuntu"
private_key = file("~/.ssh/id_ed25519")
host = aws_instance.web.public_ip
# Via bastion
bastion_host = aws_instance.bastion.public_ip
bastion_user = "ec2-user"
bastion_private_key = file("~/.ssh/aws.pem")
}
}
Terraform gère le ProxyJump nativement via bastion_host. Pas besoin de configurer SSH manuellement.
Sécuriser sshd
La config est dans /etc/ssh/sshd_config. Après chaque modification : systemctl reload sshd - pas restart.
# Désactiver le login root
PermitRootLogin no
# Désactiver l'authentification par mot de passe
PasswordAuthentication no
PubkeyAuthentication yes
# Restreindre les utilisateurs
AllowUsers cyril deploy
# Limiter les tentatives
MaxAuthTries 3
LoginGraceTime 30
# Changer le port
Port 2222
# Désactiver les protocoles legacy
Protocol 2
# Restreindre les algorithmes de chiffrement
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com
KexAlgorithms curve25519-sha256,diffie-hellman-group16-sha512
Les algorithmes de chiffrement - pourquoi ça compte : les defaults OpenSSH sont raisonnables mais incluent des algorithmes plus anciens pour la compatibilité. Sur un serveur où vous contrôlez les clients, restreindre aux algorithmes modernes (ChaCha20, AES-GCM, Curve25519) élimine les vecteurs d'attaque sur les algorithmes plus faibles.
Vérifier votre configuration :
# Tester la config sans recharger
sshd -t
# Voir les algorithmes actifs
ssh -Q cipher
ssh -Q mac
ssh -Q kex
# Auditer votre serveur depuis l'extérieur
ssh-audit mon.serveur.example.com
ssh-audit analyse votre configuration et vous signale les algorithmes faibles, les clés hôtes problématiques et les bonnes pratiques manquantes. À lancer avant de mettre un serveur en prod.
Débugger SSH
ssh -v user@host # verbose
ssh -vv user@host # plus verbose
ssh -vvv user@host # tout
Permission denied (publickey) - dans l'ordre : clé publique dans authorized_keys du serveur, permissions 700 sur ~/.ssh/ et 600 sur authorized_keys, ssh-add -l liste votre clé, PubkeyAuthentication yes dans sshd_config.
Connection refused - sshd ne tourne pas ou pas sur le port attendu. systemctl status sshd côté serveur.
Host key verification failed - empreinte changée. Si c'est normal : ssh-keygen -R mon.serveur.example.com.
Connection timed out - problème réseau ou firewall avant SSH. nc -zv mon.serveur.example.com 22 pour isoler.
Too many authentication failures - trop de clés proposées par l'agent. Ajoutez IdentitiesOnly yes dans ~/.ssh/config pour le host concerné.
Lire les logs côté serveur :
journalctl -u sshd -f
tail -f /var/log/auth.log
Les logs côté serveur donnent souvent l'explication exacte que le client cache pudiquement.
À retenir
| Commande | Pour quoi |
|---|---|
ssh -v host | Débugger une connexion |
ssh -N -f -L 5432:localhost:5432 host | Tunnel local vers une DB |
ssh -N -f -D 1080 host | Proxy SOCKS en arrière-plan |
ssh -J bastion user@interne | Sauter via un bastion |
ssh-add -t 4h ~/.ssh/id_ed25519 | Ajouter une clé pour 4h |
ssh-keygen -R host | Supprimer une empreinte connue |
ssh-keyscan -H host >> ~/.ssh/known_hosts | Pré-peupler known_hosts en CI |
rsync -avz --delete src/ user@host:/dst/ | Synchroniser un dossier |
systemctl reload sshd | Recharger la config sans couper |
sshd -t | Valider la config avant reload |
FAQ
Quelle différence entre scp et rsync ?
scp copie tout à chaque fois. rsync ne transfère que les différences. Sur de petits fichiers ponctuels, scp suffit. Sur de gros volumes ou des synchronisations répétées, rsync est indispensable.
Comment savoir quelle clé SSH est utilisée ?
ssh -v user@host - cherchez Offering public key: dans la sortie.
Changer le port SSH améliore-t-il la sécurité ?
Pas contre une attaque ciblée. Oui contre le bruit de fond des bots qui scannent le port 22. Vos logs seront plus propres.
Comment copier sa clé publique sur un serveur ?
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@serveur - gère les permissions automatiquement.
Mon tunnel SSH se coupe après quelques minutes d'inactivité.
ServerAliveInterval 60 et ServerAliveCountMax 3 dans ~/.ssh/config.
Comment utiliser SSH dans un pipeline CI sans StrictHostKeyChecking no ?
ssh-keyscan -H votre.serveur.com >> ~/.ssh/known_hosts avant la première connexion.
Pour aller plus loin
- ssh-audit - auditer votre configuration SSH
- SSH Mastery de Michael W. Lucas - le livre le plus complet sur le sujet
man ssh_config- toutes les directives avec exemplesman sshd_config- toutes les directives serveur