Published on August 5th, 2017 | by Manish Gehlot2
A to Z of a secure, hardened vanilla OpenVPN server on Debian Stretch GNU+Linux
Right to privacy being the topic of the decade, everyone wants to secure their privacy by using an anonymous VPN service. Since privacy and pseduonimity offered by these providers is nothing but privacy by policy. It is based on trust. So, it is always better to run your own VPN server to protect your privacy and stay safe from bad people when away from home or in public places for example when using an open wifi network in a bistro or coffee shop or at the airport.
This is comprehensive guide to configure a hardened OpenVPN server on Debian Stretch GNU/Linux. Although, I am going to use my favorite Debian for this guide but it would equally work for derivatives including but not limited to Ubuntu.
For those who don’t know, OpenVPN is a full-featured open source SSL VPN solution. More information can be found at https://openvpn.net
So, before we start, I am sure you have a working VMware/KVM VPS with a static/dedicated IP and root access rocking Debian GNU/Linux version 9 codename stretch and have basic knowledge of command line. Also my assumption is that you want a no-logs privacy VPN server for you and your loved ones.
In case you are looking for a VPS to setup your OpenVPN server at low cost, I would recommend you to go with Arubacloud.com, they offer 1 EUR/month VMware based VMs in Italy and CZ in Europe. They have a 60 day trial period, in case you are wanting to learn and educate yourself without spending a dime. So you can setup a low cost no-logs privacy OpenVPN server for you and your loved ones @ just 10 EUR / year.
If you are looking for anonymous VPS provider with zero data retention that accepts payments in cash or cryptocoins then go with https://urdn.com.ua, they offer servers in Sweden as of now and are based in Ukraine. Also if you spend 6 USD / month on their KVM, you get a routed /56 IPv6 block which is amazing, as it is required to configure IPv6 with your OpenVPN server. (Adding IPv6 support to your VPN server requires very little changes which would be discussed in the end.)
A. Installing latest OpenVPN stable 2.4.x from official repo
# wget -O - https://swupdate.openvpn.net/repos/repo-public.gpg|apt-key add - # echo "deb http://build.openvpn.net/debian/openvpn/stable stretch main" > /etc/apt/sources.list.d/openvpn-aptrepo.list # aptitude update && aptitude upgrade # aptitude install openvpn
B. OpenVPN server configuration
All the configurations for OpenVPN server is stored in a file at /etc/openvpn/server.conf, it need not be called server.conf, it could be tcp.conf or udp.conf like in this guide. We can run multiple instances of OpenVPN on different port and protocols on the same VPS. Do you recall the claims of commercial VPN provider offering multiple ports / protocols @ just 3 USD / month, this is how they do it. Heh.
I have written a model OpenVPN server configuration file udp.conf for you already and we would discuss the same below.
# model server configuration file - udp.conf daemon dev tun0 port 1194 proto udp4 user openvpn group openvpn persist-key persist-tun keepalive 10 120 txqueuelen 1000 sndbuf 786432 rcvbuf 786432 push "sndbuf 786432" push "rcvbuf 786432" float fast-io mssfix 0 compress remote-cert-tls client topology subnet server 10.8.0.0 255.255.255.0 push "dhcp-option DNS 188.8.131.52" push "dhcp-option DNS 184.108.40.206" push "redirect-gateway def1 bypass-dhcp" crl-verify crl.pem ca ca.crt cert main_server.crt key server.key tls-crypt static.key #ifconfig-pool-persist /home/cipher/ipp.txt 300 duplicate-cn ncp-ciphers AES-256-GCM:AES-128-GCM tls-server tls-version-min 1.2 tls-cipher TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384:TLS-ECDHE-RSA-WITH-AES-128-GCM-SHA256 dh none ecdh-curve secp384r1 reneg-sec 1200 chroot /home/cipher/ tmp-dir tmp/ status s.log log v.log #log /dev/null #status /dev/null verb 3 #verb 0
I would explain everything from the above udp.conf to you line by line and also provide you with additional commands required for particular option to work, if any. (Keep a copy of OpenVPN 2.4 manual ready for reference which is available at https://community.openvpn.net/openvpn/wiki/Openvpn24ManPage).
Runs OpenVPN as service, it will try to delay daemonization until the majority of initialization functions which are capable of generating fatal errors are complete.
Specifies the tun virtual network device as tun0, which helps in setting firewall and other related rules later. And also helps when you want to run multiple OpenVPN instances, you can do so by specifying tun device as tun1,tun2 and so on. Although it is a completely optional step and mentioning –dev tun is just as fine.
port 1194 proto udp4
Sets the port, protocol (we are using UDP protocol for high speed) to be used on both ends of the connection, i.e. server and client. Starting OpenVPN 2.4, for forcing IPv4 or IPv6 connection suffix tcp or udp with 4/6 like udp4/udp6/tcp4/tcp6 has to be mentioned, since we don’t have routed IPv6 block like in most cases when we rent a VPS, I have intentional put udp4 there, as we won’t be listening on v6.
user openvpn group openvpn
OpenVPN requires root access to set the network interface tun/tap when executing, so user and group are used to change user/group IDs of OpenVPN process after initialization. We would setup least privileged user/group on our system with additional commands which are as follows:
# addgroup --system --no-create-home --disabled-login openvpn # adduser --system --no-create-home --disabled-login openvpn
Tun/tap virtual network device requires root to setup and server private keys are advised to be read by root only, so once the OpenVPN process has dropped root privileges, these options helps to persist things without requiring root access again upon restart signal etc.
keepalive 10 120
Read the manual, it is self-explanatory.
txqueuelen 1000 sndbuf 786432 rcvbuf 786432 push "sndbuf 786432" push "rcvbuf 786432"
Increases the default length of the transmit queue and buffer sizes for high speed transfer.
float fast-io mssfix 0 compress
Read the manual, self-explanatory. For optimizing performance, I have tweaked the configuration by disabling OpenVPN’s ‘TCP Maximum Segment Size’ limitor using –mssfix 0
This is an important security precaution to protect against a man-in-the-middle attack where an authorized client attempts to connect to another client by impersonating the server. Read the manual for more.
topology subnet server 10.8.0.0 255.255.255.0 push "dhcp-option DNS 220.127.116.11" push "dhcp-option DNS 18.104.22.168" push "redirect-gateway def1 bypass-dhcp"
For –topology read the manual. –server, this directive will set up an OpenVPN server which will allocate addresses to clients out of the given network/netmask. When running multiple OpenVPN instances you have to keep in mind that the network/netmask and port or protocol you use has to be different from what is already running. For example you can run udp443.conf at –proto udp4 —port 443 –server 10.10.0.0 255.255.255.0 or run tcp1194.conf at same port –proto tcp-server –server 10.1.0.0 255.255.255.0.
dhcp-option is pushing the DNS nameservers for the OpenVPN client, that is going to pull these settings by default from the server. Frankly speaking, pushing external DNS servers is not a good option. Either setup your own DNS or just ignore dhcp-option DNS and use a good DNScrypt provider like dncrypt.is/.nl, more information available at dnscrypt.org.
redirect-gateway is configuring clients to route all the Internet traffic through vpn gateway, which is what we require when we setup a VPN for privacy.
Keep in mind these settings are pushed from the server to the clients for ease of configuration only, you are at liberty to configure these options in the client configuration file itself without the server needing to push them at all.
crl-verify crl.pem ca ca.crt cert main_server.crt key server.key
Read the manual, it is self-explanatory
TLS-crypt encrypts the tunnel with a pre-shared static key. It offers best protection against DoS attacks and also provides additional benefit of encrypting the TLS control panel by hiding the certificate used for the TLS connection hence making it harder to identify OpenVPN traffic as such.
#ifconfig-pool-persist /home/cipher/ipp.txt 300 duplicate-cn
It allows multiple clients to share the same client cert/key for authentication, making it harder for the server to identify an individual client. You can even opt for individual client cert/key, if so, uncomment –ifconfig-pool-persist line as it helps in effectively using the –persist-tun option and comment out –duplicate-cn as both options don’t go hand in hand.
Use of ncp-ciphers overrides the –cipher algo mention in the client configuration file. So, even if the client don’t mention –cipher at all, it would use –cipher bf-cbc being the default option, which would be overridded by one of the algo in the cipher list i.e AES-256-GCM or AES-128-GCM on server, depending on what is supported by the client.
Read the manual.
tls-version-min 1.2 tls-cipher TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384:TLS-ECDHE-RSA-WITH-AES-128-GCM-SHA256 dh none ecdh-curve secp384r1
I have restricted minimum TLS version to 1.2 and the cipher suites to be allowed. Since we are using ECDHE only we have disabled –dh and have specified –ecdh-curve to be used.
Minimizing the renegotiation period of data channel key to twenty minutes. I would say one would be extremely paranoid to set lower limit from the default one hour but it won’t hurt either.
Chroot defines the DIR mentioned in the option as the new root once the OpenVPN has been initialized. We have to keep in mind that because we have a new root or top-level DIR once OpenVPN has been initialized, we have to put all the necessary including but not limited to crl.pem, ipp.txt etc in this DIR and also grant it appropriate read/write permissions. Additional commands required to create a regular user account with home DIR and disabled login, which is as follows:
# adduser --disabled-login cipher
Sets the temp directory to /home/cipher/tmp/ as it is required to be in the chrooted DIR. I have created a new DIR tmp/ in /home/cipher for mounting it as tmpfs. Additional commands are required which are as follows:
# mkdir /home/cipher/tmp; chmod 1777 /home/cipher/tmp # nano /etc/fstab
Add as follows in /etc/fstab to permanently mount tmpfs in /home/cipher as /tmp:
tmpfs /home/cipher/tmp tmpfs defaults,noatime,nosuid,nodev,noexec,mode=1777,size=32M 0 0
Save and exit the /etc/fstab file and mount the newly set tmpfs as follows:.
# mount -a
Once mounted check if it is active as follows:
$ df -h
status s.log log v.log #log /dev/null #status /dev/null #verb 0 verb 3
Status and log writes the status and log files to s.log and v.log respectively instead of writing it to syslog. These logs would be stored in /etc/openvpn. I would recommend you to only use log files for debugging purposes and once you have a fully functional OpenVPN server as required by you, send the error and status logs to /dev/null instead. /dev/null is null device which is a device file that discards all data written to it but reports that the write operation succeeded. And also change the verb to 0. Because we aim to create a no-logs VPN server.
C. Packet forwarding, firewall rules and more
Packing forwarding is required to forward traffic from clients to the Internet.
Edit /etc/sysctl.conf as follows:
# nano /etc/sysctl.conf
Look for #net.ipv4.ip_forward=1 and uncomment it by removing # in front of the line. If and only if you wish to setup IPv6 OpenVPN server, also look for #net.ipv6.conf.all.forwarding=1
and do the same. Save and exit.
# sysctl -p
Configuring a firewall is a must to prevent unauthorized access to your VPS. I have used ufw, which is a popular and easy to use front-end for IPTables.
Lets start by installing it
# aptitude install ufw
UFW forwarding policy needs to be enabled in /etc/default/ufw
# nano /etc/default/ufw
Look for DEFAULT_FORWARD_POLICY=”DROP” and change it to DEFAULT_FORWARD_POLICY=”ACCEPT”.
Also, if you are only setting up Ipv4 VPN without any v6 support then look for IPV6=yes and change it to IPV6=no
Save and exit.
Time for additional UFW rules for IP masquerading of connected clients i.e. allowing OpenVPN traffic to main network interface:
# nano /etc/ufw/before.rules
/etc/ufw/below.rules would be as follows:
# # rules.before # # Rules that should be run before the ufw command line added rules. Custom # rules should be added to one of these chains: # ufw-before-input # ufw-before-output # ufw-before-forward # # START OPENVPN RULES # NAT table rules *nat :POSTROUTING ACCEPT [0:0] -A POSTROUTING -s 10.8.0.0/24 -o eth0 -j SNAT --to x.x.x.x COMMIT # END OPENVPN RULES
You would append the before.rules with text in between # START OPENVPN RULES to # END OPENVPN RULES from the above or even copy it as it is and replace the NIC name from eth0 to whatever you have on your VPS. Also change x.x.x.x with the Public IP of your VPS, to which this particular instance is to be binded. I do not recommend using -j MASQUERADE instead of -j SNAT –to x.x.x.x, as it won’t help when you have multiple instances of OpenVPN running on a VPS binded to different Public IPs. You can just add additional POSTROUTING rule like -A POSTROUTING -s 22.214.171.124/24 -o eth0 -j SNAT –to y.y.y.y and it would work seemlessly.
Allowing connections to SSH and OpenVPN port in ufw before enabling it:
# ufw allow 1194/udp # ufw allow 22/tcp # ufw enable
Enabling ufw with ufw enable, would give you a warning, “Command may disrupt existing ssh connections. Proceed with operation (y|n)?” Type y without any hesitation.
Once enabled verify it with the following command:
# ufw status verbose
One last firewall rule for VPN client isolation:
The OpenVPN “gateway” is actually a router and routing a packet back from where it came from is not a corner case or needs special handling. When multiple clients would connect to the VPN server, each would be alloted a private IP viz. 10.8.0.2,.3,.4 etc. So, even if we do not use –client-to-client nothing is stopping them from reaching each other with minimal efforts. It poses a security/privacy threat. You are required to set the following iptables rule to prevent such a situation:
# iptables -I FORWARD -i tun0 -o tun0 -j DROP
It drops packets that were received on the tun0 interface and where the outgoing interface is tun0, creating effective VPN client isolation.
This iptable rule won’t persist upon reboot, so install a package called iptables-persistent to make it persist as follows:
# aptitude install iptables-persistent
D. Create a PKI for OpenVPN, build a CA, generate server/client certs and keys.
In this guide, I am going to use easy-rsa, which is a CLI utility to build and manage a PKI CA. I would recommend you to use EasyRSA version 3 for creating a PKI for OpenVPN instead of version 2 that ships with major GNU/Linux distributions. EasyRSA version 2 is dated and has insecure options and values by default. With EasyRSA3 you get encrypted ca.key by default and better default digest for signing certs and also has support for cn_only DN mode. It also comes with elliptic curve crypto mode support, so now you can use tls cipher suites with ECDHE-ECDSA.
You can get EasyRSA version 3 by cloning it from github as follows:
$ git clone https://github.com/OpenVPN/easy-rsa.git
NOTE: Before we build a CA and generate other certs/keys, I highly recommend you to do this on a local computer (rather an air-gapped computer) instead of VPS, depending on how paranoid you are. And only transfer ca.crt,main_server.crt,server.key,static.key to VPS from local system using a secure transfer protocol like scp or sftp and the client cert/key has no use on the VPS where the VPN server is going to be deployed.
I am going generate it on VPS for the purpose of this guide as desired by most. Once cloned using the above git clone command, do as follows:
# cp -R easy-rsa/easyrsa3/ /etc/openvpn/ # cd /etc/openvpn/easyrsa3
EasyRSA uses a vars file for parameter settings. You can use the already provided vars.example file to edit it on your own. I have used RSA crypto mode, but you could also go with ec mode, if you desire to do so.
Make a copy of vars.example as vars as follows:
# cp vars.example vars # nano vars
Look for the following options in vars file and uncomment them and make changes as per your needs or use them as it is.
set_var EASYRSA "$PWD" set_var EASYRSA_OPENSSL "openssl" set_var EASYRSA_PKI "$EASYRSA/pki" set_var EASYRSA_DN "cn_only" set_var EASYRSA_KEY_SIZE 4096 set_var EASYRSA_ALGO rsa set_var EASYRSA_CA_EXPIRE 3650 set_var EASYRSA_CERT_EXPIRE 365 set_var EASYRSA_CRL_DAYS 180 set_var EASYRSA_SSL_CONF "$EASYRSA/openssl-1.0.cnf" set_var EASYRSA_DIGEST "sha512"
Save and exit vars file.
Now lets create a new PKI and CA as follows:
# ./easyrsa init-pki # ./easyrsa build-ca
This would generate a root CA key/server set. When prompted for a password, input a long pass phrase to encrypt your ca.key, which would be required for issuing server/client certs.
It would prompt you for Common Name, just hit return key and leave it to the default.
Generate a private key and request for server, client as follows:
# ./easyrsa gen-req server nopass # ./easyrsa gen-req client nopass
We have generated private keys for server/client along with request file, as you might have noticed we have mentioned nopass argument, this would generate unencrypted private keys. You might not want to use nopass for client and use a passphrase to encrypt its respective private key for additional security. But, I personally find using additional pass phrase for private key for client as redundant, albeit, I always create a new PKI for OpenVPN on a local air-gapped system and only transfer the required files to VPS where VPN server is to be deployed.
Import the requests, giving it an arbitrary “short name” with the syntax:
./easyrsa import-req /path/to/received.req UNIQUE_SHORT_FILE_NAME
# ./easyrsa import-req pki/reqs/server.req main_server # ./easyrsa import-req pki/reqs/client.req client1
The request has been successfully imported with a short name of: main_server/client1.
Finally sign it as one of the types: server or client.
# ./easyrsa sign server main_server # ./easyrsa sign client client1
You would be prompted to enter pass phrase for private ca.key, just input the same and hit the return key. The CA returns the signed certs which are stored in pki/issued/.
# ./easyrsa gen-crl
This would generate a CRL at pki/crl.pem.
Once last thing is static.key which is required for –tls-crypt:
# cd /etc/openvpn # openvpn --genkey --secret static.key
E. Managing root CA cert, server cert/key and other files:
Finally, we are done with all the difficult part, now have to move ca.crt,server.key,server.crt,crl.pem to the right places.
# cp easyrsa3/pki/issued/main_server.crt . # cp easyrsa3/pki/private/server.key . # cp easyrsa3/pki/ca.crt . # cp easyrsa3/pki/crl.pem /home/cipher/
Setting the right permissions is almost important:
# chmod 400 *.key
Makes the server/static.key read only by root being the owner.
# chmod 444 /home/cipher/crl.pem
Makes the crl.pem read only by anyone.
F. Starting OpenVPN server and enabling it to run on reboot
# systemctl start [email protected] # systemctl enable [email protected]
You can check if the OpenVPN instance is running as follows:
# systemctl status [email protected] # ifconfig tun0
If the status shows active then look at /etc/openvpn/v.log for any non-fatal errors as follows:
# cat v.log
If you see nothing wrong, then change –log and –status in /etc/openvpn/udp.conf to /dev/null like we stated above and restart the OpenVPN instance as follows:
# nano udp.conf
Change log, status and verb in udp.conf as follows:
log /dev/null status /dev/null verb 0
Save and exit.
Restart the OpenVPN instance and check the status as follows:
# systemctl restart [email protected] # systemctl status [email protected]
Hurrah! We have a working OpenVPN server-side setup is done.
G. Client configuration
Download client1.crt, client.key, ca.crt, static.key from the server to a local computer with scp on a GNU/Linux (assuming you use it or else go with WinSCP or Filezilla to securely transfer these files)as follows:
$ scp [email protected]<VPS IP>:/etc/openvpn/easyrsa3/pki/ca.crt . $ scp [email protected]<VPS IP>:/etc/openvpn/easyrsa3/pki/issued/client1.crt . $ scp [email protected]<VPS IP>:/etc/openvpn/easyrsa3/pki/private/client.key . $ scp [email protected]<VPS IP>:/etc/openvpn/static.key .
Replace <VPS IP> with the IP/hostname of your VPS. The above commands are required to be followed on a client system.
I have already written a model client.ovpn, which is made available below. All the options used therein are either similar to server configuration or self-explanatory, so refer to the manual in case of doubts. You can use it as it is or make changes as required.
# model client.ovpn client dev tun proto udp4 remote <VPS IP> 1194 resolv-retry infinite nobind persist-key persist-tun compress mute-replay-warnings reneg-sec 0 explicit-exit-notify 3 ca ca.crt cert client1.crt key client.key remote-cert-tls server tls-crypt static.key #block-outside-dns # uncomment the above for Windows 8 or newer to prevent DNS leaks verb 3 # change it to verb 0 once you have a stable client connection # user openvpn # group openvpn
Save client.ovpn in the same DIR where you have client key/cert and other files.
Connect to the OpenVPN server on GNU/Linux OpenVPN client as follows to test your VPN:
$ sudo openvpn --config client.ovpn
Upon successful connection you would see as follows:
Sat Aug 5 00:24:48 2017 Initialization Sequence Completed
Kindly visit a website like https://ipchicken.com to check your IP, if it is your VPS IP then you did it! Else dig for errors.
For Windows/OSX/Android, just copy all the files client.ovpn,client1.crt,client.key,ca.crt,static.key to any folder for example myvpn, and just import client.ovpn to the respective OpenVPN client for the given platform and it should work just fine.
Some tips to secure your VPN client configuration on GNU/Linux OpenVPN client would be to set the –user and –group to openvpn even in client.ovpn, which would require us to create openvpn group/user just like we did with OpenVPN server above. Also you can set the –verb 0, provided that you have no issues and a stable VPN connection for a good amount of time during the 1st trial itself.
Finally! We have successfully hosted a no-logs OpenVPN server on a VPS for as low as 10 EUR a year not just for you but even for your loved ones. It enforces TLSv1.2, uses AEAD cipher only for both tls control channel and data channel, also encrypt the key exchange itself with –tls-crypt making your OpenVPN connection harder to be identified. We have made efforts to isolate VPN clients. We have also setup a chroot with a tmpfs mounted inside it. We made our OpenVPN instance drop priveliges once properly executed to least priveliged system users making it even more secure.
Additional details for IPv6 support:
For setting up OpenVPN with IPv6 support, a routed network block of IPv6 that will reach the host configured as the OpenVPN server is a must.
As you already know about https://urdn.com.ua, a no data required KVM VPS provider, that gives you a routed network block of IPv6 at no additional cost at all.
I thought it is my humble duty to share the IPv6 specifics with you.
1. We would not set IPv6=no in /etc/default/ufw as mentioned above under Firewall rules.
2. In /etc/sysctl.conf, along with net.ipv4.ip_forward=1 we would also look for net.ipv6.conf.all.forwarding=1 and uncomment it.
3. We would also require the following changes in /etc/openvpn/udp.conf:
–proto udp4 would change to –proto udp
Add as follows in addition to what is already configured:
server-ipv6 2001:db8:0:123::/64 push "route-ipv6 2000::/3"
Replace the network block in –server-ipv6 with the one your VPS provider has alloted. –route-ipv6 is being pushed to the client in order to redirect all internet-bound ipv6 traffic as desired.
There is not much to change in the client configuration other than the –proto udp4 to udp.
By keeping the abovesaid in mind, you should not have any issues setting up IPv6 with OpenVPN server at all.
For any issues, suggestions or further help, you are free to comment.
Thanks for reading!