Tunneling under SSH

Use an SSH layer-3 TUN plus NAT when a remote box can accept SSH from you, but you want its outbound traffic to leave through your machine. This is not a port forward (-L/-R/-D). It is a point-to-point tunnel (-w) that needs root on both ends and a host route so you do not kill the SSH session when you replace the remote default route.

1. Allow TUN on the SSH server

On the remote host (the SSH server you connect to), set PermitTunnel yes in sshd_config (or a drop-in under /etc/ssh/sshd_config.d/), then restart the daemon. Debian/Ubuntu use the ssh unit; RHEL-family uses sshd:

# /etc/ssh/sshd_config
PermitTunnel yes

sudo systemctl restart ssh    # Debian/Ubuntu
# sudo systemctl restart sshd # RHEL, Fedora, Rocky

2. Open the tunnel as root, with your user key

sudo ssh reads /root/.ssh, not your key. Pass your identity file. -w 0:0 creates tun0 on both sides (use -w 1:1 if tun0 is already taken):

sudo ssh -w 0:0 -i /home/$USER/.ssh/id_ed25519 [email protected]

Leave that session up. Configure addresses from another terminal (or tmux).

3. Address tun0 on both sides

Local (the box with the internet you want to share):

sudo ip addr add 10.0.0.1/24 dev tun0
sudo ip link set tun0 up

Remote:

sudo ip addr add 10.0.0.2/24 dev tun0
sudo ip link set tun0 up

4. NAT on the local box

Pick the interface that actually has the default route. eth0 is often wrong on modern systems:

WAN=$(ip -4 route show default | awk '{print $5; exit}')
echo 1 | sudo tee /proc/sys/net/ipv4/ip_forward
sudo iptables -t nat -A POSTROUTING -o "$WAN" -s 10.0.0.0/24 -j MASQUERADE

On nftables-only hosts, masquerade with nft instead of iptables. Persist forwarding with net.ipv4.ip_forward=1 in /etc/sysctl.d/ only if you want this to survive reboot.

5. Pin the SSH client, then change the remote default route

If you ip route replace default on the remote first, SSH return traffic follows the tunnel and the session dies. Add a /32 for the client via the old gateway, then replace default:

# on the remote, before touching default
CLIENT=$(echo "$SSH_CLIENT" | awk '{print $1}')
GW=$(ip -4 route show default | awk '{print $3; exit}')
DEV=$(ip -4 route show default | awk '{print $5; exit}')
sudo ip route add "$CLIENT"/32 via "$GW" dev "$DEV"
sudo ip route replace default via 10.0.0.1 dev tun0

Verify

# on the remote
ping -c 3 8.8.8.8
curl -4 ifconfig.me    # should be the local machine's public IP

Teardown

# remote: restore its original default, drop the /32
sudo ip route replace default via "$GW" dev "$DEV"
sudo ip route del "$CLIENT"/32

# local: remove NAT (use the same $WAN as above)
sudo iptables -t nat -D POSTROUTING -o "$WAN" -s 10.0.0.0/24 -j MASQUERADE

# then exit the sudo ssh -w session; tun0 goes away with it

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.