VPN over SSH

From ArchWiki

There are several ways to set up a Virtual Private Network through SSH. Note that, while this may be useful from time to time, it may not be a full replacement for a regular VPN. See for example [1].

Using badvpn's tun2socks

Note: The badvpn project has been discontinued in August 2022, and the latest stable release is from April 2015.

badvpn is a collection of utilities for various VPN-related use cases.

Start SSH dynamic SOCKS proxy

First, we will set up a normal SSH dynamic socks proxy like usual:

$ ssh -TND 4711 <your_user>@<SSH_server>

Set up badvpn and tunnel interface

Afterwards, we can go ahead with setting up the TUN.

# ip tuntap add dev tun0 mode tun user <your_local_user>
# ip addr replace dev tun0
# badvpn-tun2socks --tundev tun0 --netif-ipaddr --netif-netmask --socks-server-addr localhost:4711

Now you have a working local tun0 interface which routes all traffic going into it through the SOCKS proxy you set up earlier.

Get traffic into the tunnel

All that's left to do now is to set up a local route to get some traffic into it. Let us set up a route that routes all traffic into it. We will need three routes:

  1. Route that goes to the SSH server that we use for the tunnel with a low metric.
  2. Route for DNS server (because tun2socks does not do UDP which is necessary for DNS) with a low metric.
  3. Default route for all other traffic with a higher metric than the other routes.

The idea behind setting the metrics specifically is because we need to ensure that the route picked to the SSH server is always direct because otherwise it would go back into the SSH tunnel which would cause a loop and we would lose the SSH connection as a result. Apart from that, we need to set an explicit DNS route because tun2socks does not tunnel UDP (required for DNS). We also need a new default route with a lower metric than your old default route so that traffic goes into the tunnel at all. With all of that said, let us get to work:

# ip route add <IP_of_SSH_server> via <IP_of_original_gateway> metric 5
# ip route add <IP_of_DNS_server> via <IP_of_original_gateway> metric 5
# ip route add default via metric 6

Now all traffic (except for DNS and the SSH server itself) should go through tun0.

OpenSSH's built in tunneling

OpenSSH has built-in TUN/TAP support using -w<local-tun-number>:<remote-tun-number>. Here, a layer 3/point-to-point/ TUN tunnel is described. It is also possible to create a layer 2/ethernet/TAP tunnel.

Enable forwarding for the TUN device

To enable forwarding for the TUN device, edit /etc/ssh/sshd_config and set PermitTunnel to yes, point-to-point or ethernet. Setting yes enables forwarding for both point-to-point and ethernet tunnels. See sshd_config(5) for details.

PermitTunnel yes

Then reload sshd.service.

Create tun interfaces using systemd-networkd




Once these files are created, enable them by restarting systemd-networkd.service.

Also, you may manage tun interfaces with ip tunnel command.

Creating interfaces in SSH command

SSH creates both interfaces automatically, but IP and routing should be configured after the connection is established.

ssh \
  -o PermitLocalCommand=yes \
  -o LocalCommand="sudo ifconfig tun5 pointopoint netmask" \
  -o ServerAliveInterval=60 \
  -w 5:5 vpn@example.com \
  'sudo ifconfig tun5 pointopoint netmask; echo tun0 ready'

Start SSH

ssh -f -w5:5 vpn@example.com -i ~/.ssh/key "sleep 1000000000"

or you may add keep-alive options if you are behind a NAT.

ssh -f -w5:5 vpn@example.com \
        -o ServerAliveInterval=30 \
        -o ServerAliveCountMax=5 \
        -o TCPKeepAlive=yes \
        -i ~/.ssh/key "sleep 1000000000"


  • ssh should have access rights to tun interface or permissions to create it. Check owner of tun interface and/or /dev/net/tun.
  • Obviously if you want to access a network rather than a single machine you should properly set up IP packet forwarding, routing and maybe a netfilter on both sides.
  • If you do not enable tunneling, you may get the following error when you want to create an SSH tunnel using -w:
channel 0: open failed: connect failed: open failed
Tunnel forwarding failed

Using PPP over SSH

pppd can easily be used to create a tunnel through an SSH server:

# pppd updetach noauth silent nodeflate pty "/usr/bin/ssh root@remote-gw /usr/sbin/pppd nodetach notty noauth" ipparam vpn

When the VPN is established, you can route traffic through it. To get access to an internal network:

# ip route add via

To route all Internet traffic through the tunnel, for example, to protect your communication on an unencrypted network, first add a route to the SSH server through your regular gateway:

# ip route add <remote-gw> via <current default gateway>

Next, replace the default route with the tunnel

# ip route replace default via

Helper script

pvpn (package pvpnAUR) is a wrapper script around pppd over SSH.

See also