Sunday, 11 January 2015



In my previous articles we have seen how to build a home router being a VPN gateway based off FreeBSD or OpenBSD. In these cases, we used an external VPN provider to connect to, and route all of our encrypted traffic through it. However, while I received positive feeback, I also had legitimate questions about the trust given to the VPN provider. What if the VPN provider is not honest and is in fact logging despite stating the opposite? What if he is malicious and watch his clients traffic? The VPN provider could also be perfectly honest, but could be breached (OS vulnerability, OpenSSL/Heartbleed, Shellshock, etc...) by attackers then logging clients traffics. Or simply, may be we perfectly trust it, but we just want to be in control of the VPN server itself.

Managing and controlling your own VPN server gives you the following advantages: no concurrent clients connections limit, dedicated server not used simultaneously by strangers, the choice of the OS and security parameters, and complete trust on your provider (yourself). On the other hand, depending on the server hosting provider and its prices, you may have a monthly bandwitdh limit, and in all cases, a server to maintain continuously (security patches, monitoring, backups, etc...). Also, the price will probably be a little higher when hosting your own server online. Using AirVPN I have a yearly price of 54€. Using a dedicated Virtual Private Server (VPS) for 5€ per month for instance, it makes 60€ per year. Using a VPS is cheaper than using a hardware dedicated server, while still being a dedicated (virtual) server. VPS can also be saved with whole OS snapshots, which is handy before attemping an upgrade.

Then, which VPS provider to choose? I have looked at a lot of them, looking for a rare one natively providing FreeBSD and OpenBSD. Better yet, allowing the use of custom ISO and booting onto it, and having a console access for the install or for rebooting into single user mode. I was looking at being able to do snapshots too, and have a choice of different datacenters in various countries. I have found such provider: Vultr. You can of course follow this article if you use or plan of using any other web hoster, dedicated server or VPS. It just happened that Vultr had all of the features I needed, and even more such as a Two-Factor Authentication (2FA) with Yubikey (Google Authenticator possible too). As a bonus they accept Bitcoin payments.

In the following, I assume you have chosen your server provider, and have a running OpenBSD 5.6 freshly installed. If you want installation instructions, you can check my OpenBSD VPN gateway article, keeping in mind it's best to let your interface in DHCP while installing it for the first time on your VPS. I also assume you have one root and one user account, the user account being in the wheel group, and allowed to use sudo, as described in my aforementioned article.


OpenBSD default settings are very secure, and no services are listening on the outside except SSH. However SSH is listening on the default 22 port, accepting password authentication. Before configuring our server, it is best to block any inbound access except from our computer public IP, and then take our time to lock down SSH. Let's start by a basic pf ruleset:
$ sudo vi /etc/pf.conf
block in quick from ! x.x.x.x # your public IP address
pass out quick

Replace "x.x.x.x" by your computer public IP address you are connecting from. You can check on if you don't know it. Of course this ruleset is temporary. Now apply it:
$ sudo pfctl -f /etc/pf.conf

We now have few things to do: create an SSH key, disable root login and password base authentication, and make SSH listening on another port as a bonus (to avoid automated scans adding "noise" to our logs). For Linux/BSD clients you can create a key with -t ed25519 as below, but for Windows your SSH client may only be compatible with keys created using -t rsa.
$ ssh-keygen -t ed25519

Then copy your public key to ~/.ssh/authorized_keys :
$ cp ~/.ssh/ ~/.ssh/authorized_keys

Copy the content of your private key "~/.ssh/id_ed25519" on the remote computer you will use to connect to your router, and set a permission of "600" on it. If on Linux or BSD, you will be able to connect later with "ssh -i your_private_key your_server_ip".

Now modify your SSH server with this temporary configuration, the TCP port 21598 being an example, choose the port you want. You can choose a higher port to avoid scans, or choose a port such as 443 to be able to connect to your server from everywhere, when outbound TCP port 22 could be blocked:
$ sudo vi /etc/ssh/sshd_config
# Modify default listening port
Port 21598

# Authentication
PasswordAuthentication yes # temporary
PermitRootLogin no
AllowUsers YOUR_USER
AuthorizedKeysFile .ssh/authorized_keys
AllowTcpForwarding no
UsePrivilegeSeparation sandbox # Default for new installations.
Subsystem sftp /usr/libexec/sftp-server

Restart sshd :
$ sudo /etc/rc.d/sshd restart

Connect from your remote computer with the private key, for instance with "ssh -i your_private_key your_server_ip", and check that connecting with your regular user and with the SSH key fully works. Once it's working, modify the following line in /etc/ssh/sshd_config :
PasswordAuthentication no

Restart sshd :
$ sudo /etc/rc.d/sshd restart

On the client side, you can enable SSH key fingerprint visual display in /etc/ssh/ssh_config which displays your SSH key in hex format and an ACSCII graphic everytime you connect. Once you get used to the ASCII graphic of your server, you should notice if all of a sudden it is completely different (probably a man-in-the-middle):
$ sudo vi /etc/ssh/ssh_config
# Display fingerprint in hex and ASCII graphic when connecting
VisualHostKey yes

You now have a SSH listening on a non default port, root denied from connecting in, password authentication disabled, and authencation based on SSH keys and passphrase. For further SSH hardening, read the following excellent article describing which protocols and ciphers to use for optimum security. I may in the future update the SSH settings given in this article to follow some of the advises from this page.

Finally, to update the system and packages easily, I really like to use "openup" from M:Tier:
$ ftp
$ chmod +x openup
$ sudo openup
===> Checking for openup update
===> Installing/updating binpatch(es)
===> Updating package(s)

M:Tier is a third party not related to OpenBSD project however, so you have to decide if you wish to trust their repository or not.

A few tweaks we can make before configuring further our server. As Vultr VPS are hosted on SSD, it is a good idea to add in the fstab file the mount options "softdep" and "noatime". The first one increase disk performances, while the second will prevent the "last access time" file properties to be written:
$ sudo vi /etc/fstab
YourDiskDUID.b none swap sw
YourDiskDUID.a / ffs rw,noatime,softdep 1 1
YourDiskDUID.k /home ffs rw,nodev,nosuid,noatime,softdep 1 2
YourDiskDUID.d /tmp ffs rw,nodev,nosuid,noatime,softdep 1 2
YourDiskDUID.f /usr ffs rw,nodev,noatime,softdep 1 2
YourDiskDUID.g /usr/X11R6 ffs rw,nodev,noatime,softdep 1 2
YourDiskDUID.h /usr/local ffs rw,nodev,noatime,softdep 1 2
YourDiskDUID.j /usr/obj ffs rw,nodev,nosuid,noatime,softdep 1 2
YourDiskDUID.i /usr/src ffs rw,nodev,nosuid,noatime,softdep 1 2
YourDiskDUID.e /var ffs rw,nodev,nosuid,noatime,softdep 1 2

Your server has booted with DHCP and has acquired its network configuration from your VPS provider. As the server IP address is fixed, I prefer setting manually the network configuration to avoid relying on another server (VPS provider's DHCP and DNS) I do not control. Modify the following files according to your server public ip address, mask, and gateway:
$ sudo vi /etc/hostname.vio0
inet server_public_ip server_netmask

Add the gateway :
$ sudo vi /etc/mygate

Our server will forward traffic between its VPN interface and its default network interface. We have to enable forwarding:
$ sudo sysctl net.inet.ip.forwarding=1
$ sudo vi /etc/sysctl.conf

At this point your still rely on your provider DNS server in /etc/resolv.conf, we will take care of that in the next step.

3. DNS
We will use DNSCrypt to make our DNS requests encrypted, and Unbound to have a local DNS cache. This will allow us to avoid using our VPS provider DNS servers, and will also be useful to your future VPN clients which will be able to use your VPN server as their DNS server too, if they wish too (e.g mobile phones). Both dnscrypt and unbound will listen on the localhost only, not to the outside. They will be reachable nonetheless later to your VPN clients trough the VPN tunnel, using a firewall redirection.

$ export PKG_PATH=
$ sudo pkg_add dnscrypt-proxy
$ sudo vi /etc/rc.local
# DNSCrypt
/usr/local/sbin/dnscrypt-proxy -a -u _dnscrypt-proxy -d -l /dev/null -R

You can choose your dnscrypt enabled DNS server at the following list (choose a logless DNSSEC enabled one).

$ sudo /usr/local/sbin/dnscrypt-proxy -a -u _dnscrypt-proxy -d -l /dev/null -R

We now configure and enable unbound, already included in the base system. Unbound will drop privileges and will be chrooted in /var/unbound:
$ sudo vi /var/unbound/etc/unbound.conf
username: _unbound
directory: /var/unbound
chroot: /var/unbound
do-not-query-localhost: no
access-control: refuse
access-control: allow
access-control: allow
hide-identity: yes
hide-version: yes
auto-trust-anchor-file: "/var/unbound/db/root.key"

name: "." # use for ALL queries
forward-addr: # dnscrypt-proxy

Do not forget to modify your /etc/resolv.conf:
$ sudo vi /etc/resolv.conf
nameserver # unbound is listening there at port 53

Run Unbound, and enable it to launch at startup :
$ sudo /etc/rc.d/unbound start
$ sudo vi /etc/rc.conf.local
# Unbound
unbound_flags="-c /var/unbound/etc/unbound.conf"

Test that your DNS chain is working:
$ host has address mail is handled by 6 mail is handled by 10

Unbound is listening on locahost port 53, and when contacted is forwarding to dnscrypt listening on locahost port 40, itself contacting an external dnscrypt enabled DNS server.

The pf ruleset below does many things:
- denies all inbound, except SSH and OpenVPN on non default ports
- protects SSH from SYN flood, and bruteforce
- detects scans on common ports and blacklist the miscreants for 24H
- allows VPN clients to make DNS requests to unbound on localhost (which uses dnscrypt)
- does not log blocked network traffic from the provider's DHCP

You should modify this ruleset according to your chosen SSH and VPN listening ports (we will setup OpenVPN later, but you can choose a random port now). The scan detection and blacklisting concept can theoretically backfire, if someone sends spoofed packets with trusted IPs. However, as it is implemented, only incoming traffic will match the blacklist table. Therefore, if a spoofed packet is sent with a website IP you trust, it will not prevent you to access that website at all as your traffic will be outbound. Also, as we will write a script to parse pf logs and add IPs into the blacklist, we will add an exception for our own trusted client computer public IP address to avoid being locked out. As a last resort the remote console access from the VPS provider could allow us to login, if we were totally locked out for any reason. This adaptative behavior is not mandatory fo your VPN server to be operational, but it is useful to block automatic scans on the Internet to query your SSH and VPN ports once they hit previously ports in your watchlist. Additionally, while I show below an example with selected ports in the watchlist, you could test putting the whole ports range, excluding your two open ports (more aggressive).

$ sudo vi /etc/pf.conf
# "Being your own VPN provider with OpenBSD"
# Guillaume Kaddouch:
# ruleset last modified: 2015 January 11

# ---------------------------------------------------------------------------------------
egress="vio0" # your server network interface, modify accordingly
ssh_port="21598" # just a random example, modify to match your chosen SSH port
vpn_port="21600" # just a random example, modify to match your chosen OpenVPN port

# ports watchlist example, modify as you wish.
# Match against this will be put in "badguys" table below, by an external scheduled script
bad_ports="{ ftp-data, ftp, ssh, telnet, smtp, domain, http, pop3, ntp, netbios-ns, \
netbios-dgm, netbios-ssn, https, microsoft-ds, ms-sql-s, ms-sql-m, \
mysql, postgresql }"

table <internet> const { $all_networks, !self, !$private_networks }
table <myself> const { self }
table <bruteforce> persist
table <badguys> persist

# ---------------------------------------------------------------------------------------
block log all
set block-policy drop
set loginterface egress
set skip on lo
match in all scrub (no-df max-mss 1440 random-id)
block in log quick from <bruteforce> label "bruteforce"
block in log quick from <badguys> label "old_guys"

# --------------------------------------------------------------------------------
match in on egress proto tcp from <internet> to port $ssh_port
match in on egress proto { udp tcp } from <internet> to port $bad_ports
match in on egress proto udp to port $vpn_port
match in on $vpn proto { icmp udp tcp }
match in on $vpn proto { udp tcp } to $vpn_ip port domain
match out on egress tagged VPN_TUN_IN
match out on egress proto tcp from <myself> to port { http https }
match out on egress proto { udp tcp } from <myself> to port domain
match out on egress proto udp from <myself> to port https
match out on egress proto udp from <myself> to port ntp
match in on egress from { no-route urpf-failed } to any
match out on egress from any to no-route
match inet6 all
   tag SSH_IN
   tag BAD_GUYS
   tag VPN_TUN_IN
   tag VPN_DNS_IN
   tag HTTP_OUT
   tag DNS_OUT
   tag DNS_OUT
   tag NTP_OUT
   tag IPV6

# ---------------------------------------------------------------------------------------
match in tagged VPN_EGRESS_IN set tos lowdelay set prio 6
match out tagged VPN_FORWARD nat-to (egress) set prio 6

# Blocking spoofed or malformed packets, IPv6, and some bad traffic
antispoof log quick for egress label "antispoof"
block quick log tagged BAD_PACKET label "noroute_urpf"
block quick log tagged IPV6 label "ipv6"
block quick log tagged BAD_GUYS label "new_guy"

# Standard rules
# protect SSH from SYN flood and bruteforce
pass in quick tagged SSH_IN synproxy state \
(max-src-conn 10, max-src-conn-rate 5/5, overload <bruteforce> flush global)

# Redirect VPN clients DNS requests to unbound
pass in quick inet tagged VPN_DNS_IN rdr-to port domain

pass in quick tagged VPN_EGRESS_IN
pass in quick tagged VPN_TUN_IN

pass out quick tagged HTTP_OUT
pass out quick tagged DNS_OUT
pass out quick tagged VPN_FORWARD modulate state
pass out quick tagged NTP_OUT

# no log for
block in quick proto udp from port bootpc to port bootps
block in quick proto udp from any port bootps to port bootpc

Check the ruleset syntax, with the first command, and then apply it if no error returned:
$ sudo pfctl -nf /etc/pf.conf
$ sudo pfctl -f /etc/pf.conf

Now we have to make our script which will look for IPs blocked because they matched our ports watchlist rule, labeled "new_guy". Script will be in /home/user/scripts:
$ cd ~
$ mkdir scripts
$ cd scripts
$ sudo ./
# Modify to match your server IP address and network interface


# Find rule number to look the logs for, we added a label in pf ruleset to locate it easily
# pf starts its numbering at rule 0 (tcpdump logs), while grep starts at rule 1
grep_number=`pfctl -sr | grep -n $label | cut -d":" -f1`
rule_number=$(($grep_number - 1))

echo "" > $file_temp
echo "" > $file_clean

# Look in all blocked traffic the IP blocked because of matching our ports watchlist (rule_number above)
tcpdump -enr /var/log/pflog | grep "rule $rule_number/(match) block in on $ext_if" | cut -d':' -f4 | cut -d' ' -f2 | \
awk '{split($0,ip,"."); print ip[1]"."ip[2]"."ip[3]"."ip[4]}' | sort | uniq >> $file_temp

# Remove our own trusted client IP we connect from!
sed -e "s/$trusted//g" $file_temp > $file_clean
cat $file_clean

# Add the new IP addresses in the badguys table
# pfctl handles by itself duplicate IPs and does not add an already existing one
pfctl -t badguys -T add -f $file_clean

Edit the crontab to schedule execution of this script, as well as check every hour to expire table entries older than 24 hours. Replace "/home/your_user" below with your username:
$ sudo crontab -e
# add badguys to the pf table to be blocked
*/5 * * * * /home/your_user/scripts/

# Clear pf tables
0 * * * * pfctl -t bruteforce -T expire 86400
0 * * * * pfctl -t badguys -T expire 86400

You can check with another script the state of your blacklist/badguys table, ports blocked to them once blacklisted (including ports not in the watchlist), and number of blocked IP in your bruteforce table. Here is the script example, you can freely modify:
$ sudo vi ./

BAD_TOTAL=`pfctl -t badguys -T show | wc -l | sed -e 's/^ *//' -e 's/ *$//'`
BRUTE_TOTAL=`pfctl -t bruteforce -T show | wc -l | sed -e 's/^ *//' -e 's/ *$//'`
BAD_NEW=`pfctl -sl | grep $label_new | cut -d' ' -f3`
BAD_OLD=`pfctl -sl | grep $label_old | cut -d' ' -f3`

grep_number=`pfctl -sr | grep -n $label_new | cut -d":" -f1`
rule_number=$(($grep_number - 1))
PORTS_SCAN=`tcpdump -enr /var/log/pflog | grep "rule $rule_number/(match) block in on $ext_if" | \
cut -d':' -f4 | cut -d' ' -f4 | awk '{split($0,ip,"."); print ip[5]}'| sort | uniq`

echo ""
echo -e "Table <badguys> total/first/retry : $BAD_TOTAL/$BAD_NEW/$BAD_OLD"
echo "ports blocked :"
echo "$PORTS_SCAN"

echo ""
echo -e "Table <bruteforce> : $BRUTE_TOTAL"

Make the two scripts executable. Below is also an example of a possible output from the second script:
$ chmod +x ./*.sh
$ sudo
Table <badguys> total/first/retry : 486/71/82
ports blocked :

Table <bruteforce> : 0

- "total" is the number of IP addresses blacklisted.
- "first" is the number of blocks matching the ports watchlist from IP not being blacklisted (since last ruleset reload)
- "retry" is the number of blocks matching the ports watchlist from IP already blacklisted (since last ruleset reload)
- "ports blocked" is the port list accessed by the blacklisted IPs.
- "bruteforce" is the total number of IP which made too many connections to our open SSH port.

You now have a nice adaptative firewall, however as warned above, be careful as it can backfire if trusted IPs are not excluded and become blacklisted. It is a working configuration I had no trouble with, in my context, but in your situation it may have to be modified to fit your case.

5.1 - SERVER

We are going to setup our OpenVPN server, with the help of the easy-rsa package to make it easier to build our Certificate Authority (CA) and other keys and certificates. The requirements for our server is to use secure keys and protocols only:
- CA, server and clients keys, in 4096 bits RSA
- Key exchange with 4096 bits Diffie-Hellman
- HMAC SHA1 packet authentication, with a TLS 2048 bits static key
- AES 256 bits tunnel encryption
- Perfect Forward Secrecy (PFS)

Let's download our packages and create needed directories:
$ sudo pkg_add openvpn easy-rsa

$ sudo mkdir -p /usr/local/etc/openvpn/{public,secret,clients/{public,secret},easy-rsa/keys}
$ sudo mkdir /usr/local/share/easy-rsa/keys
$ sudo mkdir -p /var/openvpn/tmp

$ cd /usr/local/share/easy-rsa/
$ chmod +x ./*

You have to manually edit /usr/local/share/easy-rsa/vars file to modify KEY_SIZE=1024 into KEY_SIZE=2048, and edit /usr/local/share/easy-rsa/build-dh to modify 2048 into 4096. Now let's create our CA and keys. Log in as root temporarily to execute these commands (notice the double dot for the ". ./vars" command):

$ su -
# cd /usr/local/share/easy-rsa/
# cp openssl-1.0.0.cnf ./openssl.cnf
# . ./vars
# ./clean-all

# ./build-ca --keysize 4096
# ./build-key-server --keysize 4096 server
# ./build-key --keysize 4096 myhome
# ./build-key --keysize 4096 myphone
# ./build-dh
# openvpn --genkey --secret ta.key

We have built certificates and keys for our CA, server, two clients, Diffie-Hellman parameters, and finally the 2048 bits TLS static key that will be used for HMAC packet authentication. We can now move the files in /usr/local/etc/openvpn, in different folders according to the public or private status of the files generated:
# cp ./* /usr/local/etc/openvpn/easy-rsa
# ./clean-all
# cd /usr/local/etc/openvpn/easy-rsa/keys

# cp ca.crt /usr/local/etc/openvpn/public
# mv ca.crt /usr/local/etc/openvpn/clients/public
# mv ca.key /usr/local/etc/openvpn/secret
# mv dh4096.pem /usr/local/etc/openvpn/public
# mv server.key /usr/local/etc/openvpn/secret
# mv server.crt /usr/local/etc/openvpn/public

# mv myhome.key /usr/local/etc/openvpn/clients/secret
# mv myhome.c* /usr/local/etc/openvpn/clients/public
# mv myphone.key /usr/local/etc/openvpn/clients/secret
# mv myphone.c* /usr/local/etc/openvpn/clients/public

# cp ta.key /usr/local/etc/openvpn/secret
# mv ta.key /usr/local/etc/openvpn/clients/secret

Files are sorted like this:
- servers public files are in /openvpn/public (ca.crt, dh4096.pem, server.crt)
- servers secret files are in /openvpn/secret (ca.key, server.key, ta.key)
- clients public files are in /openvpn/clients/public (myhome.csr, myhome.crt, myphone.csr, myphone.crt, ca.crt)
- clients secret files are in /openvpn/clients/secret (myhome.key, myphone.key, ta.key)

Let's not forget the main OpenVPN server configuration file in the process. We can logout from root account:
# logout
$ sudo vi /usr/local/etc/openvpn/server.conf
# Server configuration
# SSL/TLS certificate and keys, PFS enabled by default
ca "/usr/local/etc/openvpn/public/ca.crt"
cert "/usr/local/etc/openvpn/public/server.crt"
dh "/usr/local/etc/openvpn/public/dh4096.pem" # Diffie Helman 4096 bits
key "/usr/local/etc/openvpn/secret/server.key" # RSA 4096 bits
tls-auth "/usr/local/etc/openvpn/secret/ta.key" 0 # TLS 2048 bits for HMAC
cipher AES-256-CBC # AES 256 bits

# Network parameters
port 21600 # as an example, pick your own port
proto udp
dev tun
ifconfig-pool-persist ipp.txt
push "redirect-gateway def1 bypass-dhcp"

# push our DNS server to clients accepting it (will not override a home router DNS configuration
# with fixed DNS settings). Usefull for mobile phones for instance, where installing
# dnscrypt requires a rooted phone
push "dhcp-option DNS"

keepalive 10 120
comp-lzo no

# Limits
max-clients 5 # change this value if you plan on connecting from more clients

# Privileges, chroot
chroot /var/openvpn
user _openvpn
group _openvpn

verb 4
mute 20

It is time to start our server and to make it start at boot time:
$ sudo chmod -Rf 640 /usr/local/etc/openvpn
$ sudo /usr/local/sbin/openvpn --config /usr/local/etc/openvpn/server.conf --daemon
$ sudo vi /etc/rc.local
# OpenVPN
/usr/local/sbin/openvpn --config /usr/local/etc/openvpn/server.conf --daemon

You shoud check with tail -f /var/log/messages   that OpenVPN started successfully. If something went wrong, check your files and folders ownership and rights.

5.2 - CLIENT

In my OpenBSD router article there is a chapter about an OpenVPN client installation and configuration. Below I will just provide the OpenVPN configuration file to use on the client side:
$ sudo vi /usr/local/etc/openvpn/client.conf
# Client configuration (router, computer)
# SSL/TLS certificate and keys
ca "/usr/local/etc/openvpn/ca.crt" # public
cert "/usr/local/etc/openvpn/myhome.crt" # public
key "/usr/local/etc/openvpn/myhome.key" # secret
tls-auth "/usr/local/etc/openvpn/ta.key" 1 # secret
cipher AES-256-CBC
remote-cert-tls server

dev tun
proto udp
resolv-retry infinite
comp-lzo no

remote YOUR_SERVER_IP 21600

# Privileges, chroot
user _openvpn
group _openvpn
chroot /var/empty

verb 3
explicit-exit-notify 5

Client keys must be copied from the server to the client over a secure channel, for instance by using SCP to transfer them trough SSH. As an example, if you copied the client files on the server in /home/user/vpn, from a Linux/BSD client you can run a command such as:
$ scp -P your_ssh_port -i id_ed25519 your_remote_user@your_server_ip:/home/user/vpn/*.* ./


A smartphone such as Android can download and install "OpenVPN for Android", which is also working with CyanogenMod by the way. Then you will have to transfer on the phone an OpenVPN configuration file, which will require to have the whole configuration in it, including certificates and keys. The configuration file has the certificates and keys inside it. Basically you just copy/paste the content of the required files and insert them between tags like below. Be aware to not miss the "key-direction 1" line before the "tls-auth" block at the end:
remote-cert-tls server
cipher AES-256-CBC

dev tun
proto udp
resolv-retry infinite

remote YOUR_SERVER_IP 21600

comp-lzo no
verb 3
explicit-exit-notify 5

# ca.crt below, just a random example. Full extract is above 35 lines

# myphone.crt below, just a random example. Full extract is above 35 lines

# myphone.key below, just a random example. Full extract is above 35 lines

key-direction 1

# ta.key below, just a random example. Full extract is above 20 lines
# 2048 bit OpenVPN static key
-----BEGIN OpenVPN Static key V1-----
-----END OpenVPN Static key V1-----

This configuration file, you can name "myserver.ovpn" for instance, must be transfered into the phone via a secure channel. As I'm using SpiderOak I used the Hive folder (which is a synchronized folder among many clients/devices) to retrieve the file. However, once you copied with SCP the phone keys and certificate on your computer, I guess a classical USB transfer will do the job as well :-)


I already talked about systrace on my previous OpenBSD router article, in the following chapter, as well as on another article comparing the security of OpenBSD and FreeBSD, where systrace is mentioned in the system hardening part. While OpenVPN is ran chrooted and with no privileges, we can additionally enforce a systrace policy on it which will allow only system calls we defined. As a quick reminder, you can run a systrace learning mode with $ sudo systrace -A /bin/foo   which will store the application profile in /home/your_user/.systrace/bin_foo file. Once you edited the profile and moved it to /etc/systrace, you can run the program in enforce mode with $ sudo systrace -a -U /bin/foo   (-U argument will use the file located in /etc/systrace). Below is a working systrace policy on my VPN server, but you should review it and test it in learning mode first on yours (modify the 21600 port for instance at the line "sockaddr match "inet-*:21600""):
$ sudo vi /etc/systrace/usr_local_sbin_openvpn
Policy: /usr/local/sbin/openvpn, Emulation: native
# Global
native-issetugid: permit
native-getentropy: permit
native-fstat: permit
native-close: permit
native-read: permit
native-sigprocmask: permit
native-__sysctl: permit
native-gettimeofday: permit
native-minherit: permit
native-mquery: permit
native-sigaction: permit
native-clock_gettime: permit
native-getpid: permit
native-sendsyslog: permit
native-write: permit
native-ioctl: permit
native-fork: permit
native-wait4: permit
native-pread: permit
native-exit: permit
native-setsid: permit
native-dup2: permit
native-sigreturn: permit
native-writev: permit
native-poll: permit
native-readv: permit
native-lseek: permit
native-ftruncate: permit

# Memory
native-munmap: permit
native-mmap: prot eq "PROT_READ|PROT_WRITE" then permit
native-mmap: prot eq "PROT_READ" then permit
native-mmap: prot eq "PROT_NONE" then permit
native-mmap: prot eq "PROT_READ|PROT_EXEC" then permit
native-mprotect: prot eq "PROT_READ|PROT_WRITE" then permit
native-mprotect: prot eq "PROT_NONE" then permit
native-mprotect: prot eq "PROT_READ" then permit

# File read
native-fsread: filename eq "/usr/local/etc/openvpn/server.conf" then permit
native-fsread: filename eq "/etc/resolv.conf" then permit
native-fsread: filename match "/usr/local/etc/openvpn/public/*" then permit
native-fsread: filename match "/usr/local/etc/openvpn/secret/*" then permit
native-fsread: filename eq "/var/openvpn" then permit
native-fsread: filename eq "/tmp" then permit
native-fsread: filename match "/usr/share/zoneinfo/*" then permit
native-fsread: filename match "/usr/lib/*" then permit
native-fsread: filename match "/usr/lib/*" then permit
native-fsread: filename match "/usr/lib/*" then permit
native-fsread: filename eq "/var/run/" then permit
native-fsread: filename match "/usr/local/lib/*" then permit
native-fsread: filename eq "/etc/malloc.conf" then permit
native-fsread: filename eq "/usr/share/nls/C/" then permit
native-fsread: filename eq "/etc/group" then permit
native-fsread: filename eq "/etc/spwd.db" then permit
native-fsread: filename eq "/<non-existent filename>: /etc/resolv.conf" then permit
native-fsread: filename match "/<non-existent filename>: /usr/share/zoneinfo/*" then permit
native-fsread: filename eq "/<non-existent filename>: /usr/share/nls/C/" then permit
native-fsread: filename eq "/<non-existent filename>: /usr/share/nls/C./" then permit
native-fsread: filename eq "/var/openvpn/tmp" then permit

# File write
native-fswrite: filename eq "/dev/null" then permit
native-fswrite: filename eq "/dev/tun0" then permit

# Network
native-socket: sockdom eq "AF_INET" and socktype eq "SOCK_DGRAM" then permit
native-socket: sockdom eq "AF_UNKNOWN(17)" and socktype eq "SOCK_RAW" then permit
native-bind: sockaddr match "inet-*:21600" then permit
native-sendto: sockaddr match "inet-*:*" then permit
native-getsockopt: permit
native-setsockopt: permit
native-recvfrom: permit

# Chroot
native-chdir: filename eq "/" then permit
native-chroot: filename eq "/var/openvpn" then permit
native-setgid: gid eq "577" then permit
native-setgroups: permit
native-setuid: uid eq "577" and uname eq "_openvpn" then permit

# Exec
native-execve: filename eq "/sbin/ifconfig" and argv match "/sbin/ifconfig tun0 * mtu 1500 netmask up -link0" then permit
native-execve: filename eq "/<non-existent filename>: /sbin/route" and argv match "/sbin/route delete -net * -netmask *" then permit
native-execve: filename eq "/var/openvpn/" and argv eq "/var/openvpn/ " then permit
native-execve: filename eq "/sbin/route" and argv match "/sbin/route add -net * -netmask *" then permit

# Process
native-fcntl: cmd eq "F_SETFD" then permit
native-fcntl: cmd eq "F_SETFL" then permit

To test the profile in learning mode, kill the running process, and start it with the systrace policy (-A = learning):
$ sudo pkill openvpn
$ sudo systrace -A -U /usr/local/sbin/openvpn --config /usr/local/etc/openvpn/server.conf --daemon
$ tail -f /var/log/messages

If everything is working as expected, check your policy file. If nothing new, you can kill openvpn and restart it in enforce mode:
$ sudo pkill openvpn
$ sudo systrace -a -U /usr/local/sbin/openvpn --config /usr/local/etc/openvpn/server.conf --daemon
$ tail -f /var/log/messages

If you want to run it under a systrace policy from the startup, you have to modify the /etc/rc.local file like this:
$ sudo vi /etc/rc.local
# OpenVPN
systrace -a -U /usr/local/sbin/openvpn --config /usr/local/etc/openvpn/server.conf --daemon

Your VPN server is now finished!

VPN are usually seen as a holy grail for security, privacy, and anonymity. However, as I mentioned it in the OpenBSD VPN gateway article, in the Beyond VPN part, VPN does not solve every problem.

With VPN, you will prevent your traffic from being intercepted and watched either in a public WIFI or by your ISP, or by any country practising global surveillance. It is thus possible to reach security and privacy. One caveat mentioned in the aformentioned article, is that traffic leaving your VPN server is not encrypted and requires end-to-end encryption to protect your privacy (HTTPS, SSH, SMTPS, DNSCrypt, etc...). Security is not automatic while using a VPN however, if we read this article. We learn that the NSA is able to break some VPN such as PPTP or even IPSEC, but that's the how which is interesting, instead of declaring VPN "broken" alltogether. First method is for NSA to participate in encryption standard elaboration to weaken these standards (IETF). If we bought products using less secure encryption, it will be easier for them to decrypt. Then for IPSEC connections for instance, which is more robust, they do not break the protocol itself and hence any IPSEC connection, but are rather using their TAO team to hack routers to steal the keys. NSA targeted for instance a VPN provider named SecurityKiss in Ireland. Security of both VPN protocol and both endpoints (client and server) is of first importance to have a secure connection. If we use the best in class "uncrackable" encryption, but that the server or clients are not secured enough, secret keys can be stolen and VPN decrypted.

Privacy, even if we use strong VPN encryption with very secure server and clients, is a fragile thing. Indeed, at the other end of the tunnel, on the websites and forums you visit, what you type there is of critical importance to your privacy. Obviously, posting your whole life on Facebook quite nullifies the privacy acquired by the use of VPN. Also, even if you use a secure and privacy minded mailbox such as ProtonMail, if you still communicate with Gmail clients your emails will still be read. Additionaly, if in the confort of your VPN you confess to Google Search engine all of your questions, fears, or interests, your privacy will have a very short life (I advise using Duckduckgo search engine instead).

What about anonymity? If we have a good security and privacy, are we automatically anonymous? Having privacy is not the same as being anonymous. If you know my real name because I choose to, but I do not disclose to you my favourite movie, or what books I like to read, I have privacy (I choose what I want to say to who I want). If however you don't even know who I am, I have anonymity. Anonymity is really tricky to achieve. Even if you use a VPN, your browser is likely to betray you with tracking cookies. If you go to a particular website with a VPN server located in a foreign country, the website will see your connection coming from this country, but if it uses tracking cookies it will recognize you anyway. Browser fingerprints are pretty unique too and can be used to identify you no matter which VPN you use and even if you disable all cookies. You can test you browser fingerprint uniqueness at My browser fingerprint is unique among nearly 5 millions tests done on this site! The fact I did the test with a VPN and am using Ghostery browser plugin does not change anything. By using the TOR browser with default settings, still trough my VPN, my fingerprint was matching 8655 other browsers fingerprints, I was no longer uniquely identifiable.

Achieving security, privacy, and anonymity, requires proper end-to-end encryption and security (server and client). It also requires the use of multiple tools and encrypted channels (VPN, Tor, HTTPS, 2FA with Yubikeys), and the avoidance of public services having questionable privacy policies (use alternatives such as ProtonMail, SpiderOak, Duckduckgo, RedPhone, TextSecure, etc...). Most of all, be aware that everything written in the clear, as in an email sent to friend on a webmail account, a "private" message on a forum, or a standard mobile phone SMS, can be potentially intercepted and is thus to be considered public.

We have seen how to build an OpenVPN server based on OpenBSD, to benefit from OpenBSD's memory protection, randomness implementation, LibreSSL, and secure by default philosophy. I find the VPS server to be a cost effective way to build our own VPN server, with many benefits such as snapshot before an upgrade, full access to boot the wanted ISO, remote console access, datacenter country location choice, and Two-Factor authentication with Yubikey. It is a way to be in full control of your computer, your home router, and your VPN server at the other end.

However, there is still benefits of using an external VPN provider such as AirVPN. You spend less money yearly, less time as you do not have to maintain a server or upgrade it, and you have other options such as tunneling VPN inside a SSH or SSL connection. You can switch of VPN server country more quickly, whereas on Vultr you would have to setup DHCP again on your server, do a snapshot, delete your server, and restore the snapshot by selecting a different datacenter. It is still nice to be able to do such manipulation though!

By using an external VPN provider you choose convenience, whereas by managing your own server you prioritize control and trust. Make your choice!



  1. Hint: ARPNetworks also offers OpenBSD VPSs. They really good, I've been using them for over four years now.

    1. They are much more pricey apparently: a 500GB monthly bandwitdh limit server is 20$/month. Vultr offers 1TB for 5$. However that's good to know another OpenBSD/FreeBSD VPS provider.


  2. Why don't you use comp-lzo in OpenVpn?

    1. I tried turning it in on and off on both sides, and checked CPU usage and troughput, and didn't notice any gain in my case.


  3. looks so geeky :) and a person who is not techie I would go for a ready madevpn client. I am using PureVPN since past 3 months, and its great. I like to ask that is it save to use such ready made vpn software ?

    Sorry for asking basic question :)

    1. I am not sure I fully understand your question, but if it is "why not use a ready to use VPN" then the answer is in the introduction. You may not trust your VPN provider, or he may takes time to patch his servers, he may log your traffic, etc...


  4. Thank you VERY much: clearly and thorougly written: compliments :-)

    I was planning to set this up to tunnel traffic via my pfSense to such a VPN-VPS, this surely will help me in this for me new territory :-)

    Looking at Vultr, seeing so many different packages/plans, I have no clue what I should purchase. I'm on 160/20, 2TB a month, which package would you perhaps advise?

    And, if I may: how do you get a BSD iso installed in the VPS in the first place?

    Thank you again :-)

    1. Thanks for your comments. About wich Vultr plan to choose, I do not fully know your needs, but based on the monthly traffic limit of 2TB, I would say their second offer : 7$/month, 2TB traffic, 1GB RAM, 20GB SSD.

      I was able to boot OpenBSD 5.6 iso by choosing "custom" in the 64 bits OS selection, and then providing them an url to an OpenBSD mirror ISO, as you would do to download an ISO an your computer.


  5. Nice you want to share this knowledge
    I've got some basic questions too:

    Are you this way protected against a malicious or compromised VPS provider?
    Can you achieve the same protection with FreeBSD instead of OpenBSD?


    1. A malicious VPS provider could make a snapshot of your server, restore it elsewhere, boot on an CD and retrieve your VPN keys from it... You could use full disk encryption to avoid that. The provider has access to your server console, although credentials are still needed to login (strong passwords here is a must). A _VPN_ provider can be 1) malicious 2) slow to updates its servers 3) insecure (vpn encryption, server security). The _VPS_ provider could be malicious, but updating/managing/securing the server is in your control. If you do not trust _anyone_, the best would be to buy yourself a server and host it in another country, but then would you trust the datacenter owner? The network/ISP provider? At one point, you should trust someone else.

      As for achieveing the same protection with FreeBSD, I can direct you to my previous article OpenBSD vs FreeBSD :

      I prefer OpenBSD for a VPN server for the reasons I mention in the conclusion.


    2. Full Disk Encryption tutorial for OpenBSD and FreeBSD:

    3. I also use vultr for the same purpose with OpenBSD and M;Teir. :) I went through the hassle of encrypting my VPS disk with softraid for more security. I worked for a Large VPS hosting provider. And we use to get subpoena's / warrants for backups/copies of clients servers from US government organizations.

      I used the same BSDnow article as posted above.

  6. I might be misunderstanding something here, but doesn't the provided pf config prevent FTP traffic and therefore the use of pkg_add via FTP in the next section to install OpenVPN?

    1. Not if you use an HTTP pkg source as shown in the article:
      export PKG_PATH=

    2. Yeah, I figured.

      I got it working completely on Vultr with OpenBSD 5.6 (my first time using a BSD :) ), and Arch Linux x86_64 and Android 4.4 as clients.

      Thank you very much for this writeup!

      Sidenote: In certain parts of the SYSTRACE section, you wrote `/usr/local/etc/openvpn` when you probably meant `/usr/local/sbin/openvpn`.

      Also, dunno if it was intentional, but a port (possibly your actual one?) appears in the systrace conf for openvpn.

      Also, on first pass I missed the `key-direction 1` directive for the Android OpenVPN conf as it's sort of hidden in between the crt/key blocks. Perhaps move it up for greater visibility?

      Also, for the dnscrypt-proxy section, it might be worth noting that out of the provided list of resolvers, only those which support DNSSEC validation will work.

      These might be obvious to you, but they tripped my up when I went through the guide.

      Thanks again!

    3. I'm glad you got it working! Thanks for your corrections too, it was indeed mistakes about the openvpn binary path and systrace port. I fixed them, and added warning/information about the "key-direction" line and DNSSEC validated servers.


  7. Hello, i have some trouble to PKG_ADD and SSH in, to access on ssh, i add a line for pass out ssh, and when i try to pkg_add something, ftp can't be ls, what i done wrong ?

    1. Hello,

      The pf ruleset in this article does not allow FTP. Either you have to open outbound FTP on your ruleset, or you use an HTTP PKG source as I did (PKG_PATH=


  8. Thank you for sharing such a great tutorial. Mich appreciated. Keep up doing the great work. Cheers !

  9. Doesn't renting a server from a company who take your data tarnish your anonymity as much as a VPN service would anyway?

    1. If you want anonymity from the hosting company, choose one accepting Bitcoin payments. If you want your data secure from the provider too, use Full Disk Encryption.

    2. Very good points, thanks for the insight :)

  10. Really great article very interesting indeed. My one caveat is if you are concerned about the amount of logging your VPN provider is doing, so you know how much logging your VPS provider is doing? Most VPS providers are not focused on NOT logging info and in fact to thwart and manage abuse the often log quite a bit. By running your own VPN you are just kicking the can down the street at they say. Your activities will now be available to anyone with the proper request. They will then very quickly be able to give them all your contact/billing info.

    1. If you use Full Disk Encryption on your VPN server, are accessing you server via a VPN connection, and are surfing through HTTPS, they is nothing your VPS provider can see. If you are concerned about giving your name and billing info, choose one accepting Bitcoins payments for instance.

      I am not concerned about logging from my VPS provider as I own the server and set it up myself. I trust myself to not make my log available. I do not necessarily trust a VPN provider "claiming" not to log (I have no way to check that).


  11. Great writeup. curious why you haven't locked down ssh to uncomment protocol 2 in sshd_config. Is there a reason for this not to be uncommented or am I misunderstanding.? Would doing this cause more damage than good?


  12. Maybe I'm wrong but you can correct me. I think the provided PF config prevent FTP traffic, therefore its good to install OpenVPN. But I prefer to use logless VPN service, what do you say its good or not ?

  13. I dont know for you guys but this doesnt work for me at all...after i finish everything I cant ping nothing, not google, only i can ping is cant go outside, i cant ssh from inside or outside it is totally locked in. Any idea why, would be great help....thanks in advance!

  14. This is definitely the best guide I ever read. Seriously. You guided me step by step and after some minor tweaks related to specific configuration I have a VPN with VPS also on mobile. You taught me a lot, I'm very grateful for that.
    Will you have some time to write about also on how to chain multiple VPS?
    Thank you


    1. Hello,

      I'm happy this article was useful to you! However I have no time currently to make other articles like this one, unfortunately, as I am working on a certification which requires all of my spare time.


  15. Hello, thank you for tutorial. I am having trouble getting "trusted" ips working. It definely blocks but right now anyone can connect, would you be able to help?

  16. I truly like to reading your post. Thank you so much for taking the time for sharing about VPN. Waiting for new post.