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


3 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