Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 17 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save trepidacious/ef3d68014258843944e3df0570876031 to your computer and use it in GitHub Desktop.
Save trepidacious/ef3d68014258843944e3df0570876031 to your computer and use it in GitHub Desktop.
Raspberry Pi VPN Router

Raspberry Pi VPN Router

This is a quick-and-dirty guide to setting up a Raspberry Pi as a "router on a stick" to ExpressVPN.

This is adapted from SuperJamie's gist.

Requirements

I used a Raspberry Pi 3 - seems like the extra speed may be useful for running VPN.

Download of Raspbian Jessie Lite (e.g. I used 2016-11-25-raspbian-jessie-lite.zip from raspberrypi.org).

Installation and Basic Configuration of Raspbian

Install Raspbian image to SD card. Connect HDMI, keyboard and ethernet (to your LAN) to pi, power up. When boot is complete, log in as user pi, password raspberry. Update packages: sudo apt-get update Install avahi (may be already installed by default): sudo apt-get install avahi-daemon. This means you can reach the pi as raspberrypi.local on the LAN. Run config: sudo raspi-config.

  • First select Advanced options, then navigate to SSH, press Enter and select <YES>.
  • Next (optionally) configure the Memory Split to give 16Mb (the minimum) to the GPU. Finally, set password to something secure using passwd.

From now on you can disconnect the keyboard and HDMI and run the pi headless, administering it from another PC using SSH. To connect, use ssh pi@raspberrypi.local, with the password you set up above (or default raspberry if you didn't change it).

IP Addressing

My home network is setup as follows:

  • Internet Router: 192.168.0.1
  • Subnet Mask: 255.255.255.0
  • Router gives out DHCP range: 192.168.100-200

If your network range is different, that's fine, use your network range instead of mine. Note that you will need to adjust any 192.168.0.x addresses below to match your network.

I'm going to give my Raspberry Pi a static IP address of 192.168.0.42 by configuring /etc/network/interfaces like so (adjust address and gateway lines to match your network if necessary):

auto lo
iface lo inet loopback

auto eth0
allow-hotplug eth0
iface eth0 inet static
    address 192.168.0.42
    netmask 255.255.255.0
    gateway 192.168.0.1
    dns-nameservers 8.8.8.8 8.8.4.4

You can use WiFi if you like, there are plenty tutorials around the internet for setting that up, but this should do:

auto lo
iface lo inet loopback

auto eth0
allow-hotplug eth0
iface eth0 inet manual

auto wlan0
allow-hotplug wlan0
iface wlan0 inet static
    wpa-ssid "Your SSID"
    wpa-psk  "Your Password"
    address 192.168.0.42
    netmask 255.255.255.0
    gateway 192.168.0.1
    dns-nameservers 8.8.8.8 8.8.4.4

You only need one connection into your local network, don't connect both Ethernet and WiFi. I recommend Ethernet if possible.

NTP

Accurate time is important for the VPN encryption to work. If the VPN client's clock is too far off, the VPN server will reject the client.

You shouldn't have to do anything to set this up, the ntp service is installed and enabled by default.

Double-check your Pi is getting the correct time from internet time servers with ntpq -p, you should see at least one peer with a + or a * or an o, for example:

$ ntpq -p
     remote           refid      st t when poll reach   delay   offset  jitter
==============================================================================
-0.time.xxxx.com 104.21.137.30    2 u   47   64    3  240.416    0.366   0.239
+node01.jp.xxxxx 226.252.532.9    2 u   39   64    7  241.030   -3.071   0.852
*t.time.xxxx.net 104.1.306.769    2 u   38   64    7  127.126   -2.728   0.514
+node02.jp.xxxxx 250.9.592.830    2 u    8   64   17  241.212   -4.784   1.398

Setup VPN Client

Install the OpenVPN client:

sudo apt-get install openvpn

Download an ExpressVPN OpenVPN configuration file (.ovpn). Follow the "Download the OpenVPN configuration files" section of the manual linux setup guide. When you have got yourself a .ovpn file you can ignore the rest.

Copy the ExpressVPN OpenVPN profile to the OpenVPN client - if you used a different location, adapt the filenames - the first is just what you downloaded, assuming it is in the Downloads directory in your home directory, the second is whatever you want to call the location, and must end in .conf:

sudo cp ~/Downloads/my_expressvpn_location_udp.ovpn /etc/openvpn/location.conf

Test VPN

At this point you should be able to test the VPN actually works:

sudo openvpn --config /etc/openvpn/location.conf

If all is well, you'll see something like this (the important bit is it ends with Initialization Sequence Completed):

$ sudo openvpn --config /etc/openvpn/Japan.conf 
Sat Oct 24 12:10:54 2015 OpenVPN 2.3.4 arm-unknown-linux-gnueabihf [SSL (OpenSSL)] [LZO] [EPOLL] [PKCS11] [MH] [IPv6] built on Dec  5 2014
Sat Oct 24 12:10:54 2015 library versions: OpenSSL 1.0.1k 8 Jan 2015, LZO 2.08
Sat Oct 24 12:10:54 2015 UDPv4 link local: [undef]
Sat Oct 24 12:10:54 2015 UDPv4 link remote: [AF_INET]123.123.123.123:1194
Sat Oct 24 12:10:54 2015 WARNING: this configuration may cache passwords in memory -- use the auth-nocache option to prevent this
Sat Oct 24 12:10:56 2015 [Private Internet Access] Peer Connection Initiated with [AF_INET]123.123.123.123:1194
Sat Oct 24 12:10:58 2015 TUN/TAP device tun0 opened
Sat Oct 24 12:10:58 2015 do_ifconfig, tt->ipv6=0, tt->did_ifconfig_ipv6_setup=0
Sat Oct 24 12:10:58 2015 /sbin/ip link set dev tun0 up mtu 1500
Sat Oct 24 12:10:58 2015 /sbin/ip addr add dev tun0 local 10.10.10.6 peer 10.10.10.5
Sat Oct 24 12:10:59 2015 Initialization Sequence Completed

Exit this with Ctrl+c

Enable VPN at boot

sudo systemctl enable openvpn@location

Setup Routing and NAT

Enable IP Forwarding:

echo -e '\n#Enable IP Routing\nnet.ipv4.ip_forward = 1' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p

Setup NAT from the local LAN down the VPN tunnel:

sudo iptables -t nat -A POSTROUTING -o tun0 -j MASQUERADE
sudo iptables -A FORWARD -i tun0 -o eth0 -m state --state RELATED,ESTABLISHED -j ACCEPT
sudo iptables -A FORWARD -i eth0 -o tun0 -j ACCEPT

Make the NAT rules persistent across reboot:

sudo apt-get install iptables-persistent

The installer will ask if you want to save current rules, select Yes

If you don't select yes, that's fine, you can save the rules later with sudo netfilter-persistent save

Make the rules apply at startup:

sudo systemctl enable netfilter-persistent

VPN Kill Switch

Note that this currently doesn't work with ExpressVPN - I think because it uses a different port and UDP traffic, so the rules need to be adjusted. Leaving this here for now for reference.

This will block outbound traffic from the Pi so that only the VPN and related services are allowed.

Once this is done, the only way the Pi can get to the internet is over the VPN.

This means if the VPN goes down, your traffic will just stop working, rather than end up routing over your regular internet connection where it could become visible.

sudo iptables -A OUTPUT -o tun0 -m comment --comment "vpn" -j ACCEPT
sudo iptables -A OUTPUT -o eth0 -p icmp -m comment --comment "icmp" -j ACCEPT
sudo iptables -A OUTPUT -d 192.168.0.0/24 -o eth0 -m comment --comment "lan" -j ACCEPT
sudo iptables -A OUTPUT -o eth0 -p udp -m udp --dport 1198 -m comment --comment "openvpn" -j ACCEPT
sudo iptables -A OUTPUT -o eth0 -p tcp -m tcp --sport 22 -m comment --comment "ssh" -j ACCEPT
sudo iptables -A OUTPUT -o eth0 -p udp -m udp --dport 123 -m comment --comment "ntp" -j ACCEPT
sudo iptables -A OUTPUT -o eth0 -p udp -m udp --dport 53 -m comment --comment "dns" -j ACCEPT
sudo iptables -A OUTPUT -o eth0 -p tcp -m tcp --dport 53 -m comment --comment "dns" -j ACCEPT
sudo iptables -A OUTPUT -o eth0 -j DROP

And save so they apply at reboot:

sudo netfilter-persistent save

If you find traffic on your other systems stops, then look on the Pi to see if the VPN is up or not.

You can check the status and logs of the VPN client with:

sudo systemctl status openvpn@location
sudo journalctl -u openvpn@location

Configure Other Systems on the LAN

Now we're ready to tell other systems to send their traffic through the Raspberry Pi.

Configure other systems' network so they are like:

  • Default Gateway: Pi's static IP address (eg: 192.168.0.42)
  • DNS: Something public like Google DNS (8.8.8.8 and 8.8.4.4)

Don't use your existing internet router (eg: 192.168.1.1) as DNS, or your DNS queries will be visible to your ISP and hence may be visible to organizations who wish to see your internet traffic.

Optional: DNS on the Pi

Note: This doesn't work for me - I had to stick with using 8.8.8.8 for DNS on clients even with dnsmasq running.

To ensure all your DNS goes through the VPN, you could install dnsmasq on the Pi to accept DNS requests from the local LAN and forward requests to external DNS servers.

sudo apt-get install dnsmasq

You may now configure the other systems on the LAN to use the Pi (192.168.0.42) as their DNS server as well as their gateway.

Additional Notes on IPTables

If you want to try tweaking iptables rules, it's useful to be able to undo changes.

To remove rules, run sudo netfilter-persistent flush. This clears all rules - make sure to set them up again with iptables commands above.

To see what rules you currently have, run sudo iptables --list.

After setting rules back up again, use sudo netfilter-persistent save to save them and make them persistent across reboots.

@langphil
Copy link

langphil commented Jul 7, 2019

I've just implemented this and there seems to be a step missing.

I needed to provide a new file /etc/openvpn/login with ExpressVPN credentials on separate lines (Username, Password). These are the credentials that are provided when the OpenVPN configuration file is downloaded in the Manual Configuration screen on the Express VPN website.

Then I needed to give correct access to the new login file sudo chmod 600 /etc/openvpn/login

Then sudo nano /etc/openvpn/<NAME>.conf and change auth-user-pass to auth-user-pass /etc/openvpn/login.

This ensures that ExpressVPN gets the crednetials it needs, otherwise it gets stuck in a loop looking for them.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment