Configuring SSH Remote Port Forwarding to Bind to Specific IP Addresses for Multi-Host Access


2 views

When setting up SSH remote port forwarding (-R) across multiple backend machines through a hub server with multiple IP addresses, a common issue arises where the forwarded ports bind to all interfaces (0.0.0.0) despite specifying a particular bind_address. Let's examine the core problem and solutions.

Given your configuration:

# From machine-one
ssh -NR 100.200.130.121:22:localhost:22 root@hub-server.tld

# On hub-server.tld
netstat -tan | grep LISTEN
tcp        0      0 100.200.130.121:2222        0.0.0.0:*                   LISTEN
tcp        0      0 :::22                       :::*                        LISTEN

The key observation here is that even when explicitly specifying 100.200.130.121 as the bind_address, SSH still listens on all interfaces (0.0.0.0). This prevents you from simultaneously running separate SSH forwarding on other IPs of the same server.

First verify your SSH version:

ssh -V
OpenSSH_8.2p1, OpenSSL 1.1.1f  31 Mar 2020

Some older versions (pre-7.0) had limitations in interface-specific binding. Modern versions should support this functionality when properly configured.

Your current sshd_config contains:

GatewayPorts yes

This is correct, but we need additional parameters. Try these modifications:

# In /etc/ssh/sshd_config on hub-server.tld
GatewayPorts clientspecified
AllowTcpForwarding remote

Then restart SSH:

systemctl restart sshd

Use this enhanced syntax:

ssh -NR \*:100.200.130.121:2222:localhost:22 root@hub-server.tld

The backslash before the asterisk is crucial to prevent shell expansion. This tells SSH to bind specifically to the given IP.

If the SSH method still doesn't work as expected, implement iptables rules:

# For machine-one (100.200.130.121)
iptables -t nat -A PREROUTING -d 100.200.130.121 -p tcp --dport 22 -j DNAT --to-destination machine-one:22
iptables -A FORWARD -p tcp -d machine-one --dport 22 -j ACCEPT

# Repeat for other machines with their respective IPs
iptables -t nat -A PREROUTING -d 100.200.130.122 -p tcp --dport 22 -j DNAT --to-destination machine-two:22
iptables -A FORWARD -p tcp -d machine-two --dport 22 -j ACCEPT

After implementation, verify with:

ss -tulpn | grep 22
# Should show specific IP bindings

# Test connectivity
telnet 100.200.130.121 22
telnet 100.200.130.122 22

For permanent solutions:

# For SSH method: Add to ~/.ssh/config
Host hub-forward-1
  HostName hub-server.tld
  RemoteForward \*:100.200.130.121:2222 localhost:22

# For iptables method
iptables-save > /etc/iptables.rules
# Add to /etc/rc.local
iptables-restore < /etc/iptables.rules

When setting up reverse SSH tunnels (-R) on a server with multiple IP addresses, you'll notice SSH automatically binds to all interfaces (0.0.0.0) regardless of specifying a bind_address. This behavior persists even with GatewayPorts enabled in sshd_config.

# Problematic behavior example
ssh -NR 100.200.130.121:22:localhost:22 user@hub-server.tld
# Still listens on all interfaces:
netstat -tuln | grep 22
tcp    0    0 0.0.0.0:22    0.0.0.0:*    LISTEN

The SSH protocol implements remote port forwarding (-R) with these characteristics:

  • bind_address parameter primarily controls who can connect (security layer)
  • Actual socket binding occurs at the kernel level before SSH process starts
  • Legacy behavior maintains compatibility with older implementations

Solution 1: Combining SSH with iptables

Create targeted firewall rules to restrict access:

# On hub-server.tld:
iptables -A INPUT -p tcp --dport 22 -d 100.200.130.121 -j ACCEPT
iptables -A INPUT -p tcp --dport 22 -j DROP

# Verify with:
iptables -L INPUT -n -v

Solution 2: Port Multiplexing with socat

Use socat as a smart proxy:

# On hub-server.tld:
socat TCP-LISTEN:22,bind=100.200.130.121,fork,reuseaddr TCP:localhost:2222 &

# SSH command becomes:
ssh -NR 2222:localhost:22 user@hub-server.tld

Solution 3: Systemd Socket Activation

For systemd-based systems:

# Create /etc/systemd/system/ssh-port121.socket
[Socket]
ListenStream=100.200.130.121:22
BindIPv6Only=both
Accept=yes

[Install]
WantedBy=sockets.target

Here's a complete setup for three machines:

# On hub-server.tld's sshd_config:
Match Host machine-one
    AllowTcpForwarding yes
    PermitOpen 100.200.130.121:22

# machine-one's connection command:
autossh -M 0 -NR 12122:localhost:22 -p 2222 user@hub-server.tld

# Corresponding iptables rule:
iptables -t nat -A PREROUTING -d 100.200.130.121 -p tcp --dport 22 \
    -j DNAT --to-destination :12122

When implementing these solutions:

  • Monitor connection latency with 'ss -ti'
  • Consider using PersistentKeepalive in SSH config
  • For high-traffic services, test kernel TCP stack settings

Always verify:

  • Firewall rules persist after reboots
  • SSH GatewayPorts aren't exposed to public interfaces
  • Regularly audit port binding behavior