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#

schema
                    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#

dispositivo privato
# 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 tunnel

La 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.

/etc/ssh/sshd_configConfig
# ✗ 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:

portatile
# 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/
Con ProxyJump nel file di configurazione, tutto ciò che parla SSH funziona senza sapere che c'è un tunnel: scp, rsync, sftp, il port forwarding di un editor remoto.

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 VPSShell
# 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.

/etc/systemd/system/tunnel.serviceConfig
# 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
perché servonoShell
# 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#

VPS
# 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.
Il controllo delle porte in ascolto è il monitoraggio giusto: dice quali tunnel sono realmente attivi adesso, non quali servizi risultano avviati.

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#

alternativeShell
# 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.
SituazioneStrumentoPerché
1-5 dispositivi, accesso amministrativoreverse SSHgià installato, zero dipendenze, credenziali che sapete gestire
10+ dispositivi, o che devono parlarsiVPN mesh (WireGuard)instradamento vero, chiavi gestite, nessuna tabella di porte a mano
Esporre un solo servizio HTTPtunnel applicativonessuna shell raggiungibile, autenticazione a livello HTTP
Accesso una tantum, assistitocondivisione schermo su richiestanessuna infrastruttura permanente da mantenere e da proteggere
Rete che ispeziona il traffico TLStunnel su 443 con ProxyCommandSSH 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.