Un túnel SSH reenvía un puerto TCP a través de una conexión SSH cifrada. Sirve para llegar a servicios que no están expuestos a Internet (una base de datos que solo escucha en localhost), publicar temporalmente un servicio de tu equipo en un servidor remoto o navegar a través de un servidor como si fuera un proxy. En este tutorial usarás los tres tipos de reenvío (local, remoto y dinámico), los guardarás en ~/.ssh/config y dejarás un túnel permanente con autossh y systemd en Ubuntu 24.04.

Requisitos previos

Para seguir esta guía necesitas:

  • Un servidor con Ubuntu 24.04 LTS accesible por SSH, por ejemplo un VPS de CubePath. En los ejemplos se llama your_server_ip.
  • Un usuario no root con privilegios sudo en ese servidor (your_user) y acceso con clave SSH.
  • Un equipo local con cliente OpenSSH (Linux, macOS o Windows 10/11 con OpenSSH).
  • Para los ejemplos de base de datos, un servicio escuchando solo en local en el servidor, como PostgreSQL en 127.0.0.1:5432. Puedes sustituirlo por cualquier otro servicio TCP.

Los tres tipos de túnel que verás:

TipoOpciónDirecciónUso típico
Local-LTu equipo → servidor → destinoAcceder a una base de datos o panel interno
Remoto-RServidor → tu equipoEnseñar un servicio local desde el servidor
Dinámico-DTu equipo → cualquier destino vía servidorProxy SOCKS para el navegador o curl

Paso 1: Comprobar que el servidor permite el reenvío

El reenvío de puertos lo controla el demonio sshd del servidor. En Ubuntu está permitido por defecto, pero conviene comprobarlo antes de buscar errores en otro sitio. Conéctate al servidor y muestra la configuración efectiva:

sudo sshd -T | grep -Ei 'allowtcpforwarding|gatewayports|permitopen'
gatewayports no
allowtcpforwarding yes
permitopen any
  • allowtcpforwarding yes: se permiten los túneles. Si ves no, algún archivo lo está desactivando.
  • gatewayports no: los puertos de un túnel remoto (-R) solo escuchan en 127.0.0.1 del servidor. Lo cambiarás en el paso 4 si lo necesitas.

Si allowtcpforwarding aparece como no, busca dónde se define:

sudo grep -ri 'AllowTcpForwarding' /etc/ssh/sshd_config /etc/ssh/sshd_config.d/

Cambia el valor a yes en ese archivo, valida la sintaxis y reinicia el servicio:

sudo sshd -t && sudo systemctl restart ssh

sshd -t no muestra nada si la configuración es correcta.

Paso 2: Crear un túnel local (-L)

Un túnel local abre un puerto en tu equipo y reenvía todo lo que llega a él hacia un destino visto desde el servidor. La sintaxis es:

ssh -L [dirección_local:]puerto_local:host_destino:puerto_destino usuario@servidor

El host_destino se resuelve en el servidor, así que localhost significa el propio servidor. Para llegar a PostgreSQL, que en el servidor solo escucha en 127.0.0.1:5432, ejecuta en tu equipo local:

ssh -N -L 5433:localhost:5432 your_user@your_server_ip
  • -N indica que no quieres ejecutar ningún comando remoto, solo mantener el túnel.
  • Se usa el puerto local 5433 para no chocar con un PostgreSQL que tengas instalado en tu equipo.

El comando se queda en primer plano mientras el túnel esté abierto. En otra terminal, comprueba que el puerto escucha:

ss -tln | grep 5433
LISTEN 0      128        127.0.0.1:5433       0.0.0.0:*
LISTEN 0      128            [::1]:5433          [::]:*

Ahora conéctate como si la base de datos fuera local:

psql -h 127.0.0.1 -p 5433 -U your_db_user your_db

Por defecto el puerto local solo escucha en 127.0.0.1, que es lo que quieres casi siempre. Si otras máquinas de tu red deben usar el túnel, indica la dirección de escucha explícitamente (por ejemplo -L 0.0.0.0:5433:localhost:5432), sabiendo que cualquiera que llegue a ese puerto accederá al servicio.

Cierra el túnel con Ctrl+C.

Reenviar varios puertos en una conexión

Puedes repetir -L tantas veces como necesites:

ssh -N -L 5433:localhost:5432 -L 6380:localhost:6379 your_user@your_server_ip

Llegar a un host interno a través de un servidor de salto

El destino no tiene que ser el propio servidor. Si 10.0.0.20 es una base de datos en la red privada del servidor, el servidor hace de intermediario:

ssh -N -L 5433:10.0.0.20:5432 your_user@your_server_ip

El tráfico va cifrado de tu equipo al servidor; el último tramo (servidor → 10.0.0.20) viaja sin el cifrado de SSH por la red privada.

Paso 3: Ejecutar el túnel en segundo plano

Para no ocupar una terminal, añade -f para que SSH pase a segundo plano tras autenticarse, y ExitOnForwardFailure para que falle si el puerto local ya está ocupado en lugar de quedarse conectado sin túnel:

ssh -f -N -o ExitOnForwardFailure=yes -L 5433:localhost:5432 your_user@your_server_ip

Localiza el proceso para cerrarlo más tarde:

pgrep -af 'ssh -f -N'
48211 ssh -f -N -o ExitOnForwardFailure=yes -L 5433:localhost:5432 your_user@your_server_ip
kill 48211

Paso 4: Crear un túnel remoto (-R)

Un túnel remoto hace lo contrario: abre un puerto en el servidor y lo reenvía a un destino visto desde tu equipo. Es útil para enseñar una aplicación que corre en tu portátil (por ejemplo en localhost:3000) o para dar acceso a una máquina que está detrás de NAT.

Desde tu equipo local:

ssh -N -R 8080:localhost:3000 your_user@your_server_ip

En el servidor, el puerto 8080 escucha solo en local porque GatewayPorts vale no:

ss -tln | grep 8080
LISTEN 0      128        127.0.0.1:8080       0.0.0.0:*

Desde el propio servidor ya puedes probarlo con curl http://127.0.0.1:8080. Si quieres que el puerto sea accesible desde Internet, crea un archivo de configuración en el servidor:

sudo nano /etc/ssh/sshd_config.d/10-gatewayports.conf
GatewayPorts clientspecified

clientspecified deja que el cliente elija la dirección de escucha, en lugar de abrirla siempre a todas las interfaces como haría yes. Valida y reinicia:

sudo sshd -t && sudo systemctl restart ssh

Abre el puerto en el firewall del servidor si usas UFW:

sudo ufw allow 8080/tcp

Vuelve a crear el túnel indicando la dirección pública de escucha:

ssh -N -R 0.0.0.0:8080:localhost:3000 your_user@your_server_ip

Ahora http://your_server_ip:8080 muestra la aplicación de tu equipo. Cuando termines, cierra el túnel y elimina la regla con sudo ufw delete allow 8080/tcp.

Paso 5: Crear un proxy SOCKS (-D)

El reenvío dinámico abre en tu equipo un proxy SOCKS5. Cada aplicación que lo use decide a qué destino conectarse y el servidor hace las conexiones por ella. Sirve para navegar con la IP del servidor o proteger tu tráfico en una wifi pública.

ssh -N -D 1080 your_user@your_server_ip

Compruébalo consultando tu IP pública a través del proxy. Deberías ver la IP del servidor, no la tuya:

curl --socks5-hostname 127.0.0.1:1080 https://api.ipify.org
your_server_ip

--socks5-hostname hace que también la resolución DNS se haga en el servidor. En Firefox, configura el proxy en Ajustes > Configuración de red como SOCKS v5, host 127.0.0.1, puerto 1080, y marca Usar proxy DNS cuando se utilice SOCKS v5.

Paso 6: Guardar los túneles en ~/.ssh/config

Escribir las opciones cada vez es propenso a errores. Define los túneles en el archivo de configuración del cliente, en tu equipo local:

nano ~/.ssh/config
Host db-tunnel
    HostName your_server_ip
    User your_user
    IdentityFile ~/.ssh/id_ed25519
    LocalForward 5433 localhost:5432
    LocalForward 6380 localhost:6379
    ExitOnForwardFailure yes
    ServerAliveInterval 30
    ServerAliveCountMax 3

Host socks-proxy
    HostName your_server_ip
    User your_user
    DynamicForward 1080

Host internal-db
    HostName 10.0.0.20
    User your_user
    ProxyJump your_user@your_server_ip
  • ServerAliveInterval y ServerAliveCountMax cierran la conexión si el servidor deja de responder durante unos 90 segundos, en lugar de dejar un túnel colgado.
  • ProxyJump permite abrir una sesión SSH directamente en un host interno pasando por el servidor, sin túnel manual. Equivale a ssh -J.

Ahora abrir un túnel es tan corto como:

ssh -N db-tunnel

Y la conexión a un host interno:

ssh internal-db

Paso 7: Mantener un túnel permanente con autossh y systemd

Para un túnel que debe estar siempre activo (por ejemplo, una aplicación que lee de una base de datos remota), usa autossh, que vuelve a lanzar SSH si la conexión se cae, y gestiónalo con systemd para que arranque con el sistema. Estos pasos se hacen en la máquina que abre el túnel, que aquí es un equipo con Ubuntu 24.04.

Instala autossh:

sudo apt update
sudo apt install autossh

El servicio se ejecutará sin interacción, así que necesita una clave sin frase de paso y el host ya presente en known_hosts. Crea una clave dedicada para el túnel:

ssh-keygen -t ed25519 -f ~/.ssh/tunnel_ed25519 -N '' -C 'autossh tunnel'
ssh-copy-id -i ~/.ssh/tunnel_ed25519.pub your_user@your_server_ip

Conéctate una vez con esa clave para aceptar la huella del servidor y confirmar que funciona:

ssh -i ~/.ssh/tunnel_ed25519 your_user@your_server_ip exit

Crea la unidad de systemd:

sudo nano /etc/systemd/system/ssh-tunnel-db.service
[Unit]
Description=Túnel SSH a PostgreSQL en your_server_ip
After=network-online.target
Wants=network-online.target

[Service]
User=your_user
Environment=AUTOSSH_GATETIME=0
ExecStart=/usr/bin/autossh -M 0 -N \
    -o ServerAliveInterval=30 \
    -o ServerAliveCountMax=3 \
    -o ExitOnForwardFailure=yes \
    -i /home/your_user/.ssh/tunnel_ed25519 \
    -L 5433:localhost:5432 \
    your_user@your_server_ip
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target
  • -M 0 desactiva el puerto de monitorización propio de autossh y delega la detección de caídas en ServerAliveInterval, que es la forma recomendada hoy.
  • AUTOSSH_GATETIME=0 hace que autossh reintente aunque el primer intento falle, algo habitual justo después del arranque.

Carga la unidad y arráncala:

sudo systemctl daemon-reload
sudo systemctl enable --now ssh-tunnel-db.service

Comprueba su estado y que el puerto escucha:

systemctl status ssh-tunnel-db.service --no-pager
ss -tln | grep 5433
● ssh-tunnel-db.service - Túnel SSH a PostgreSQL en your_server_ip
     Loaded: loaded (/etc/systemd/system/ssh-tunnel-db.service; enabled; preset: enabled)
     Active: active (running) since Thu 2026-09-24 10:12:03 UTC; 8s ago
LISTEN 0      128        127.0.0.1:5433       0.0.0.0:*

Para limitar lo que puede hacer esa clave en el servidor, añade restricciones delante de ella en ~/.ssh/authorized_keys del servidor:

restrict,port-forwarding,permitopen="localhost:5432" ssh-ed25519 AAAA... autossh tunnel

Con esto la clave no puede abrir una shell ni reenviar a ningún destino que no sea localhost:5432.

Solución de problemas

bind [127.0.0.1]:5433: Address already in use: el puerto local ya está ocupado, a menudo por un túnel anterior que sigue en segundo plano. Localízalo con ss -tlnp | grep 5433 y ciérralo, o usa otro puerto.

channel 2: open failed: administratively prohibited: el servidor rechaza el reenvío. Revisa AllowTcpForwarding (paso 1) y, si usas permitopen en authorized_keys, que el destino coincida exactamente.

channel 2: open failed: connect failed: Connection refused: el túnel funciona, pero en el destino no hay nada escuchando en ese puerto. Compruébalo en el servidor con sudo ss -tlnp | grep 5432.

El túnel remoto solo responde desde el servidor: GatewayPorts sigue en no o no has indicado 0.0.0.0 en -R. Revisa el paso 4 y el firewall.

El servicio de autossh se reinicia en bucle: revisa journalctl -u ssh-tunnel-db -n 50 --no-pager. Lo más habitual es Host key verification failed (no aceptaste la huella con ese usuario) o permisos incorrectos en la clave (chmod 600 ~/.ssh/tunnel_ed25519).

Para ver en detalle qué hace el cliente, añade -v a cualquier comando ssh.

Conclusión

Has creado túneles locales para llegar a servicios internos, túneles remotos para publicar un servicio de tu equipo, un proxy SOCKS y un túnel permanente gestionado por systemd con una clave restringida. Los túneles SSH son ideales para accesos puntuales o para unos pocos servicios concretos.

Como siguientes pasos puedes:

  • Usar sshuttle si necesitas enrutar subredes completas a través de SSH sin configurar cada puerto.
  • Montar una VPN con WireGuard cuando varios equipos necesiten acceso permanente a una red privada.
  • Reforzar el servidor SSH desactivando el acceso por contraseña y el login de root.