Introduction
Vous avez un serveur, une machine cliente, un Raspberry Pi, ou un NAS chez un client / chez vous derrière un routeur que vous ne pouvez pas configurer (box internet bridée, IP dynamique, NAT carrier-grade). Comment l'administrer à distance ?
Solution classique : port forwarding sur le routeur. Impossible quand vous n'avez pas la main.
Solution moderne : reverse SSH tunnel. La machine cible se connecte d'elle-même vers un VPS public que vous contrôlez (le "bastion"). Une fois la session SSH inverse établie, vous pouvez vous connecter au bastion et rebondir vers la cible.
Architecture :
[Vous] ──SSH──▶ [VPS Bastion] ◀──Reverse SSH── [Machine cible derrière NAT]
(IP publique) (10.0.0.42)
Prérequis
- Un VPS public avec SSH actif (= le bastion). Un petit VPS VeryCloud suffit.
- Une machine cible Linux avec SSH client (Raspberry Pi, NAS, serveur derrière NAT...)
- Accès root ou sudo sur les deux
Étape 1 : Préparer le bastion
Sur le VPS bastion, créez un user dédié au tunnel :
sudo adduser tunnel
sudo usermod -s /bin/false tunnel # Pas de shell interactif (sécurité)
Désactivez les fonctions inutiles dans /etc/ssh/sshd_config :
Match User tunnel
PasswordAuthentication no
PermitTTY no
AllowAgentForwarding no
AllowTcpForwarding yes
X11Forwarding no
PermitOpen any
ForceCommand /bin/false
sudo systemctl reload sshd
Autorisez l'ouverture de ports en écoute (GatewayPorts) :
sudo nano /etc/ssh/sshd_config
Ajoutez :
GatewayPorts clientspecified
sudo systemctl reload sshd
Étape 2 : Générer une clé SSH sur la machine cible
Sur la machine cible (celle derrière le NAT) :
sudo ssh-keygen -t ed25519 -N "" -f /root/.ssh/id_tunnel
Récupérez la clé publique :
cat /root/.ssh/id_tunnel.pub
Étape 3 : Installer la clé sur le bastion
Sur le bastion, ajoutez la clé publique aux authorized_keys du user tunnel :
sudo mkdir -p /home/tunnel/.ssh
sudo nano /home/tunnel/.ssh/authorized_keys
Collez la clé publique. Permissions :
sudo chown -R tunnel:tunnel /home/tunnel/.ssh
sudo chmod 700 /home/tunnel/.ssh
sudo chmod 600 /home/tunnel/.ssh/authorized_keys
Étape 4 : Tester le tunnel manuellement
Sur la machine cible :
sudo ssh -i /root/.ssh/id_tunnel \
-N -R 2222:localhost:22 \
tunnel@IP_BASTION
Décrypter :
-N: pas d'exécution de commande-R 2222:localhost:22: ouvre le port 2222 sur le bastion, redirigé vers le port 22 (SSH) de la machine cible
Sur votre poste, connectez-vous au bastion puis rebondissez :
ssh user@IP_BASTION
ssh -p 2222 user_de_la_cible@localhost
Ou directement en une commande depuis votre poste (sans étape intermédiaire) :
ssh -J tunnel@IP_BASTION -p 2222 user_de_la_cible@localhost
Si ça marche, vous êtes connecté à la cible via le bastion.
Étape 5 : Service systemd pour pérenniser le tunnel
Le tunnel manuel meurt au reboot ou à la moindre coupure. On va le rendre persistant.
Sur la machine cible :
sudo nano /etc/systemd/system/reverse-tunnel.service
[Unit]
Description=Reverse SSH tunnel to bastion
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=root
ExecStart=/usr/bin/ssh \
-NT \
-o ServerAliveInterval=30 \
-o ServerAliveCountMax=3 \
-o ExitOnForwardFailure=yes \
-o StrictHostKeyChecking=accept-new \
-i /root/.ssh/id_tunnel \
-R 2222:localhost:22 \
tunnel@IP_BASTION
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
Activer :
sudo systemctl daemon-reload
sudo systemctl enable --now reverse-tunnel
sudo systemctl status reverse-tunnel
Vérifiez :
sudo journalctl -u reverse-tunnel -f
Sur le bastion :
ss -tlnp | grep 2222
# Doit montrer une écoute sur 127.0.0.1:2222
Étape 6 : Avec autossh (plus robuste que ssh natif)
autossh détecte les coupures et reconnecte plus vite. Installation sur la cible :
sudo apt install -y autossh
Modifiez le service :
sudo nano /etc/systemd/system/reverse-tunnel.service
[Service]
ExecStart=/usr/bin/autossh \
-M 0 -NT \
-o ServerAliveInterval=30 \
-o ServerAliveCountMax=3 \
-o ExitOnForwardFailure=yes \
-i /root/.ssh/id_tunnel \
-R 2222:localhost:22 \
tunnel@IP_BASTION
sudo systemctl daemon-reload
sudo systemctl restart reverse-tunnel
Étape 7 : Plusieurs tunnels pour plusieurs cibles
Si vous avez 5 Raspberry Pi chez 5 clients, attribuez un port distinct au bastion :
| Client | Port bastion | Cible |
|---|---|---|
| ClientA | 2201 | rpi-a:22 |
| ClientB | 2202 | rpi-b:22 |
| ClientC | 2203 | rpi-c:22 |
Sur chaque cible, adaptez le port dans le -R 22XX:localhost:22.
Pour vous connecter :
ssh -J tunnel@IP_BASTION -p 2202 pi@localhost
Étape 8 : Exposer des ports autres que SSH
Le tunnel reverse n'est pas limité à SSH. Vous pouvez exposer un service web local :
# Sur la cible
ssh -R 8080:localhost:80 tunnel@IP_BASTION
Sur le bastion, le port 8080 redirige vers le serveur web (port 80) de la cible.
Pour le rendre accessible publiquement (pas seulement depuis le bastion), utilisez 0.0.0.0 :
ssh -R 0.0.0.0:8080:localhost:80 tunnel@IP_BASTION
⚠️ Nécessite GatewayPorts clientspecified ou yes côté bastion (déjà fait à l'étape 1).
Étape 9 : Reverse tunnel multi-ports
Pour exposer plusieurs services en un seul tunnel :
ssh -N \
-R 2222:localhost:22 \
-R 8080:localhost:80 \
-R 9090:localhost:9090 \
tunnel@IP_BASTION
Étape 10 : Nginx en reverse-proxy frontal
Au lieu d'exposer un port 8080 random, faites passer le tunnel par Nginx avec HTTPS et sous-domaine. Sur le bastion :
server {
listen 443 ssl http2;
server_name rpi-clienta.verycloud.fr;
ssl_certificate /etc/letsencrypt/live/rpi-clienta.verycloud.fr/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/rpi-clienta.verycloud.fr/privkey.pem;
location / {
proxy_pass http://127.0.0.1:8080; # le port du tunnel
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
Vous accédez maintenant au Raspberry Pi de chez le client A via https://rpi-clienta.verycloud.fr.
Étape 11 : Sécurité supplémentaire
Restreindre les ports exposables
Dans le authorized_keys du user tunnel, préfixez la clé avec des options :
restrict,port-forwarding,permitlisten="2222",no-pty ssh-ed25519 AAAAC3Nz... mathys@laptop
Ce client ne pourra créer des tunnels que sur le port 2222 du bastion. Pratique pour limiter par client.
Firewall sur le bastion
Bloquez l'accès aux ports tunnels depuis l'extérieur :
sudo ufw deny 2222:2299/tcp
Du coup pour vous connecter, vous DEVEZ d'abord passer par SSH sur le bastion (port 22), pas un accès direct depuis internet.
Monitoring du tunnel
Sur la cible :
# Statut
sudo systemctl status reverse-tunnel
# Logs en live
sudo journalctl -u reverse-tunnel -f
Sur le bastion, vérifiez les connexions actives :
ss -tnp | grep tunnel
Étape 12 : Alternative moderne — Tailscale
Pour la même problématique en mode SaaS / zero-config, voir le tuto Tailscale Mesh VPN. Moins de configuration manuelle mais dépendance à un service tiers.
Reverse SSH est :
- ✅ 100% self-hosted
- ✅ Minimaliste (juste SSH)
- ❌ Plus manuel
- ❌ Nécessite de gérer ports/Nginx
Tailscale est :
- ✅ Zero-config (clé d'auth et ça marche)
- ✅ Mesh (chaque machine voit chaque machine)
- ❌ Dépendance à un service tiers (coordination server)
- ❌ Plus de couche logicielle
Dépannage
Tunnel ne tient pas
Activez les keepalives (déjà fait dans le service systemd). Vérifiez aussi le NAT/firewall côté cible qui peut couper les connexions inactives. ServerAliveInterval=30 envoie un paquet toutes les 30s.
"Could not request local forwarding"
Le port est déjà utilisé sur le bastion ou GatewayPorts mal configuré. Changez le port ou vérifiez sshd_config.
"Connection refused" depuis le bastion
Le tunnel n'est pas actif. Côté cible :
sudo systemctl status reverse-tunnel
sudo journalctl -u reverse-tunnel -n 30
"Host key verification failed"
Première connexion vers le bastion : SSH demande de valider la fingerprint. Avec StrictHostKeyChecking=accept-new dans le service, c'est auto au premier passage. Si ça échoue, supprimez la ligne du bastion dans /root/.ssh/known_hosts.
Performances dégradées
SSH chiffre tout. Pour de gros transferts, optez pour des ciphers rapides :
ssh -c [email protected] ...
Commandes utiles
# Voir tous les tunnels actifs sur le bastion
ss -tlnp | grep sshd
# Connexions du user tunnel
who | grep tunnel
# Tester la connectivité bastion depuis la cible
sudo -u root ssh -i /root/.ssh/id_tunnel tunnel@IP_BASTION exit
echo $? # 0 = OK
# Forcer la reconnexion
sudo systemctl restart reverse-tunnel
# Lister les ports exposés depuis le bastion
ss -tlnp | grep '127.0.0.1\|0.0.0.0'
# Killer un tunnel zombie (sur le bastion)
sudo pkill -u tunnel
Conclusion
Le reverse SSH tunnel est l'outil minimaliste pour atteindre n'importe quelle machine derrière un NAT :
- Pas de port forwarding chez le client/sur la box
- 100% chiffré (SSH)
- Persistant via systemd + autossh
- Multi-tunnel possible avec un seul bastion
Pour aller plus loin :
- Combinez avec Nginx + Let's Encrypt pour exposer du HTTPS
- Utilisez Cloudflare Tunnel comme alternative SaaS
- Migrez vers Tailscale ou WireGuard pour du mesh VPN
Ressources
- Documentation OpenSSH : https://www.openssh.com/manual.html
- autossh : https://www.harding.motd.ca/autossh/
- Tuto VeryCloud — WireGuard VPN :
/docs/article/wireguard - Tuto VeryCloud — Tailscale Mesh :
/docs/article/tailscale - Tuto VeryCloud — SSH hardening :
/docs/article/ssh-hardening


















