When executing commands like:
ssh -N -f -R127.0.2.3:23000:127.1.2.3:23000 user@remote
Many developers expect the tunnel to bind to the specified loopback address (127.0.2.3) on the remote server. However, SSH defaults to binding only to 127.0.0.1 regardless of what you specify in the -R parameter.
This behavior stems from OpenSSH's security design:
- Remote port forwarding (-R) defaults to localhost binding only
- The GatewayPorts setting in sshd_config controls this behavior
- Modern SSH versions restrict bind addresses for security reasons
To achieve the desired binding behavior:
Server-side Configuration
Edit /etc/ssh/sshd_config
on the remote server:
# Enable binding to non-localhost addresses
GatewayPorts clientspecified
# Then restart sshd
sudo systemctl restart sshd
Alternative Workarounds
If you can't modify server configuration:
# Use socat to redirect after establishing the tunnel
ssh -N -f -R23000:127.1.2.3:23000 user@remote
# Then on remote server:
socat TCP-LISTEN:23000,bind=127.0.2.3,fork TCP:127.0.0.1:23000
Verify the binding with these commands:
# On remote server:
ss -tulnp | grep 23000
# Or alternatively:
netstat -tulnp | grep 23000
For multiple services on the same port:
# First tunnel
ssh -N -f -R127.0.2.1:443:service1.local:443 user@remote
# Second tunnel
ssh -N -f -R127.0.2.2:443:service2.local:443 user@remote
# Requires GatewayPorts clientspecified on server
When executing SSH remote port forwarding with specific bind addresses like:
ssh -N -f -R127.0.2.3:23000:127.1.2.3:23000 user@remote
Many developers expect the remote end to bind to the specified IP (127.0.2.3 in this case), but it defaults to 127.0.0.1 regardless of the configuration.
SSH servers typically restrict remote forwarding to localhost (127.0.0.1) by default for security reasons. This is controlled by the GatewayPorts
option in sshd_config.
To enable binding to specific loopback addresses:
# On the remote server's sshd_config
GatewayPorts clientspecified
# Then restart sshd
sudo systemctl restart sshd
Alternative approach using socat as a workaround:
# On remote server after standard SSH tunnel is established
socat TCP-LISTEN:23000,bind=127.0.2.3,reuseaddr,fork TCP:127.0.0.1:23000
For multiple services needing the same port:
# Tunnel 1
ssh -N -f -R23000:service1:23000 user@remote
# Tunnel 2 (using different IP)
ssh -N -f -R23001:service2:23000 user@remote
# Then on remote:
socat TCP-LISTEN:23000,bind=127.0.0.2,reuseaddr,fork TCP:127.0.0.1:23000
socat TCP-LISTEN:23000,bind=127.0.0.3,reuseaddr,fork TCP:127.0.0.1:23001
When modifying GatewayPorts
, be aware that:
- It potentially exposes services to other network interfaces
- Always combine with firewall rules
- Consider using SSH certificates for authentication
For temporary solutions, you can specify at connection time:
ssh -o GatewayPorts=yes -R127.0.2.3:23000:127.1.2.3:23000 user@remote
Note this requires server-side configuration to be permissive.
To check where your tunnels are actually bound:
# On remote server
ss -tulnp | grep 23000
# Or for BSD systems:
sockstat -l | grep 23000