Túneles SSH explicados: reenvío de puertos local, remoto y dinámico

El reenvío de puertos SSH tiene tres tipos. ssh -L abre un puerto en tu máquina que llega a algo del lado del servidor. ssh -R abre un puerto en el servidor que llega a algo de tu lado. ssh -D abre un proxy SOCKS local que envía cualquier conexión a través del servidor. Añade -N para no ejecutar ningún comando remoto, y -o ExitOnForwardFailure=yes para que un túnel que no puede abrirse falle con un error.

Actualizada el . Todos los reenvíos, direcciones de enlace, valores de GatewayPorts, valores de AllowTcpForwarding y mensajes de fallo se ejecutaron con OpenSSH 10.3 en macOS como cliente contra OpenSSH 10.0 en Debian 13, usando pequeños servidores HTTP como destino. Varias opciones -L en una misma conexión, -L a través de -J y el error de configuración por los dos puntos en LocalForward se volvieron a ejecutar con OpenSSH 10.0 en Debian 13.

¿Cuál necesitas?

OpciónEl puerto se abre enÚsalo para
-L localTu máquinaLlegar a un servicio que solo el servidor puede alcanzar, como una base de datos.
-R remotoEl servidorDejar que el servidor llegue a algo de tu máquina, como un sitio de desarrollo.
-D dinámicoTu máquinaNavegar o conectarte a muchos hosts como si estuvieras en el servidor.

Una forma fácil de recordarlo: -L escucha en tu máquina local, -R escucha en el servidor remoto, y -D es dinámico porque la aplicación elige el destino cada vez.

Reenvío local: -L

ssh -N -L 8080:localhost:80 user@server
your app -> your machine, port 8080 -> SSH -> server -> localhost:80 as seen by the server

El formato es puerto_local:destino:puerto_destino. Ahora http://127.0.0.1:8080 en tu máquina muestra el servidor web que escucha solo en la dirección de loopback del propio servidor.

Lo importante es que el servidor resuelve el destino. localhost significa el propio servidor, no tu máquina. El destino también puede ser otro host al que llegue el servidor, y ese último salto no lo cifra SSH:

ssh -N -L 8080:intranet.example.com:80 user@server

Reenvío remoto: -R

ssh -N -R 9000:localhost:3000 user@server
a program on the server -> server, port 9000 -> SSH -> your machine -> localhost:3000 as seen by your machine

Ahora un programa del servidor puede abrir http://127.0.0.1:9000 y llegar a la aplicación que corre en tu máquina. Funciona incluso cuando tu máquina está detrás de un NAT, porque tu lado inició la conexión.

Reenvío dinámico: -D

ssh -N -D 1080 user@server
your app -> SOCKS proxy on your machine, port 1080 -> SSH -> server -> any host:port the app asks for

Apunta una aplicación compatible con SOCKS a 127.0.0.1:1080. Para probarlo con curl:

curl --socks5-hostname 127.0.0.1:1080 http://intranet.example.com/

--socks5-hostname hace que la consulta DNS la haga el servidor, así que los nombres internos funcionan y la consulta no se filtra desde tu propia red. Con --socks5 a secas el nombre se resuelve en tu máquina.

La configuración del navegador y las fugas de DNS están en cómo usar SSH como proxy SOCKS.

Varios puertos, o a través de un jump host

Repite la opción para abrir varios reenvíos sobre una misma conexión:

ssh -N -L 127.0.0.1:8080:localhost:80 -L 127.0.0.1:5433:localhost:5432 user@server

Cuando el servidor está detrás de un bastión, añade -J. El reenvío sale del último servidor de la cadena, así que es ese servidor el que resuelve el destino:

ssh -N -J user@bastion -L 127.0.0.1:5433:localhost:5432 user@db-server

Las cadenas de servidores se explican en cómo usar ProxyJump y un jump host.

Ejecutar un túnel sin shell

  • -N no ejecuta ningún comando remoto. La sesión solo lleva el túnel.
  • -f pasa a segundo plano después de autenticarse, de modo que todavía puedes escribir antes una frase de contraseña.
  • -o ExitOnForwardFailure=yes termina con un error si no se puede establecer el reenvío.
ssh -f -N -o ExitOnForwardFailure=yes -L 127.0.0.1:8080:localhost:80 user@server

Sin ExitOnForwardFailure, ssh muestra un aviso y sigue conectado sin un túnel que funcione, algo que desconcierta dentro de un script. Con esa opción, un puerto local ya en uso termina con código de salida 255 y Could not request local forwarding, y un puerto remoto en uso termina con remote port forwarding failed for listen port.

Una trampa que vimos en las pruebas: con un -L 8080:... sin dirección, ssh escucha tanto en 127.0.0.1 como en ::1. Si solo una de ellas está ocupada, por ejemplo por otro programa en IPv4, ssh muestra bind [127.0.0.1]:8080: Address already in use y sigue funcionando incluso con ExitOnForwardFailure=yes. Tu aplicación podría entonces llegar a ese otro programa. Escribir la dirección, como en -L 127.0.0.1:8080:..., hace que sea un fallo de verdad, así que usa esa forma en los scripts.

Para detener un túnel en segundo plano, busca su proceso y termínalo:

pgrep -f 'L 127.0.0.1:8080'
kill PID

Para los túneles que abres a menudo, un socket de control te deja detenerlos por su nombre con ssh -O exit. Consulta cómo guardar, iniciar y detener túneles SSH.

Para dejar un túnel en tu configuración, usa LocalForward, RemoteForward y DynamicForward:

Host dbtunnel
  HostName 203.0.113.10
  User user
  LocalForward 127.0.0.1:8080 localhost:80
  ExitOnForwardFailure yes

Enlazar solo a localhost

Un puerto reenviado está abierto para quien pueda llegar a él, y el servicio que hay detrás puede fiarse de las conexiones procedentes del servidor. Por defecto, ssh enlaza solo al loopback, así que déjalo así salvo que tengas un motivo.

Estos comandos abren el puerto local a toda tu red. Confirmamos que ambos escuchan en todas las interfaces:

ssh -N -L 0.0.0.0:8080:localhost:80 user@server
ssh -N -g -L 8080:localhost:80 user@server

Con -R, decide el servidor, mediante GatewayPorts en su configuración de sshd:

GatewayPorts-R 9000:...-R 0.0.0.0:9000:...
no (por defecto)Solo loopbackSolo loopback, sin ningún error
clientspecifiedSolo loopbackTodas las interfaces
yesTodas las interfacesTodas las interfaces, y 127.0.0.1:9000 también se abre en todas las interfaces

Así que si no se puede llegar desde fuera a tu puerto -R, el motivo habitual es GatewayPorts. Exponer a internet un servicio de tu portátil de esta forma rara vez es lo que quieres. Si lo necesitas, prefiere clientspecified y añade una regla de firewall.

Cuando el servidor lo rechaza

AllowTcpForwarding en la configuración de sshd del servidor controla esto. Los valores son yes (el valor por defecto), no, local (permite -L y -D) y remote (permite -R).

  • Un -L o -D bloqueado se conecta sin problema. El error aparece solo cuando algo usa el puerto: channel 2: open failed: administratively prohibited: open failed.
  • Un -R bloqueado dice remote port forwarding failed for listen port.

La página del manual señala que desactivar el reenvío aporta poco si los usuarios también tienen un shell, ya que pueden ejecutar su propio reenviador. Para limitar de verdad a alguien, quítale también el shell, como en un usuario solo de túnel para una base de datos.

Otros dos errores salen a menudo. Cuando el puerto local o remoto está ocupado, consulta Address already in use y remote port forwarding failed. Cuando se rechazan las conexiones a través del túnel, consulta administratively prohibited: open failed.

Preguntas

¿Qué diferencia hay entre ssh -L y ssh -R?

-L abre un puerto en tu equipo que lleva a algo que alcanza el servidor. -R abre un puerto en el servidor que lleva de vuelta a algo de tu equipo.

¿Cómo reenvío varios puertos con un solo comando ssh?

Repite la opción: ssh -N -L 127.0.0.1:8080:localhost:80 -L 127.0.0.1:5433:localhost:5432 user@server. Una sola conexión los lleva todos. En ~/.ssh/config, añade una línea LocalForward por puerto.

¿Cómo mantengo activo un túnel SSH?

Arráncalo con -N y -o ExitOnForwardFailure=yes, y deja que autossh o un servicio de systemd lo reinicie tras un corte. Consulta autossh y systemd.

¿Por qué el túnel conecta pero no funciona nada?

El servidor puede prohibir el reenvío con AllowTcpForwarding. Un -L o -D bloqueado conecta igual y solo falla al usar el puerto, con administratively prohibited: open failed.

¿Es seguro el reenvío de puertos SSH?

El tráfico va cifrado entre tu equipo y el servidor SSH. El tramo del servidor al destino no, y un puerto que escucha en 0.0.0.0 lo puede usar cualquiera en tu red, así que deja la dirección de escucha en 127.0.0.1.

Fuentes

Más en Túneles y jump hosts

Todas las guías