Capita spesso: un dispositivo va raggiunto da remoto, ma sta dietro un NAT, un firewall aziendale o una rete su cui non avete alcuna autorità. Non c'è un IP pubblico, non si possono aprire porte, e chiedere una regola in ingresso al reparto IT è un percorso che si misura in settimane.
Il reverse SSH tunneling risolve il problema sfruttando l'unica cosa che quelle reti concedono quasi sempre: l'uscita.
La topologia#
Internet │ ┌────────────────────┼────────────────────┐ │ │ │┌──┴───────────┐ ┌────┴─────────┐ ┌──────┴────────────────┐│ Voi │ │ VPS pubblico│ │ Rete privata ││ (ovunque) │ │ 198.51.100.9│ │ NAT + firewall ││ │ │ │ │ nessuna porta aperta ││ │ │ :22 SSH │ │ ┌──────────────────┐ ││ ssh -p ────┼──▶│ :2222 ◀─────┼───┼──┤ dispositivo │ ││ 2222 │ │ tunnel│ │ │ ssh -R (uscente) │ │└──────────────┘ └──────────────┘ │ └──────────────────┘ │ └────────────────────────┘Il punto è tutto qui: la connessione che regge il tunnel PARTE da dentro.Il firewall vede traffico in uscita sulla 22, che quasi ovunque è permesso.Nessuna regola in ingresso, nessun port forwarding, nessun IP pubblico.L'inversione è tutta qui. Normalmente chi vuole entrare bussa dall'esterno e il firewall dice di no. Nel modello inverso è il dispositivo interno che si collega verso l'esterno — traffico in uscita sulla porta 22, indistinguibile da qualunque altra sessione SSH — e chiede al server pubblico di aprire una porta che rimanda indietro dentro quella stessa connessione già stabilita.
Il comando, e cosa significa ogni pezzo#
# Dal dispositivo privato, verso il VPS. -R apre una porta SUL VPS# e la rimanda indietro dentro questa stessa connessione.ssh -N -R 2222:localhost:22 tunnel@198.51.100.9# │ │ │ │ │# │ │ │ │ └─ porta 22 sul dispositivo privato# │ │ │ └────────── "localhost" e' relativo al dispositivo privato# │ │ └─────────────── porta aperta sul VPS# │ └────────────────── remote forwarding# └───────────────────── nessun comando: solo il tunnelLa parte che confonde di più è localhost:22: quel localhost è relativo al dispositivo privato, cioè alla macchina che esegue il comando. Si sta dicendo al server pubblico: «apri la porta 2222; tutto ciò che ci arriva, rimandalo a me, e io lo consegnerò alla mia porta 22».
Il che significa anche che il bersaglio non deve essere per forza la macchina stessa: -R 8080:192.168.1.50:80 pubblica il server web di un'altra macchina della rete interna, raggiungibile da quella che apre il tunnel.
La trappola: GatewayPorts#
Aperto il tunnel, il tentativo naturale è collegarsi dall'esterno alla porta 2222 del VPS. Non funziona, e la ricerca del motivo porta quasi sempre allo stesso consiglio sbagliato.
# ✗ Trappola numero uno, e quasi nessuno la nota: per impostazione# predefinita sshd associa la porta remota SOLO a 127.0.0.1.# "ssh -p 2222 utente@198.51.100.9" da fuori NON funziona.## La soluzione sbagliata che si trova ovunque:GatewayPorts yes # ← espone la 2222 A TUTTO INTERNET# ✓ La soluzione corretta: lasciare la porta legata al loopback del VPS# e arrivarci con un secondo salto. Il tunnel resta invisibile dall'esterno.GatewayPorts no # (il default: va bene così)La forma corretta lascia il default. La porta resta sul loopback del VPS, quindi è raggiungibile solo da chi è già dentro il VPS — e per esserci dentro serve la vostra chiave SSH. L'accesso diventa un doppio salto:
# Dal proprio portatile: due salti in un comando solo.# -J fa da ponte sul VPS e da lì raggiunge la porta 2222 del loopback.ssh -J tunnel@198.51.100.9 -p 2222 utente@localhost# Meglio ancora: si scrive una volta in ~/.ssh/configcat ~/.ssh/configHost ponte HostName 198.51.100.9 User tunnelHost dispositivo HostName localhost Port 2222 User utente ProxyJump pontessh dispositivoscp relazione.pdf dispositivo:/tmp/rsync -az dispositivo:/var/log/app/ ./log/Restringere la chiave sul server pubblico#
Il dispositivo interno ha bisogno di una chiave per collegarsi al VPS. Quella chiave sta su una macchina che è, per definizione, in un posto su cui avete meno controllo — un magazzino, un totem, un impianto. Va data per scontata la possibilità che finisca in mani altrui.
# Sul VPS: l'account che regge il tunnel non deve poter fare NIENT'ALTRO.# Se il dispositivo privato viene compromesso, la sua chiave apre solo# un tunnel — non una shell sul vostro server pubblico.# 1. Utente senza shell e senza home utileadduser --system --shell /usr/sbin/nologin --no-create-home tunnel# 2. Restrizioni nella chiave autorizzata, non nella configurazione globale:# valgono per QUELLA chiave e viaggiano con essa.# ~tunnel/.ssh/authorized_keysrestrict,port-forwarding,permitopen="localhost:2222",command="/usr/sbin/nologin" ssh-ed25519 AAAAC3Nz... dispositivo-magazzino# "restrict" nega tutto (agent forwarding, X11, pty, esecuzione), poi si# riabilita solo il forwarding e solo verso una porta. Senza permitopen,# quella chiave potrebbe aprire tunnel verso QUALUNQUE host raggiungibile# dal VPS — compresa la vostra rete interna, se il VPS ne fa parte.Le due parole che contano sono restrict e permitopen. La prima nega tutto — pty, esecuzione di comandi, agent forwarding, X11 — e poi si riabilita esattamente ciò che serve. La seconda limita verso dove quella chiave può aprire tunnel: senza, una chiave rubata potrebbe usare il vostro VPS come trampolino verso qualunque host esso raggiunga, rete interna compresa.
Tenerlo in piedi davvero#
Un tunnel lanciato a mano dura fino alla prima disconnessione. La parte interessante non è metterlo in un servizio — quella è facile — ma le opzioni senza le quali il servizio sembra funzionare.
# Il tunnel nudo muore: cade la rete, cambia l'IP, il portatile va in# sospensione. autossh lo risolleva, ma da solo non basta: senza i# keepalive, un tunnel "morto ma non chiuso" resta aperto per ore.[Unit]Description=Tunnel inverso verso il VPSAfter=network-online.targetWants=network-online.target[Service]User=servizioEnvironment=AUTOSSH_GATETIME=0ExecStart=/usr/bin/autossh -M 0 -N \ -o ServerAliveInterval=30 \ -o ServerAliveCountMax=3 \ -o ExitOnForwardFailure=yes \ -o StrictHostKeyChecking=yes \ -i /etc/tunnel/id_ed25519 \ -R 2222:localhost:22 tunnel@198.51.100.9Restart=alwaysRestartSec=10[Install]WantedBy=multi-user.target# Le tre opzioni che distinguono un tunnel affidabile da uno che sembra# funzionare finché non serve davvero:## ServerAliveInterval 30 + ServerAliveCountMax 3# Il client manda un ping cifrato ogni 30 s; dopo tre mancate risposte# chiude. Senza, una caduta di rete lascia un socket semiaperto: dal VPS# la porta 2222 risulta occupata, ma non arriva da nessuna parte.## ExitOnForwardFailure yes# Se la porta remota non si riesce ad aprire (perche' e' ancora occupata# dal tunnel precedente), ssh esce invece di restare connesso SENZA il# forwarding. Senza questa opzione autossh crede che vada tutto bene.## ClientAliveInterval 30 (lato sshd, sul VPS)# L'altra metà: il server chiude le connessioni fantasma e libera la porta.Il guasto tipico, quando queste opzioni mancano, è questo: la rete cade per novanta secondi. Il dispositivo se ne accorge e riparte; il VPS no, e tiene la porta 2222 occupata da una connessione morta. Il tunnel nuovo prova ad aprirla, fallisce, ma ssh resta collegato lo stesso — senza forwarding. autossh vede un processo vivo e non fa niente. Da fuori il servizio risulta attivo; in realtà non passa nulla, e resterà così finché qualcuno non se ne accorgerà.
Più dispositivi#
# Con più dispositivi serve una porta per ciascuno. Un elenco esplicito# su file e' meglio di una convenzione a memoria.cat /etc/tunnel/porte2201 magazzino-milano2202 magazzino-roma2203 totem-ingresso# Sul VPS, chi e' effettivamente collegato in questo momento:ss -tlnp 'sport >= 2200 and sport <= 2299'LISTEN 0 128 127.0.0.1:2201 users:(("sshd",pid=8812,fd=9))LISTEN 0 128 127.0.0.1:2203 users:(("sshd",pid=9104,fd=9))# 2202 manca: il magazzino di Roma non e' su. Ed e' esattamente questo# il controllo da mettere in un monitoraggio.Con qualche dispositivo l'assegnazione manuale delle porte funziona. Il modello comincia a scricchiolare quando i dispositivi diventano decine, quando devono parlare fra loro, o quando ognuno espone più servizi: a quel punto si sta ricostruendo a mano una tabella di instradamento, ed esiste da tempo software che lo fa meglio.
Quando conviene qualcos'altro#
# Quando il tunnel SSH NON è la scelta giusta:## - Più di una decina di dispositivi, o serve la comunicazione fra loro.# → una VPN mesh (WireGuard, o soluzioni che ne fanno l'orchestrazione):# stessa proprietà di uscita, ma con instradamento vero e chiavi gestite.## - Serve solo esporre UN servizio HTTP, non l'accesso al sistema.# → un reverse proxy con tunnel applicativo: meno superficie, autenticazione# a livello HTTP, nessuna shell raggiungibile per costruzione.## - Il dispositivo è uno solo e l'accesso è occasionale.# → il tunnel SSH va benissimo: è già installato, non aggiunge dipendenze# e usa credenziali che sapete già gestire.| Situazione | Strumento | Perché |
|---|---|---|
| 1-5 dispositivi, accesso amministrativo | reverse SSH | già installato, zero dipendenze, credenziali che sapete gestire |
| 10+ dispositivi, o che devono parlarsi | VPN mesh (WireGuard) | instradamento vero, chiavi gestite, nessuna tabella di porte a mano |
| Esporre un solo servizio HTTP | tunnel applicativo | nessuna shell raggiungibile, autenticazione a livello HTTP |
| Accesso una tantum, assistito | condivisione schermo su richiesta | nessuna infrastruttura permanente da mantenere e da proteggere |
| Rete che ispeziona il traffico TLS | tunnel su 443 con ProxyCommand | SSH su 22 potrebbe essere filtrato; su 443 passa quasi sempre |
Una nota sull'ultima riga, perché è il caso limite più frequente: se anche la 22 in uscita è bloccata, SSH può viaggiare sulla 443 (spesso già attiva su sshd con una direttiva Port 443 su un secondo indirizzo del VPS), oppure incapsulato in un ProxyCommand che passa per il proxy HTTP aziendale.