Reverse VPN: turn any private device into public cloud server

3 Apr

NOTICE there might still be some bugs and sparsely commented snippets in this document, but if you follow it, it should work.

Video Demo by ravallblog

Typically when you think of a VPN, you think of a private office network behind a firewall that you want to securely access from home or another office.

But what about when your home server hosting your blog and your business site is stuck behind an apartment firewall?

Or what if you want to be able to take a device with you, attach it to any network, and begin using it’s web services immediately?

Effectively, we’re turning the VPN inside out. A reverse VPN, if you will.

Use a public VPS as the VPN server

Digital Ocean, ChunkHost, AWS, or a server (or Raspberry Pi) in your office, home, or a friend’s home – anything that you can get root access to and give a public network access (even if it’s with a dynamic dns service).

Install some tools

NOTE: You must be using bash (the default shell / terminal) — not zsh or fish or anything fancy — legit bash.

Log into your VPS (ssh and then continue:

Install the Tools

sudo apt-get install --yes openvpn

Switch to the root user

Be the root you know in your heart. You’re going to have to do pretty much everything as root from this point on, so it makes sense just to switch (if you aren’t root already).

sudo su -

Get the easy-rsa 2.x tools from github

The new line of tools is 3.x, but I’m not familiar with those yet and these work, so these are what we will use. 😀

# Install git
sudo apt-get install --yes git
# Copy all the files from the internet to your computer
git clone ~/easy-rsa
pushd ~/easy-rsa/
git checkout 'release/2.x'

# Copy all the files into the right place
rsync -av ~/easy-rsa/easy-rsa/2.0/ /etc/openvpn/easy-rsa/

Next we update the config

# Push into the easy-rsa directory
pushd /etc/openvpn/easy-rsa/
# Then open the ./vars file, which is a config file
# You'll need to take a look around and season to taste
# It's nothing fancy, just names and preferences
vim ./vars

Note a few things that you probably should change:


# change all of the things down at the bottom that look like these
# (these are given as an example for me, yours should be different)
export KEY_PROVINCE="Utah"
export KEY_CITY="Provo"
export KEY_ORG="AJ ONeal Tech LLC"
export KEY_EMAIL=""
export KEY_CN="*"
export KEY_NAME="AJ ONeal"
export KEY_OU="Department of Docs & Blogging"

Now we’ll get to work

Then load all of the vars into your environment

# load the variables
source ./vars

# delete any and all previous keys.

mkdir -p ./keys
ls -lah ./keys

# TODO (note to self) show the plain openssl commands (it's not that hard, y'know?)

# Build a certificate authority for your organization
# NOTE: answer the questions

sudo apt-get install --yes tree
tree ./keys/
# The out put will look like this:
# ./keys/
# ├── ca.crt
# ├── ca.key
# ├── index.txt
# └── serial

# Inpsect the keys, just for fun
openssl x509 -text -noout -in ./keys/ca.crt
openssl x509 -text -noout -in ./keys/ca.key

# When prompted, enter the same CN as above - in my case
# No Password

tree ./keys/
# ./keys/
# ├── 01.pem
# ├── ca.crt
# ├── ca.key
# ├── index.txt
# ├── index.txt.attr
# ├── index.txt.old
# ├──
# ├──
# ├──
# ├── serial
# └── serial.old
# The less you're doing with your computer, the longer this will take.
# If you're downloading a big file or something or https, it should only take a minute.
# Otherwise it could take several minutes


If you use https, you can download a file over and over again (such as a large image) with curl and you’ll get a different set of bits each time because the random seed for the https connetion will be different. This will give you tons of entropy if you run it while generating keys.

while true; do sleep 1; curl '' > /dev/null; done
openvpn --genkey --secret keys/ta.key
curl -fssL \
  -o /etc/openvpn/server.conf
vim /etc/openvpn/server.conf 

Configure the Server’s Network and Firewall settings

vim /etc/sysctl.conf


# uncomment the following

TODO: the router may need to be setup to forward 1194 to the pi

sysctl -p

ufw status
ufw allow 1194/udp

# IMPORTANT change the to your server's IP
iptables -t nat -A POSTROUTING -s -o eth0 -j SNAT --to-source

touch /etc/
chmod 700 /etc/

# this will be open, you'll add the stuff below
vim /etc/


# IMPORTANT change the to your server's IP
iptables -t nat -A POSTROUTING -s -o eth0 -j SNAT --to-source

Now we’ll make sure that this firewall rule is added on each boot

vim /etc/network/interfaces

# You'll see a line that looks like this
iface eth0 inet dhcp

# underneath it you'll need to add this line
# (it shouldn't matter whether you use a tab or spaces)
        pre-up /etc/

And finally we’re ready to restart the openvpn service

/etc/init.d/openvpn stop
/etc/init.d/openvpn start

Use a private device as a Web Server

I must note that this should be the process:

  • create your client key on the client,
  • create a client.CSR.PEM (certificate signing request)
  • send the client.CSR.PEM to the server
  • the server creates a client.CRT.PEM from the client.CSR.PEM

However, if the server is compromised then the server private key and certificate are compromised which means that every certificate the server has signed are now invalid.


We’re going to create the client’s keys and OVPN file.

# For example

Generating Client Keys

Each device will need its own keys.

Use ./build-key if the device is meant to be always-on and not require user interaction, like a raspberry pi home server.

# Example
# this will NOT require a passphrase

# just showing the new files (omitting *.old backups)
tree ./keys/
# ./keys/
# ├── 02.pem
# ├──
# ├──
# └──

Use ./build-key-pass if the device is something you log into, such as your laptop.

# Example
# this WILL require a passphrase

# just showing the new files (omitting *.old backups)
tree ./keys/
# ./keys/
# ├── 03.pem
# ├──
# ├──
# └──

Some devices (iOS, Android, Macbook(?)) need a des3 version of the key, might as well do that now:

# this will require your previous passphrase, and to create a passphrase (it can be the same)
openssl rsa -in ./keys/ -des3 -out ./keys/

vim /etc/openvpn/easy-rsa/keys/Default.txt


dev tun 
proto udp 
remote 1194 
resolv-retry infinite 
ns-cert-type server 
key-direction 1 
cipher AES-128-CBC 
verb 1 
mute 20

# Default Variable Declarations 
if [ -z "${NAME}" ]; then
  #Ask for a Client name 
  echo "Please enter an existing Client Name:"
  read NAME 
#1st Verify that client’s Public Key Exists 
if [ ! -f $NAME$CRT ]; then 
 echo "[ERROR]: Client Public Key Certificate not found: $NAME$CRT" 
echo "Client’s cert found: $NAME$CR" 
#Then, verify that there is a private key for that client 
if [ ! -f $NAME$KEY ]; then 
 echo "[ERROR]: Client 3des Private Key not found: $NAME$KEY" 
echo "Client’s Private Key found: $NAME$KEY"
#Confirm the CA public key exists 
if [ ! -f $CA ]; then 
 echo "[ERROR]: CA Public Key not found: $CA" 
echo "CA public Key found: $CA" 
#Confirm the tls-auth ta key file exists 
if [ ! -f $TA ]; then 
 echo "[ERROR]: tls-auth Key not found: $TA" 
echo "tls-auth Private Key found: $TA" 
#Ready to make a new .opvn file - Start by populating with the default file 
#Now, append the CA Public Cert 
echo "<ca>" >> $NAME$FILEEXT 
echo "</ca>" >> $NAME$FILEEXT
#Next append the client Public Cert 
echo "<cert>" >> $NAME$FILEEXT 
echo "</cert>" >> $NAME$FILEEXT 
#Then, append the client Private Key 
echo "<key>" >> $NAME$FILEEXT 
echo "</key>" >> $NAME$FILEEXT 
#Finally, append the TA Private Key 
echo "<tls-auth>" >> $NAME$FILEEXT 
echo "</tls-auth>" >> $NAME$FILEEXT 
echo "Done! $NAME$FILEEXT Successfully Created."
#Script written by Eric Jodoin
pushd /etc/openvpn/easy-rsa/keys

# copy this and paste it on the client

On the Client

You’ve just got to paste that config onto the client and start the connection.

sudo apt-get install --yes openvpn
# note it's important to use '&' rather than ';' or '&&', even though it's a daemon
sudo kill --signal TERM $(cat /var/run/ovpn."${OVPN_CLIENT}".pid)
sleep 0.5
sudo kill --signal KILL $(cat /var/run/ovpn."${OVPN_CLIENT}".pid)
sudo openvpn --config "${OVPN_CLIENT}".ovpn --daemon --writepid /var/run/ovpn."${OVPN_CLIENT}".pid
# you can later kill the process with
# sudo bash -c 'kill $(cat /var/run/'

Check if you were successful.

ping -c 1

If you were successful you’ll see a tun0 device with an IP address and you should be able to ping the VPN server.

Connect on Startup

If you’re lucky the openvpn service was already configured to start on boot and all you have to do is move your config file to the correct directory and change the config file to say that it will be loaded

sudo mv /etc/openvpn/client-xyz.conf
sudo vim /etc/default/openvpn
# uncomment the following
# AUTOSTART="client-xyz"


vim /etc/init/myopenvpn
# OpenVPN autostart on boot upstart job

start on runlevel [2345]
stop on runlevel [!2345]


exec /usr/sbin/openvpn --status /var/run/openvpn.client.status 10 --cd /etc/openvpn --config /etc/openvpn/client.conf --syslog openvpn

By AJ ONeal

If you loved this and want more like it, sign up!