How to set up VPN network for securetly managing Linux servers in 15 minutes.

Adam Jurkiewicz Pythonista
8 min readAug 18, 2023

This article explains how to set up VPN network based on OpenVPN OpenSource project https://community.openvpn.net/openvpn

Image from https://openvpn.net site.

In this example, we set up server on my linux-admin.jurkiewicz.tech machine : VPS NVMe.Light — https://rapiddc.pl/

Welcome to Ubuntu 22.04.3 LTS (GNU/Linux 5.15.0-79-generic x86_64)

* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage

System information as of Thu Aug 17 08:55:08 PM CEST 2023

System load: 0.03857421875 Processes: 97
Usage of /: 36.6% of 19.60GB Users logged in: 0
Memory usage: 10% IPv4 address for eth0: 188.68.237.191
Swap usage: 0%

All scriptts and files you can find in my GitHub repository

We will use OpenVPN from system package:

root@linux-admin:~# apt info openvpn
Package: openvpn
Version: 2.5.5-1ubuntu3.1
Priority: optional
Section: net
Origin: Ubuntu
Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
Original-Maintainer: Bernhard Schmidt <berni@debian.org>
Bugs: https://bugs.launchpad.net/ubuntu/+filebug

If there is no openvpn installed in system, we can install it with standard command:

root@linux-admin:~# apt install openvpn

Next, we have to put few files from out repository to our server. We can use Midnight Commander or any other software like scpfor this.

root@linux-admin:/etc/openvpn/server# ls -l
total 56
-rwxr-xr-x 1 root root 242 Aug 17 21:23 build-ca
-rwxr-xr-x 1 root root 228 Aug 17 21:23 build-dh
-rwxr-xr-x 1 root root 516 Aug 17 21:23 build-key
-rwxr-xr-x 1 root root 662 Aug 17 21:23 build-key-server
-rwxr-xr-x 1 root root 280 Aug 17 21:23 clean-all
-rwxr-xr-x 1 root root 268 Aug 17 21:23 make-crl
-rw-r--r-- 1 root root 7491 Aug 17 21:23 openssl.cnf
-rw-r--r-- 1 root root 1303 Aug 17 21:23 vars
-rw-r--r-- 1 root root 9659 Aug 17 21:23 vpn_example.conf

And next, we have to tweak our vars file and fill aur data. We use standard vim to do it:

root@linux-admin:/etc/openvpn/server# vim vars

## we have to change the following env variables:

export KEY_COUNTRY="PL"
export KEY_PROVINCE="Mazowsze"
export KEY_CITY="Warszawa"
export KEY_ORG="OpenVPN-Adam"
export KEY_EMAIL="vpn@jurkiewicz.tech"

We must run vars to export variables using command:

root@linux-admin:/etc/openvpn/server# source vars
NOTE: when you run ./clean-all, I will be doing a rm -rf on /etc/openvpn/server/keys

We can check if everything is correct:

root@linux-admin:/etc/openvpn/server# env | grep KEY_
KEY_COUNTRY=PL
KEY_EMAIL=vpn@jurkiewicz.tech
KEY_ORG=OpenVPN-Adam
KEY_PROVINCE=Mazowsze
KEY_SIZE=2048
KEY_CITY=Warszawa
KEY_DIR=/etc/openvpn/server/keys
KEY_CONFIG=/etc/openvpn/server/openssl.cnf

The first step in building an OpenVPN 2.0 configuration is to establish a PKI (public key infrastructure). The PKI consists of:

  • a separate certificate (also known as a public key) and private key for the server and each client, and
  • a master Certificate Authority (CA) certificate and key which is used to sign each of the server and client certificates.

We initialize PKI

First we have to clean and create some directories:

root@linux-admin:/etc/openvpn/server# ./clean-all 

Second, we create master Certificate Authority (CA) certificate (remember to write server-namefor Common Name)

root@linux-admin:/etc/openvpn/server# ./build-ca
..+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*...........+.+.....+.+........+.+...+..+...+.......+.........+..+.........+.+..+...+.............+..+....+...+...+........+......+...+.......+.....+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*..+..........+.....+.+.....+.......+..+....+.........+..+....+..+....+......+............+...........+.+.....+....+.....+....+...........+....+........+...................+...+..+.........+.......+.....+....+...+.....+............+...+......+....+.....+.......+...........+...+....+.....+.......+...+..+....+.....+..........+.....+.+...+.................+.+..............+.+.....+....+......+..............+.+........................+...+...+...+.....+......+...+......+.......+.........+...+...+............+...........+......+.+.....+............+.+.....+.+......+..+......+..........+...........+.+.........+..............+....+...+............+...+..+...+....+...+......+.....+.......+.....+....+.....+.+...+......+.........+.....+......+.+.........+...+...........+......+...+..........+.....+...+............+...+...+.......+........+.......+...............+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
..........+.+.........+...+..+..........+...+........+.........+..........+...+..+....+..+...+.+......+..+.+..+.......+.....+......+......+....+..+...+.+...+..+.........+.+...+...........+.......+..+.+.........+.....+...+.............+..+....+.....+.+...+..............+.+.....+....+..+.+.....+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*..+.......+........+.+.....+....+...+.....+...+....+........+...+....+......+...........+....+.....+.+...+.....+............+..........+.....+.+..+......+.+.....+......+...+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*...+....+......+............+..+.......+.....+.+........+.......+.....+..........+..+............+....+..+...+.........+............+.+..+......+.+.........+.....+.......+............+..+....+..............+...............+.+.........+...+........+.+......+..+.+.....+.........+...+......................+........+..........+..+..........+..+..........+......+.....+....+......+..............+......+...+...............+....+..+......+...+....+......+.........+..+......+...+....+......+.........+...+..............+..........+......+..+...+..........+..............+.+..+...............+.+..+...+....+...+...+.........+...+........+.+.........+.....+....+.....+...+...............+.......+..+.+.................+......+....+...+.........+.....+...+.+......+.........+.....+.........+......+......+.....................+....+...+.....+.+......+......+...+..+....+.....+.........+.+......+.....+.......+...............+........+...+......+...+..................+...+.+..............+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [PL]:
State or Province Name (full name) [Mazowsze]:
Locality Name (eg, city) [Warszawa]:
Organization Name (eg, company) [OpenVPN-Adam]:
Organizational Unit Name (eg, section) []:
Common Name (eg, your name or your server's hostname) []:linux-admin.jurkiewicz.tech
Email Address [vpn@jurkiewicz.tech]:

Third, we will generate a certificate and private key for the server (remember to answer Y to questions in this process and just press “ENTER” for a Challenge password — it means, there will be no password in connection process):

root@linux-admin:/etc/openvpn/server# ./build-key-server server
Ignoring -days without -x509; not generating a certificate
............+......+++++++++++++++++++++++++++++++++++++++++++++++++++++
++++++++++++*...+..........+...++++++++++++++++++++++++++++++++++++++++++
+++++++++++++++++++++++*....+....+...+..+++++++++++++++++++++++++++++++++++
++++++++++++++++++++++++++++++
..+.....+.+.........+..+.+........+.......+..+.+.....+++++++++++++++++++++++
++++++++++++++++++++++++++++++++++++++++++*.+...+....+..+++++++++++++++++++++
++++++++++++++++++++++++++++++++++++++++++++*....+..+...+....+...+.........+..
......+......+.+......+.........+.....+.+........................+...+.....+..
....+....+...+............+........+....+........+.+..............+.......+...
..+....+.................+...............+....+...++++++++++++++++++++++++++++
+++++++++++++++++++++++++++++++++++++
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [PL]:
State or Province Name (full name) [Mazowsze]:
Locality Name (eg, city) [Warszawa]:
Organization Name (eg, company) [OpenVPN-Adam]:
Organizational Unit Name (eg, section) []:
Common Name (eg, your name or your server's hostname) []:linux-admin.jurkiewicz.tech
Email Address [vpn@jurkiewicz.tech]:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
Using configuration from /etc/openvpn/server/openssl.cnf
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
countryName :PRINTABLE:'PL'
stateOrProvinceName :PRINTABLE:'Mazowsze'
localityName :PRINTABLE:'Warszawa'
organizationName :PRINTABLE:'OpenVPN-Adam'
commonName :PRINTABLE:'linux-admin.jurkiewicz.tech'
emailAddress :IA5STRING:'vpn@jurkiewicz.tech'
Certificate is to be certified until Aug 14 19:36:10 2033 GMT (3650 days)
Sign the certificate? [y/n]:y


1 out of 1 certificate requests certified, commit? [y/n]y
Write out database with 1 new entries
Data Base Updated

Fourth, the Diffie Hellman parameters must be generated for the OpenVPN server — this will take some time, it depends on CPU:

root@linux-admin:/etc/openvpn/server# ./build-dh 
Generating DH parameters, 2048 bit long safe prime
........................................................................+..
...+.........................................................................
........+...................................................................
...............................................+............................
................................

Fifth, we need to have CRL file, so we create one:

root@linux-admin:/etc/openvpn/server# ./make-crl crl.pem
Using configuration from /etc/openvpn/server/openssl.cnf

Now, we can tweak server’s configuration file: /etc/openvpn/server/vpn_server.conf

We need to set up VPN Network number, and IP port number. on which VPN serwer will listen to clients; we change the following parameters according to our needs (below there are mine on Ubuntu system):

# these are my values - please read the comments in file
server 172.30.0.0 255.255.0.0
client-config-dir ccd
route 172.30.0.0 255.255.0.0
#
user nobody
group nogroup
port 1195
dh keys/dh2048.pem
client-to-client
status /etc/openvpn/server/openvpn-status.log
log /etc/openvpn/server/openvpn.log
log-append /etc/openvpn/server/openvpn.log

When we finished, we can try to start server:

root@linux-admin:/etc/openvpn/server# systemctl enable openvpn-server@vpn_server
Created symlink /etc/systemd/system/multi-user.target.wants/openvpn-server@vpn_server.service → /lib/systemd/system/openvpn-server@.service.
root@linux-admin:/etc/openvpn/server# systemctl start openvpn-server@vpn_server

And next, we can check….

root@linux-admin:/etc/openvpn/server# systemctl status openvpn-server@vpn_server
● openvpn-server@vpn_server.service - OpenVPN service for vpn_server
Loaded: loaded (/lib/systemd/system/openvpn-server@.service; enabled; vendor preset: enabled)
Active: active (running) since Fri 2023-08-18 16:33:48 CEST; 48s ago
Docs: man:openvpn(8)
https://community.openvpn.net/openvpn/wiki/Openvpn24ManPage
https://community.openvpn.net/openvpn/wiki/HOWTO
Main PID: 1420 (openvpn)
Status: "Initialization Sequence Completed"
Tasks: 1 (limit: 2219)
Memory: 2.9M
CPU: 13ms
CGroup: /system.slice/system-openvpn\x2dserver.slice/openvpn-server@vpn_server.service
└─1420 /usr/sbin/openvpn --status /run/openvpn-server/status-vpn_server.log --status-version 2 --suppress-timestamps --co>

Aug 18 16:33:48 linux-admin.jurkiewicz.tech systemd[1]: Starting OpenVPN service for vpn_server...
Aug 18 16:33:48 linux-admin.jurkiewicz.tech systemd[1]: Started OpenVPN service for vpn_server.

root@linux-admin:/etc/openvpn/server# ifconfig tun0
tun0: flags=4305<UP,POINTOPOINT,RUNNING,NOARP,MULTICAST> mtu 1500
inet 172.30.0.1 netmask 255.255.255.255 destination 172.30.0.2
inet6 fe80::e957:9659:7ebe:f5e3 prefixlen 64 scopeid 0x20<link>
unspec 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 txqueuelen 500 (UNSPEC)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 5 bytes 240 (240.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

Everything is working!

The last thing is to create client’s certificates, we will generate for one client.

First, we need to write server IP in file: /etc/openvpn/server/client/client.conf (below my server IP)

##############################
# IP and port for server - this shoud be changed
remote 188.68.237.191 1195

We will use mk-openvpn-key script , which is included in my GitHub repository. Remember about challenge password — just press ENTER (no password).

root@linux-admin:~# mk-openvpn-key vpn_adam_laptop
NOTE: when you run ./clean-all, I will be doing a rm -rf on /etc/openvpn/server/keys
OK, i will create: vpn_adam_laptop. [ENTER]
----------------------------------
CLIENT_KEY: vpn_adam_laptop ...
CLIENT_CONFIG_FILE : vpn_adam_laptop.conf
Press ENTER...

Ignoring -days without -x509; not generating a certificate
......+........+...............+.......+..+...+............+...+.+..+...+.+...+......+......+......+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*..+.....+.+...+..+.+..+......+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*..+............+..+................+..+.+..+....+...........+...+.+......+.....+..........+.....+.......+.....+.............+.........+........+.......+..............+....+.....+................+..+.........+.+.........+..............+.+........+...+...+.......+...+......+.....+.+...+..+.......+...+..................+..+.........+.............+........+....+........+.+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.......+......+.+.........+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*....+...+............+..+.+....................+...+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*.+....+.....+...+......+.........+.+.........+..+.+............+..+.+...+.........+...+.................+.......+...+.....+...+...+.+.....+...+...............+...+....+........+.......+......+.....+...+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [PL]:
State or Province Name (full name) [Mazowsze]:
Locality Name (eg, city) [Warszawa]:
Organization Name (eg, company) [OpenVPN-Adam]:
Organizational Unit Name (eg, section) []:
Common Name (eg, your name or your server's hostname) []:vpn_adam_laptop
Email Address [vpn@jurkiewicz.tech]:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
Using configuration from /etc/openvpn/server/openssl.cnf
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
countryName :PRINTABLE:'PL'
stateOrProvinceName :PRINTABLE:'Mazowsze'
localityName :PRINTABLE:'Warszawa'
organizationName :PRINTABLE:'OpenVPN-Adam'
commonName :T61STRING:'vpn_adam_laptop'
emailAddress :IA5STRING:'vpn@jurkiewicz.tech'
Certificate is to be certified until Aug 18 19:16:28 2033 GMT (3650 days)
Sign the certificate? [y/n]:y


1 out of 1 certificate requests certified, commit? [y/n]y
Write out database with 1 new entries
Data Base Updated
tar: Removing leading `../' from member names
../tmp/vpn_adam_laptop.conf
tar: Removing leading `../' from hard link targets
../tmp/vpn_adam_laptop.crt
../tmp/vpn_adam_laptop.key

We can now place client’s files in /etc/openvpn directory:

root@devel:/etc/openvpn# ls -la
razem 68
drwxr-xr-x 4 root root 4096 08-21 21:51 .
drwxr-xr-x 150 root root 12288 08-21 21:44 ..
-rw-r--r-- 1 adasiek adasiek 1712 08-17 21:34 ca_server_rapiddc.crt
-rw-r--r-- 1 adasiek adasiek 600 08-21 21:51 client_rapiddc.conf
-rw-r--r-- 1 root root 5110 08-21 21:09 vpn_adam_main.crt
-rw------- 1 root root 1704 08-21 21:09 vpn_adam_main.key

Next, we restart a client machine… and we have VPN tunel available:

tun0: flags=4305<UP,POINTOPOINT,RUNNING,NOARP,MULTICAST>  mtu 1500
inet 172.30.0.9 netmask 255.255.255.255 destination 172.30.0.10
inet6 fe80::d93c:6480:714b:5240 prefixlen 64 scopeid 0x20<link>
unspec 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 txqueuelen 500 (UNSPEC)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 7 bytes 336 (336.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

We have IP tunnel, secure and private, just between two machines with Linux.

root@devel:/etc/openvpn# ifconfig tun0
tun0: flags=4305<UP,POINTOPOINT,RUNNING,NOARP,MULTICAST> mtu 1500
inet 172.30.0.9 netmask 255.255.255.255 destination 172.30.0.10
inet6 fe80::d93c:6480:714b:5240 prefixlen 64 scopeid 0x20<link>
unspec 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 txqueuelen 500 (UNSPEC)
RX packets 1 bytes 48 (48.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 9 bytes 432 (432.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

root@devel:/etc/openvpn# ping -c3 -w3 172.30.0.1
PING 172.30.0.1 (172.30.0.1) 56(84) bytes of data.
64 bytes from 172.30.0.1: icmp_seq=1 ttl=64 time=24.6 ms
64 bytes from 172.30.0.1: icmp_seq=2 ttl=64 time=24.0 ms
64 bytes from 172.30.0.1: icmp_seq=3 ttl=64 time=24.3 ms

--- 172.30.0.1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
rtt min/avg/max/mdev = 24.018/24.324/24.621/0.246 ms

root@devel:/etc/openvpn# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 192.168.22.1 0.0.0.0 UG 100 0 0 eno1
172.30.0.0 172.30.0.10 255.255.0.0 UG 0 0 0 tun0
172.30.0.10 0.0.0.0 255.255.255.255 UH 0 0 0 tun0
192.168.22.0 0.0.0.0 255.255.255.0 U 100 0 0 eno1

Last, but not least — we need to secure VPN Server. We ill ALLOW traffic only from VPN network, and just enable VPN UDP port from outside.

root@linux-admin:~# ufw status verbose
Status: active
Logging: off
Default: deny (incoming), allow (outgoing), disabled (routed)
New profiles: skip

To Action From
-- ------ ----
Anywhere ALLOW IN 172.30.0.0/24
1195/udp ALLOW IN Anywhere

--

--

Linux (Debian/Ubuntu) admin 😆, Python (OOP, fastAPI) programmer 🖥️ | Teacher, trainer 📚 ⚓