How to Configure SSHD to Bind Exclusively to a VPN Interface (tun0) for Dynamic IP Environments


15 views

When working with VPN connections through interfaces like tun0, administrators often need to restrict SSH access exclusively to the VPN tunnel. The traditional ListenAddress approach becomes problematic when:

  • The VPN-assigned IP is dynamic
  • Multiple tunnel connections may exist
  • Security policies require interface-level isolation

Modern Linux systems using systemd can leverage socket units for interface-specific binding:

# /etc/systemd/system/sshd-tun0.socket
[Unit]
Description=SSH Socket for tun0
ConditionPathExists=/sys/class/net/tun0

[Socket]
ListenStream=0.0.0.0:22
BindToInterface=tun0
IPAddressAllow=10.8.0.0/24
SocketMode=0600
Backlog=128

[Install]
WantedBy=sockets.target

For complex VPN setups, consider isolating sshd in a network namespace:

# Create namespace
ip netns add vpnspace

# Move interface to namespace
ip link set tun0 netns vpnspace

# Launch sshd in namespace
ip netns exec vpnspace /usr/sbin/sshd -f /etc/ssh/sshd_config_tun0

When direct binding isn't possible, use packet filtering:

iptables -A INPUT -p tcp --dport 22 -i eth0 -j DROP
iptables -A INPUT -p tcp --dport 22 -i tun0 -j ACCEPT
  • Verify interface existence: ip link show tun0
  • Check binding: ss -tulnp | grep sshd
  • Monitor connections: journalctl -u sshd --follow

When working with VPN configurations, especially OpenVPN's tun0 interface, administrators often need to restrict SSH access exclusively through the VPN tunnel. The standard ListenAddress directive in /etc/ssh/sshd_config typically expects a static IP address, which becomes problematic when dealing with dynamic VPN-assigned addresses.

Modern Linux systems allow binding services to specific network interfaces using either of these approaches:


# Method 1: Systemd socket binding (recommended for systemd systems)
[Unit]
Description=SSH Socket for tun0
[Socket]
ListenStream=0.0.0.0:22
BindToInterface=tun0
SocketUser=root
SocketGroup=root
SocketMode=0600
[Install]
WantedBy=sockets.target

# Method 2: IPTables firewall rules
iptables -A INPUT -p tcp --dport 22 ! -i tun0 -j DROP

For systemd-based distributions:


1. Create /etc/systemd/system/sshd-tun0.socket
2. Add the configuration shown in Method 1 above
3. Reload systemd: systemctl daemon-reload
4. Disable default sshd socket: systemctl stop sshd.socket
5. Enable new socket: systemctl enable --now sshd-tun0.socket

For non-systemd systems:


1. Add the iptables rule from Method 2
2. Make it persistent (Debian/Ubuntu):
   iptables-save > /etc/iptables/rules.v4
3. For CentOS/RHEL:
   service iptables save

After implementation, verify the binding with these commands:


# Check listening ports
ss -tulpn | grep sshd

# Verify interface binding (systemd method)
systemctl status sshd-tun0.socket

# Test connection from non-VPN interface
ssh user@server_ip

For environments where the VPN might disconnect:


# Create a watchdog script (/usr/local/bin/sshd-interface-watchdog)
#!/bin/bash
while true; do
    if ! ip link show tun0 > /dev/null 2>&1; then
        systemctl stop sshd
    else
        systemctl start sshd
    fi
    sleep 30
done

For maximum isolation:


# Create network namespace
ip netns add vpnspace

# Move tun0 interface to namespace
ip link set tun0 netns vpnspace

# Start SSH in namespace
ip netns exec vpnspace /usr/sbin/sshd -f /etc/ssh/sshd_config.vpn