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.
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:3000Remote forwarding (-R):
Remote Server Local Machine
localhost:8080 ─── SSH ──────► localhost:3000The 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_SERVERFor 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_IPThis 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 ApplicationA 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 ServiceNo 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_SERVERThe 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:80This 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_SERVERThe listener is normally accessible through:
127.0.0.1:8080This 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_SERVERThis 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_configThe 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:8080If 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_SERVERThis 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 accessIf 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.1unless 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_SERVERThe SSH process moves into the background after authentication.
The remote server can then access the local application through:
127.0.0.1:2393To inspect listening ports on the remote Linux server:
$ ss -lntpDepending 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_SERVERThe 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_SERVERFor 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_SERVEROn the remote server, check whether the listening port exists:
$ ss -lntpTest the forwarded endpoint from the remote server:
$ curl http://127.0.0.1:2393If the connection fails, verify that the destination service is reachable from the machine running the SSH client:
$ curl http://127.0.0.1:2399Common 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@serverExplicit loopback binding
$ ssh -N -R 127.0.0.1:8080:localhost:3000 user@serverForward to another host reachable from your machine
$ ssh -N -R 8080:10.10.20.50:80 user@serverBackground tunnel
$ ssh -f -N -R 127.0.0.1:8080:localhost:3000 user@serverDebugging
$ 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 DestinationRemote forwarding (-R):
Remote Listener
│
▼
Destination Reachable from the SSH ClientRemember:
-L: Listen locally, forward remotely.-R: Listen remotely, forward back through SSH.
The SSH connection itself still originates from the client:
Local Machine ──────────► SSH ServerWith 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.