SSH Tunneling — Part 2: Remote Port Forwarding.

Learn how to route connections through an encrypted SSH tunnel using remote port forwarding (`-R`). Explore reverse tunnels, bind addresses, `GatewayPorts`, bastion hosts, connection management, troubleshooting, and security best practices.

SSH Tunneling — Part 2: Remote Port Forwarding.
Photo by yannick Coffi —see +< @ yannickcoffi @ PRINTS





In Part 1: Local Port Forwarding, we used SSH local port forwarding (-L) to access remote services from our local machine.

In this article, we'll reverse the forwarding direction.
How reverse SSH tunnels make local services accessible from remote systems

Remote port forwarding (-R) creates a listening port on the remote SSH server and forwards incoming connections through the SSH tunnel to a destination reachable from the machine running the SSH client.

This technique, commonly called a reverse SSH tunnel, is useful when a local service needs to be accessed remotely but cannot accept direct inbound connections.



1. Understanding Remote Port Forwarding

SSH supports three primary forwarding modes:

  • Local forwarding (-L): Creates a listener on the local machine that forwards traffic to a remote-side destination.
  • Remote forwarding (-R): Creates a listener on the remote side that forwards traffic to a client-side destination.
  • Dynamic forwarding (-D): Creates a SOCKS proxy for dynamically routing TCP connections through SSH.

The main difference between -L and -R is where the listening port is created.

Local forwarding (-L):

Local Machine                  Remote Server

localhost:8080 ─── SSH ──────► localhost:3000

Remote forwarding (-R):

Remote Server                  Local Machine

localhost:8080 ─── SSH ──────► localhost:3000

The SSH connection still originates from the local machine and connects to the remote server. With -R, however, the listening port is created on the remote side.

2. The Basic SSH Command

The general syntax is:

$ ssh -N -R [REMOTE_BIND_ADDRESS:]REMOTE_PORT:DESTINATION_HOST:DESTINATION_PORT user@REMOTE_SERVER

For example, suppose a web application is running on your local machine at localhost:2399, and you want to access it through a remote server.

Create the reverse tunnel:

$ ssh -N -R 2393:localhost:2399 user@REMOTE_SERVER_IP

This command establishes an SSH connection to the remote server and requests a listener on port 2393.

The traffic flows as follows:

Remote Server
127.0.0.1:2393
      │
      │ SSH tunnel
      ▼
Your Local Machine
127.0.0.1:2399
      │
      ▼
Web Application

A process on the remote server can now connect to 127.0.0.1:2393, and SSH forwards the connection to port 2399 on your local machine.

Understanding the options

  • -R: Enables remote port forwarding.
  • -N: Prevents execution of a remote command or opening of an interactive shell.
  • 2393: The listening port on the remote server.
  • localhost:2399: The destination as seen from the machine running the SSH client.

The key distinction: the SSH connection travels from your machine to the remote server, but connections arriving at the remote listener are forwarded back through that existing connection.

3. Why Reverse SSH Tunneling Is Useful

Reverse tunneling is particularly useful when your machine cannot accept inbound connections because it is behind:

  • Network Address Translation (NAT).
  • A home router or corporate firewall.
  • Carrier-Grade NAT (CGNAT).
  • A restrictive network.

Normally, a remote server cannot directly connect to a service on your laptop.

However, if your laptop can establish an outbound SSH connection, it can create a reverse tunnel.

Your Laptop
     │
     │ Outbound SSH connection
     ▼
Remote Server
     │
     │ Connection to forwarded port
     ▼
SSH Tunnel
     │
     ▼
Your Laptop's Service

No direct inbound connection to your laptop is required.

Common use cases include remote development, temporary service access, testing web applications, and reaching services behind restrictive network boundaries.

4. Forwarding to Another Host

The destination does not have to be localhost. It can be another host reachable from the machine running the SSH client.

For example:

$ ssh -N -R 8080:10.10.20.50:80 user@REMOTE_SERVER

The remote server listens on port 8080. Incoming connections travel through SSH to 10.10.20.50:80, accessed from your local machine's network environment.

Remote Server
127.0.0.1:8080
      │
      │ SSH tunnel
      ▼
Your Local Machine
      │
      ▼
10.10.20.50:80

This is useful when your local machine can reach an internal service that the remote server cannot access directly.

5. Controlling Access to the Remote Port

By default, OpenSSH remote forwarding typically binds the listening port to the remote server's loopback interface.

For example:

$ ssh -N -R 8080:localhost:2399 user@REMOTE_SERVER

The listener is normally accessible through:

127.0.0.1:8080

This means processes running on the remote server can access the forwarded service, but other machines generally cannot connect directly to that port.

To specify loopback binding explicitly:

$ ssh -N -R 127.0.0.1:8080:localhost:2399 user@REMOTE_SERVER

This is the recommended default when the forwarded service only needs to be accessed locally on the remote server.

6. Understanding GatewayPorts

The SSH server's GatewayPorts setting controls whether remote forwarded ports can accept connections from other machines.

It is configured in:

/etc/ssh/sshd_config

The main options are:

GatewayPorts no

This is the default in OpenSSH.

Remote forwarded ports are restricted to loopback addresses, making them accessible only from the remote server itself.

GatewayPorts yes

Allows remote forwarded ports to bind to wildcard addresses, potentially making them accessible through the server's network interfaces.

For example:

0.0.0.0:8080

If the server is publicly reachable and its firewall permits access, the forwarded service may become accessible from the internet.

GatewayPorts clientspecified

Allows the SSH client to request a specific remote listening address, subject to server configuration and available interfaces.

For example:

$ ssh -N -R 0.0.0.0:8080:localhost:2399 user@REMOTE_SERVER

This requests a listener on all remote IPv4 interfaces. It requires compatible server configuration, such as GatewayPorts clientspecified.

Important: changing GatewayPorts affects the exposure of forwarded services. Enable broader access only when necessary, and restrict it with appropriate firewall rules and SSH access controls.

7. Security Considerations

A reverse tunnel can provide convenient access to a private service, but it can also expose that service to unintended users.

For example:

Private Service
127.0.0.1:2399
      │
      │ SSH tunnel
      ▼
Remote Server
Port 8080
      │
      ▼
Potential external access

If the remote listener is bound to a public interface, anyone who can reach that port may be able to access the forwarded service.

Follow these practices:

  • Bind remote forwards to 127.0.0.1 unless external access is required.
  • Restrict SSH access to authorized users.
  • Apply firewall rules to limit access to forwarded ports.
  • Avoid exposing administrative interfaces or sensitive services.
  • Audit persistent tunnels and their destinations.
  • Protect SSH keys and use least privilege.
  • Keep SSH clients, servers, and destination applications updated.

A reverse tunnel is a network access mechanism, not a substitute for application security.

8. Running a Reverse Tunnel in the Background

To run the tunnel without keeping an interactive terminal session open, use -f:

$ ssh -f -N \
$   -R 127.0.0.1:2393:localhost:2399 \
$   user@REMOTE_SERVER

The SSH process moves into the background after authentication.

The remote server can then access the local application through:

127.0.0.1:2393

To inspect listening ports on the remote Linux server:

$ ss -lntp

Depending on your permissions, process details may require elevated privileges.

9. Keeping Reverse Tunnels Alive

For long-running tunnels, SSH keepalives help detect unresponsive connections.

$ ssh -N \
$   -o ServerAliveInterval=30 \
$   -o ServerAliveCountMax=3 \
$   -R 127.0.0.1:2393:localhost:2399 \
$   user@REMOTE_SERVER

The options configure SSH to send keepalive messages every 30 seconds and terminate the connection after three consecutive unanswered messages.

Keepalives detect failures but do not automatically reconnect a terminated session.

For automatic recovery, use autossh:

$ autossh -M 0 -N \
$   -o ServerAliveInterval=30 \
$   -o ServerAliveCountMax=3 \
$   -R 127.0.0.1:2393:localhost:2399 \
$   user@REMOTE_SERVER

For production environments, consider managing persistent tunnels through a service manager such as systemd.

10. Troubleshooting

If a reverse tunnel fails, enable verbose SSH output:

$ ssh -vvv -N \
$   -R 127.0.0.1:2393:localhost:2399 \
$   user@REMOTE_SERVER

On the remote server, check whether the listening port exists:

$ ss -lntp

Test the forwarded endpoint from the remote server:

$ curl http://127.0.0.1:2393

If the connection fails, verify that the destination service is reachable from the machine running the SSH client:

$ curl http://127.0.0.1:2399

Common causes include:

  • The local application is not running.
  • The destination host or port is incorrect.
  • The remote listening port is already occupied.
  • The SSH server rejects remote forwarding.
  • The requested bind address is not permitted.
  • Firewall rules prevent access to the remote listener.

Troubleshooting principle: verify the remote listener, the SSH connection, the destination service, and any applicable network restrictions separately.

Quick Reference

Basic reverse tunnel

$ ssh -N -R 8080:localhost:3000 user@server

Explicit loopback binding

$ ssh -N -R 127.0.0.1:8080:localhost:3000 user@server

Forward to another host reachable from your machine

$ ssh -N -R 8080:10.10.20.50:80 user@server

Background tunnel

$ ssh -f -N -R 127.0.0.1:8080:localhost:3000 user@server

Debugging

$ ssh -vvv -N -R 127.0.0.1:8080:localhost:3000 user@server



The Mental Model

Local and remote forwarding differ primarily in where the listener is created.

Local forwarding (-L):

Local Listener
      │
      ▼
Remote Destination

Remote forwarding (-R):

Remote Listener
      │
      ▼
Destination Reachable from the SSH Client

Remember:

  • -L: Listen locally, forward remotely.
  • -R: Listen remotely, forward back through SSH.

The SSH connection itself still originates from the client:

Local Machine ──────────► SSH Server

With remote forwarding, connections arriving at the remote listener travel back through that existing connection to the destination.

We hope this was helpful.
Keep learning. Keep exploring. Stay sharp.

What's Next?

In Part 1, we explored local port forwarding (-L).
In this article, we used remote port forwarding (-R) to make local services accessible through a remote SSH server without requiring direct inbound connectivity.

Next, we'll move beyond fixed port mappings with dynamic port forwarding (-D) and SOCKS proxies, allowing applications to route TCP connections through an SSH server.

Keep Us Caffeinated  ⦿ ⦿
Spotify Logo
↑
SNUBmonkey Join our readers