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