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
sudoen 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:
| Tipo | Opción | Dirección | Uso típico |
|---|---|---|---|
| Local | -L | Tu equipo → servidor → destino | Acceder a una base de datos o panel interno |
| Remoto | -R | Servidor → tu equipo | Enseñar un servicio local desde el servidor |
| Dinámico | -D | Tu equipo → cualquier destino vía servidor | Proxy 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 vesno, algún archivo lo está desactivando.gatewayports no: los puertos de un túnel remoto (-R) solo escuchan en127.0.0.1del 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
-Nindica que no quieres ejecutar ningún comando remoto, solo mantener el túnel.- Se usa el puerto local
5433para 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.
Advertenciaun puerto remoto abierto a Internet expone directamente el servicio de tu equipo. Úsalo solo de forma temporal o con un proxy inverso con TLS y autenticación delante.
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
ServerAliveIntervalyServerAliveCountMaxcierran la conexión si el servidor deja de responder durante unos 90 segundos, en lugar de dejar un túnel colgado.ProxyJumppermite abrir una sesión SSH directamente en un host interno pasando por el servidor, sin túnel manual. Equivale assh -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 0desactiva el puerto de monitorización propio de autossh y delega la detección de caídas enServerAliveInterval, que es la forma recomendada hoy.AUTOSSH_GATETIME=0hace 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
sshuttlesi 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.
