Gestor de túneles SSH: guardar, iniciar y detener reenvíos de puertos

Guarda cada túnel como un bloque Host en ~/.ssh/config con LocalForward, RemoteForward o DynamicForward, y luego inícialo por su nombre con ssh -f -N nombre. Añade ControlMaster, ControlPath y ControlPersist al bloque y podrás detenerlo con ssh -O exit nombre, sin buscar el proceso. ssh -O forward y ssh -O cancel añaden o quitan un reenvío mientras el túnel sigue en marcha.

Actualizada el . Ejecutado con OpenSSH 10.0 en Debian 13 en contenedores desechables: un cliente, un host de salto y un destino con un pequeño servidor HTTP.

Guarda cada túnel en ~/.ssh/config

Volver a escribir las opciones -L y -R cada vez es la forma más fácil de confundir los puertos. Mejor dale un nombre a cada túnel, con su propio bloque Host:

Host db-tunnel
  HostName 203.0.113.10
  User deploy
  LocalForward 127.0.0.1:5432 127.0.0.1:5432
  ExitOnForwardFailure yes

Host dev-share
  HostName 203.0.113.10
  User deploy
  RemoteForward 127.0.0.1:9000 127.0.0.1:3000

Host socks
  HostName 203.0.113.10
  User deploy
  DynamicForward 127.0.0.1:1080

Cada línea es la forma de configuración de una opción de la línea de comandos: LocalForward es -L, RemoteForward es -R y DynamicForward es -D. Primero van la dirección y el puerto de escucha, luego un espacio y luego el destino. Igual que con las opciones, el destino de un reenvío local lo resuelve el servidor, así que ahí 127.0.0.1 es el propio servidor. Si los tres tipos son nuevos para ti, empieza por los túneles SSH local, remoto y dinámico explicados.

Usa un nombre aparte para los túneles, no el Host con el que entras a diario. Si no, cada inicio de sesión intenta abrir los mismos puertos y el segundo muestra Address already in use.

Inicia un túnel por su nombre

ssh -f -N db-tunnel

-N no ejecuta ningún comando remoto, así que la conexión solo lleva los reenvíos. -f manda ssh a segundo plano después de iniciar sesión, por lo que la petición de frase de contraseña o contraseña te llega antes. Tu herramienta de base de datos ya puede conectarse a 127.0.0.1:5432.

Con ExitOnForwardFailure yes, un puerto ya ocupado detiene ssh con el código de salida 255 en lugar de dejar una conexión sin túnel. Esto es lo que pasa al iniciar el mismo túnel dos veces:

bind [127.0.0.1]:5432: Address already in use
channel_setup_fwd_listener_tcpip: cannot listen to port: 5432
Could not request local forwarding.

Para iniciar varios túneles guardados a la vez:

for t in db-tunnel socks; do ssh -f -N "$t"; done

Detén un túnel

Un túnel en segundo plano no es más que un proceso ssh. Sin nada más, lo buscas y lo matas:

ps ax | grep '[s]sh -f -N db-tunnel'
pkill -f 'ssh -f -N db-tunnel'

pkill -f afecta a cualquier proceso cuya línea de comandos contenga ese texto. En nuestras pruebas también mató el script de shell que había iniciado el túnel, porque el script contenía las mismas palabras. Un socket de control es más limpio. Añade tres líneas al bloque:

Host db-tunnel
  HostName 203.0.113.10
  User deploy
  LocalForward 127.0.0.1:5432 127.0.0.1:5432
  ExitOnForwardFailure yes
  ControlMaster auto
  ControlPath ~/.ssh/cm-%C
  ControlPersist yes

Ahora el túnel responde a comandos por su nombre:

ssh -f -N db-tunnel
ssh -O check db-tunnel
ssh -O exit db-tunnel
Master running (pid=27)
Exit request sent.

El socket también permite que otros comandos ssh y scp hacia db-tunnel reutilicen la misma conexión. Los detalles están en multiplexación SSH con ControlMaster.

Añade o quita un reenvío sin reconectar

Con un socket de control puedes cambiar un túnel en marcha. La conexión y sus otros reenvíos siguen activos:

ssh -O forward -L 127.0.0.1:8081:127.0.0.1:8000 db-tunnel
ssh -O cancel -L 127.0.0.1:8081:127.0.0.1:8000 db-tunnel

cancel necesita exactamente la misma especificación que usaste para añadir el reenvío. También funciona con -R y -D. Con -R 0:..., el servidor elige un puerto libre y ssh -O forward lo muestra.

Mantén activo un túnel guardado

Un túnel iniciado con -f dura lo que dura su conexión SSH. La suspensión, un cambio de Wi-Fi o un reinicio del servidor lo terminan. Añade estas líneas al bloque para que ssh detecte una conexión muerta en unos 45 segundos y salga, en lugar de quedarse colgado:

  ServerAliveInterval 15
  ServerAliveCountMax 3

Después algo tiene que volver a iniciarlo: un bucle, un gestor de servicios o autossh para un túnel SSH persistente.

Errores que rompen los túneles guardados

  • Dos puntos en lugar de un espacio. LocalForward 5432:127.0.0.1:5432 es el formato de la línea de comandos, no el del archivo de configuración. ssh responde Missing target argument, y en nuestra prueba esa única línea mal escrita impidió conectar a todos los hosts del archivo.
  • Escuchar en todas las interfaces. LocalForward 0.0.0.0:5432 ... o GatewayPorts yes dejan que otras máquinas de tu red usen el túnel. Mantén 127.0.0.1 salvo que lo hagas a propósito.
  • Dos túneles en el mismo puerto local. Solo arranca el primero. Da a cada túnel guardado su propio puerto.
  • Reenvío rechazado por el servidor. Si el puerto se abre pero las conexiones fallan con administratively prohibited, el servidor tiene AllowTcpForwarding desactivado para ti.

Preguntas

¿Cómo veo qué túneles SSH están activos?

Ejecuta ps ax | grep '[s]sh -f -N' para listar los procesos ssh en segundo plano con sus opciones. Para un túnel con socket de control, ssh -O check nombre responde directamente. Dentro de una sesión interactiva, pulsa Intro y escribe ~# para ver sus conexiones reenviadas.

¿Puedo iniciar varios túneles con un solo comando?

Sí. Pon varias líneas LocalForward o DynamicForward en un mismo bloque Host y se abren todas con un solo ssh -f -N nombre. Si un puerto está ocupado y ExitOnForwardFailure está activado, no queda abierta ninguna.

¿Por qué mi túnel guardado se para tras la suspensión o un cambio de red?

El túnel vive dentro de una conexión SSH, y esa conexión muere cuando cae la red. Añade ServerAliveInterval para que ssh lo detecte, y reinícialo con un bucle o con autossh.

Fuentes

Más en Túneles y jump hosts

Todas las guías