termal.in

← Blog

Tunnels SSH inverses (ssh -R) : exposer un port local via un serveur distant

· Termalin Team sshport-forwardingtunnelingtutorial

Vous avez quelque chose qui tourne sur votre ordinateur portable — un serveur de dev, une API, une machine à la maison derrière un NAT — et quelque chose à l’autre bout d’Internet doit l’atteindre. Pas d’IP publique, pas d’accès au routeur, pas le temps de monter un VPN. C’est le rôle du tunnel SSH inverse, ssh -R : il fait apparaître un port sur un serveur que vous pouvez joindre, et renvoie discrètement vers votre machine tout ce qui frappe à ce port.

C’est l’option que l’on comprend souvent à l’envers, alors clarifions d’abord le modèle mental, puis parcourons les cas concrets.

Le modèle mental : -L, en miroir

Tout tunnel SSH ouvre un port d’écoute sur une machine et achemine ce qui s’y présente vers un point joignable depuis l’autre machine. Avec la redirection locale (-L), le port s’ouvre sur votre machine et le trafic ressort du côté du serveur. Un tunnel inverse en est l’exact miroir :

Avec -R, le port d’écoute s’ouvre sur le serveur distant, et les connexions qui l’atteignent ressortent de votre côté.

C’est toute l’astuce. La machine incapable d’accepter des connexions entrantes établit plutôt une connexion SSH sortante — et fait revenir les connexions par ce canal. Si les deux se confondent encore, le portrait de famille complet se trouve dans La redirection de port SSH, expliquée.

La syntaxe, lue de gauche à droite

ssh -R [bind_address:]remote_port:localhost:local_port user@server

Lisez-la ainsi : sur le serveur, écoute sur remote_port ; redirige tout ce qui arrive vers localhost:local_port — tel que résolu depuis ma machine. Donc :

ssh -R 8080:localhost:3000 deploy@vps.example.com

ouvre le port 8080 sur le VPS, et chaque connexion qui l’atteint aboutit sur le port 3000 de votre portable. La destination n’est pas forcément votre portable lui-même — -R 8080:192.168.1.50:80 redirige vers une autre machine de votre réseau local, en utilisant votre portable comme sortie.

Une option à connaître : -R 0:localhost:3000 laisse le serveur choisir n’importe quel port libre, et affiche Allocated port 43022 for remote forward pour vous indiquer lequel vous avez obtenu.

Trois choses que l’on construit vraiment avec

Montrer un serveur de dev local à un client. Votre travail en cours tourne sur localhost:3000. Plutôt que de le déployer quelque part juste pour une démo :

ssh -N -R 8080:localhost:3000 deploy@demo.example.com

Quiconque peut joindre demo.example.com:8080 regarde maintenant l’app qui tourne sur votre portable, en direct. (-N évite le shell distant — vous ne voulez que le tunnel. Que tout le monde ou seul le serveur lui-même puisse se connecter dépend de GatewayPorts — section suivante.)

Atteindre une machine à la maison derrière un NAT. Votre machine domestique ne peut pas accepter de connexions, mais elle peut en établir. Faites-lui maintenir un tunnel sortant vers un petit VPS :

# on the home machine
ssh -N -R 2222:localhost:22 tunnel@vps.example.com

Désormais, le port 2222 du VPS est le port SSH de la machine domestique, et depuis n’importe où :

ssh -J vps.example.com -p 2222 you@localhost

rebondit par le VPS droit dans la machine derrière le NAT. C’est la réponse auto-hébergée classique à comment se connecter en SSH à son serveur maison.

Livrer des webhooks sur votre portable. Un prestataire de paiement ou un hébergeur git doit envoyer un POST à votre gestionnaire en cours de développement. Faites revenir leur endpoint joignable jusqu’à vous :

ssh -N -R 9000:localhost:9000 deploy@vps.example.com

Pointez le webhook vers le VPS. Si vous gardez le tunnel lié à la boucle locale du serveur (le réglage par défaut), placez nginx ou n’importe quel reverse proxy sur le VPS devant 127.0.0.1:9000 — vous obtenez la terminaison TLS gratuitement et n’exposez jamais le port brut du tunnel.

GatewayPorts : pourquoi personne d’autre ne peut joindre votre tunnel

La surprise numéro un : le tunnel marche quand vous le testez depuis le serveur (curl localhost:8080 réussit), mais personne de l’extérieur ne peut se connecter. C’est voulu — par défaut, sshd lie les ports redirigés à distance à 127.0.0.1 uniquement, quelle que soit l’adresse de liaison demandée.

Pour ouvrir un tunnel au monde entier, le /etc/ssh/sshd_config du serveur doit contenir :

GatewayPorts clientspecified

puis redémarrez sshd et demandez la liaison explicitement : ssh -R 0.0.0.0:8080:localhost:3000 .... (GatewayPorts yes lie chaque redirection distante à toutes les interfaces, ce qui est plus large que ce que l’on veut d’ordinaire.) C’est une décision côté serveur que vous ne pouvez pas forcer depuis le client — un réglage de sécurité délibéré, car un tunnel inverse est un trou percé de l’intérieur, et tout ce qu’il expose se retrouve de fait exposé à Internet. Traitez-le comme tel.

Deux autres réglages côté serveur peuvent vous piéger : AllowTcpForwarding no désactive complètement les tunnels, et PermitListen peut restreindre les ports que -R a le droit de réclamer.

Le maintenir en vie

Un tunnel inverse n’est qu’une seule connexion TCP, et les connexions TCP inactives meurent. Pour tout ce qui dure plus qu’une démo :

ssh -N -R 2222:localhost:22 \
    -o ServerAliveInterval=30 \
    -o ExitOnForwardFailure=yes \
    tunnel@vps.example.com
  • ServerAliveInterval 30 entretient le trafic pour que les entrées NAT et les délais d’inactivité ne fauchent pas la connexion.
  • ExitOnForwardFailure yes fait échouer ssh bruyamment si le port distant ne peut pas être lié, au lieu de se connecter quand même sans rien rediriger.
  • autossh enveloppe la commande et relance le tunnel dès qu’il tombe — l’outil de référence pour les tunnels censés survivre aux redémarrages et aux réseaux capricieux, lancé en général sous la forme autossh -M 0 -N -R ... dans une unité systemd.

Et une fois qu’un tunnel mérite une place permanente, déplacez-le dans ~/.ssh/config avec une ligne RemoteForward pour qu’il voyage avec l’entrée de l’hôte — la structure est détaillée dans le guide ssh_config (en anglais) :

Host homelink
    HostName vps.example.com
    User tunnel
    RemoteForward 2222 localhost:22
    ServerAliveInterval 30
    ExitOnForwardFailure yes

-L, -R et -D en un coup d’œil

OptionLe port s’ouvre surLe trafic ressort surUsage typique
-Lvotre machinele côté serveurramener une base/un tableau de bord distant vers localhost
-Rle serveurvotre côtéexposer un service local ; atteindre une machine derrière un NAT
-Dvotre machinele côté serveur, n’importe quelle destinationproxy SOCKS vers un réseau

Quand le tunnel inverse est le mauvais outil

Le tunnel inverse est une superbe réponse temporaire : démos, débogage de webhooks, sauvetage d’une machine derrière un NAT. C’en est une mauvaise permanente. Si ce que vous exposez doit rester en ligne des mois, servir de vrais utilisateurs ou survivre à la mise en veille de votre portable — c’est de l’entrée (ingress), et l’ingress mérite une vraie infrastructure : un reverse proxy sur un serveur, un VPN ou un réseau overlay, un déploiement en bonne et due forme. Le charme du tunnel, c’est d’être une seule commande sans empreinte permanente ; dès que vous lui écrivez de la supervision, vous l’avez dépassé.

Dépanner les échecs habituels

  • Warning: remote port forwarding failed for listen port 8080 — le port est déjà pris sur le serveur. Souvent, le coupable est votre propre tunnel précédent : l’ancien processus sshd garde le port jusqu’à l’expiration de la connexion morte. Les keepalives évitent le zombie ; -R 0:... contourne la collision ; ExitOnForwardFailure transforme l’avertissement en une erreur impossible à manquer.
  • Marche depuis le serveur, refusé depuis l’extérieur — l’écouteur est sur la boucle locale du serveur. C’est le réglage par défaut de GatewayPorts qui fait son travail ; soit vous le configurez, soit vous placez un reverse proxy devant le port.
  • Le tunnel se connecte mais le service ne répond pas — vérifiez la moitié locale : y a-t-il réellement quelque chose à l’écoute sur le port local que vous avez indiqué ? -R 8080:localhost:3000 sans rien sur votre :3000 renvoie un « connection refused » à chaque visiteur.
  • La connexion SSH elle-même refuse de s’établir — c’est un autre problème, avec sa propre liste de contrôle : « connection refused » et « timed out », décodés.

Quand les tunnels deviennent du mobilier

Le tunnel inverse que vous lancez une fois, vous le tapez. Celui qui porte votre home lab, votre machine de démo et votre endpoint de webhook, c’est un état que vous reconstruisez chaque matin après chaque mise en veille — et dont vous perdez le fil dès le jeudi. Le gestionnaire de tunnels enregistrés de Termalin garde les redirections locales, distantes et dynamiques rattachées à l’hôte auquel elles appartiennent : lancez-les d’un clic, voyez d’un coup d’œil lesquelles sont actives, et laissez-les se rétablir avec la connexion au lieu de les retaper depuis l’historique du shell. La liste des hôtes — clés, hôtes de rebond, keepalives et tunnels compris — se synchronise entre vos machines, chiffrée de bout en bout. Et quand une machine reste définitivement derrière un NAT, son Connector en connexion sortante uniquement remplace entièrement le tunnel inverse bricolé à la main — aucun port entrant, et aucune unité autossh à surveiller. La ligne de commande vous apprend ce que -R signifie ; un client évite que les tunnels dont vous dépendez ne dépendent de votre mémoire.


Termalin est un client SSH gratuit et multiplateforme, avec tunnels intégrés, SFTP et gestionnaire de clés — téléchargez-le, ou voyez comment il gère vos clés en toute sécurité.

Essayez-le sur un hôte.

Termalin est un client SSH rapide pour vous — et pour vos agents.

Offre gratuite · 14 jours d’essai Pro · tarifs