<--

Classic office VPN Setup (OpenVPN)

Intro

VPN was originally created to extend a private corporate LAN across a public network, allowing remote users to reach internal resources without exposing them directly to the internet. Rather than making file shares, intranet services, databases, or administrative interfaces publicly reachable, the VPN terminates an authenticated, encrypted tunnel at the network edge and then forwards authorized traffic into the internal office subnet.
OpenVPN became one of the first SSL/TLS-based generation of VPNs (~2002), comparing to older PPTP/IPsec lineage.
Here a simple illustration:

           Remote User
         (laptop / phone)
                |
                |   encrypted OpenVPN tunnel
                |=======================================\
                |                                        \
                v                                         v
        [ public internet ]                       +----------------+
                                                 | OpenVPN Server |
                                                 +----------------+
                                                          |
                                                          | routed access
                                                          v
                                                 +------------------+
                                                 |   Office LAN     |
                                                 |------------------|
                                                 | files / intranet |
                                                 | db / printers    |
                                                 | internal apps    |
                                                 +------------------+

Here is a protocol-focused OpenVPN diagram:

   OpenVPN Client                                   OpenVPN Server
   (user device)                                    (vpn gateway)
        |                                                 |
        |   TCP/UDP connection to server port             |
        |------------------------------------------------>|
        |                                                 |
        |   TLS handshake                                 |
        |      - server certificate check                 |
        |      - optional client certificate              |
        |<----------------------------------------------->|
        |                                                 |
        |   User authentication                           |
        |      - username/password, cert, or both         |
        |<----------------------------------------------->|
        |                                                 |
        |   Session keys established                      |
        |<----------------------------------------------->|
        |                                                 |
        |   Encrypted VPN tunnel comes up                 |
        |<----------------------------------------------->|
        |                                                 |
        |   IP packets carried inside tunnel              |
        |      (internal routes / office resources)       |
        |<----------------------------------------------->|
        |                                                 |

In case of OpenVPN, it’s also really easy to install and configure clients - their usually presented on any major apps maketplace.
So we mostly will focus on setting up the server part.

Prerequisites

Prepare a machine running Ubuntu 22.04 with at least 2 vCPU, 2 GB RAM, and 20 GB disk.

Also ensure your network edge has a static public IP and that port 4443 is forwarded from the router/firewall to this machine so OpenVPN clients can reach it from the internet.

Server set up

Installing OpenVPN and Easy-RSA

First, install openvpn itself:

sudo apt update
sudo apt install openvpn easy-rsa -y

Let’s assume the username both on the server and the client part is testuser.

mkdir ~/easy-rsa
ln -s /usr/share/easy-rsa/* ~/easy-rsa/
sudo chown testuser ~/easy-rsa
chmod 700 ~/easy-rsa

Creating a PKI for OpenVPN

cd ~/easy-rsa
vi vars

Insert the following:

set_var EASYRSA_ALGO "ec"
set_var EASYRSA_DIGEST "sha512"

Run:

./easyrsa init-pki
./easyrsa build-ca nopass

Generate Server Certificate and Key

./easyrsa gen-req server nopass
./easyrsa sign-req server server

When prompted to confirm the signing request, type yes.

openvpn --genkey --secret ta.key

Copy the generated files to the /etc/openvpn directory:

sudo cp pki/ca.crt pki/issued/server.crt pki/private/server.key ta.key /etc/openvpn/server

Configuring OpenVPN Server

sudo cp /usr/share/doc/openvpn/examples/sample-config-files/server.conf /etc/openvpn/server/

sudo vi /etc/openvpn/server/server.conf

Make the following changes in the file (keep in mind that comments could start both with # and ;):

;tls-auth ta.key 0 # This file is secret
tls-crypt ta.key

cipher AES-256-CBC
;cipher AES-256-GCM
auth SHA256

;dh dh2048.pem
dh none

Uncomment the following:

user nobody
group nogroup

(Optional) Push DNS Changes to redirect all client traffic through the VPN (which you don’t usually want if it’s just access to the local network; more on that - in the Client Configuration Infrastructure section).
Uncomment:

push "redirect-gateway def1 bypass-dhcp"
push "dhcp-option DNS 208.67.222.222"
push "dhcp-option DNS 208.67.220.220"
push "dhcp-option DNS 8.8.8.8"
push "dhcp-option DNS 1.1.1.1"
...

Add any other DNS servers you’re intersted in.

Adjust the Port and Protocol

port 4443
proto udp
explicit-exit-notify 0

It’s better to setup udp. OpenVPN already does its own reliability/ordering where needed. If you run VPN traffic inside TCP, you can get the classic “TCP-over-TCP meltdown” problem: worse throughput and higher latency.

But for restrictive networks that block UDP - use:

proto tcp

Adjusting the OpenVPN Server Networking Configuration

sudo vi /etc/sysctl.conf

Then add the following line at the bottom of the file:

net.ipv4.ip_forward = 1

To read the file and load the new values for the current session:

sudo sysctl -p

Expected output:

net.ipv4.ip_forward = 1

Firewall Configuration

ip route list default

Ecpected output:

default via 10.129.0.1 dev eth0 proto dhcp src 10.129.0.40 metric 100

Take the interface, at this case it’s eth0, and update ufw rules file:

sudo vi /etc/ufw/before.rules
#
# 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]
# Allow traffic from OpenVPN client to eth0 (change to the interface you discovered!)
-A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE
COMMIT
# END OPENVPN RULES
 
# Don't delete these required lines, otherwise there will be errors
*filter
sudo vi /etc/default/ufw

Add ACCEPT instead of DROP:

DEFAULT_FORWARD_POLICY="ACCEPT"

Save file and quit.

Allos needed ports:

sudo ufw allow 4443/tcp
sudo ufw allow 4443/udp
sudo ufw allow OpenSSH

And for DNS port:

sudo ufw allow 53/tcp
sudo ufw allow 53/udp

Reload rules:

sudo ufw disable
sudo ufw enable

Starting OpenVPN Server

Enable the daemon:

sudo systemctl -f enable openvpn-server@server.service

Start:

sudo systemctl start openvpn-server@server.service

Check status (should be in active (running) state):

sudo systemctl status openvpn-server@server.service

Check the logs in case of issues:

sudo journalctl -u openvpn-server@server.service

Creating the Client Configuration Infrastructure

Make a directory to store certs in (do only once):

mkdir -p ~/client-configs/keys
chmod -R 700 ~/client-configs

Copy server certs - do it only once, when setting up the server, not on every new client certs generation:

cp ~/easy-rsa/ta.key ~/client-configs/keys/
sudo cp /etc/openvpn/server/ca.crt ~/client-configs/keys/
sudo chown gas.gas ~/client-configs/keys/*

cp /usr/share/doc/openvpn/examples/sample-config-files/client.conf ~/client-configs/base.conf

Open and edit the config:

vi ~/client-configs/base.conf

Change to the following:

remote <PUBLIC_IPV4> 4443
proto udp # or proto tcp in case udp can't be established
user nobody
group nogroup

Usually it’s useful to setup routing to only apply vpn on target network subnet addresses

Discover subnets in the local networks (office) you want to expose:

ip addr | grep "inet 192"

Possible output:

inet 192.168.88.81/24 brd 192.168.88.255 scope global dynamic noprefixroute enp11s0

Search for all machines in that subnet to be sure that that’s what you’ll need:

nmap -sn 192.168.88.0/24

Sample output:

Nmap scan report for router.lan (192.168.88.1)
Host is up (0.0026s latency).
Nmap scan report for 192.168.88.61
Host is up (0.0052s latency).
Nmap scan report for 192.168.88.63
Host is up (0.00041s latency).
Nmap scan report for 192.168.88.75
Host is up (0.076s latency).
Nmap scan report for hq-2 (192.168.88.81)
Host is up (0.000021s latency).
...

And after that, you can just add that subnet to the client config, so VPN could route users to these machines when any ip in that range being used:

# Split tunnel: do NOT accept default-route from server
pull-filter ignore "redirect-gateway"
# Route only the office LAN(s) through OpenVPN
route 192.168.88.0 255.255.255.0

The only disadvantage in that simple setup - if the office subnet interfers with the users’ home local network, but it usually doesn’t happen, and not really a huge issue.

Comment out the keys:

;ca ca.crt
;cert client.crt
;key client.key
;tls-auth ta.key 1

Define cipher as it’s defined in the server config:

cipher AES-256-CBC
auth SHA256

key-direction 1

Add the following commented out lines, which will be useful for configuring Linux clients:

; script-security 2
; up /etc/openvpn/update-resolv-conf
; down /etc/openvpn/update-resolv-conf

; script-security 2
; up /etc/openvpn/update-systemd-resolved
; down /etc/openvpn/update-systemd-resolved
; down-pre
; dhcp-option DOMAIN-ROUTE .

Next, create a script which will compile the client configs:

vi ~/client-configs/make_config.sh
#!/bin/bash
 
# First argument: Client identifier
 
KEY_DIR=~/client-configs/keys
OUTPUT_DIR=~/client-configs/files
BASE_CONFIG=~/client-configs/base.conf
 
cat ${BASE_CONFIG} \
    <(echo -e '<ca>') \
    ${KEY_DIR}/ca.crt \
    <(echo -e '</ca>\n<cert>') \
    ${KEY_DIR}/${1}.crt \
    <(echo -e '</cert>\n<key>') \
    ${KEY_DIR}/${1}.key \
    <(echo -e '</key>\n<tls-crypt>') \
    ${KEY_DIR}/ta.key \
    <(echo -e '</tls-crypt>') \
    > ${OUTPUT_DIR}/${1}.ovpn
chmod 700 ~/client-configs/make_config.sh

Generating Client Configurations

Convinience script (you can put it to the ~/client-configs/generate_client_config.sh and run from there like ./generate_client_config.sh testuser)

#!/usr/bin/env bash
set -Eeuo pipefail

CLIENT_NAME="${1:-}"

if [[ -z "$CLIENT_NAME" ]]; then
  echo "Usage: $0 <client-config-name>" >&2
  exit 1
fi

EASYRSA_DIR="$HOME/easy-rsa"
CLIENT_CONFIGS_DIR="$HOME/client-configs"
KEYS_DIR="$CLIENT_CONFIGS_DIR/keys"
FILES_DIR="$CLIENT_CONFIGS_DIR/files"

log() {
  printf '[%s] %s\n' "$(date '+%F %T')" "$*"
}

die() {
  log "ERROR: $*"
  exit 1
}

run_auto_confirm() {
  log "Auto-confirm enabled for: $*"
  printf 'yes\n\n' | "$@"
}

cd "$EASYRSA_DIR"

log "Starting client config generation for: $CLIENT_NAME"

log "Generating client request and private key"
run_auto_confirm ./easyrsa gen-req "$CLIENT_NAME" nopass

log "Signing client request"
run_auto_confirm ./easyrsa sign-req client "$CLIENT_NAME"

log "Copying client key and certificate to central keys directory"
mkdir -p "$KEYS_DIR"
cp -fv \
  "pki/private/${CLIENT_NAME}.key" \
  "pki/issued/${CLIENT_NAME}.crt" \
  "$KEYS_DIR/"

cd "$CLIENT_CONFIGS_DIR"

log "Generating .ovpn config"
./make_config.sh "$CLIENT_NAME"

log "Listing generated files"
ls -l "$FILES_DIR"

OVPN_FILE="$FILES_DIR/${CLIENT_NAME}.ovpn"
[[ -f "$OVPN_FILE" ]] || die "Expected output file was not created: $OVPN_FILE"

log "Done"
log "Generated client config: $OVPN_FILE"

First, generate needed certs for the new client (just pass-by all the suggestion from the easyrsa generator):

cd ~/easy-rsa
./easyrsa gen-req testuser nopass

Sign, type yes in the end:

./easyrsa sign-req client testuser

And then copy all that to keep all client certs in a central place:

cp pki/private/testuser.key pki/issued/testuser.crt ~/client-configs/keys/

Now, generate the actual config file:

cd ~/client-configs
./make_config.sh testuser

ls ~/client-configs/files

The file should be there:

~/client-configs/files/testuser.ovpn

Copy it to the client machine (run it on your local machine):

scp <USER>@<PUBLIC_IPV4>:/home/gas/client-configs/files/testuser.ovpn ~/Documents/openvpn-configs/

That’s it - you just need to share that .ovpn file with the VPN users, and they can load it into the OpenVPN client app.
Since only office subnet-realted IPs will be routed through the VPN, user can use, for example, another VPN which can work for the rest of the traffic - no problem at all.

Revoking someone access

Run easyrsa command, answering yes:

cd ~/easy-rsa
./easyrsa revoke testuser

Generate a new crl file:

./easyrsa gen-crl

A new crl.pem should appear. Now copy it to the openvpn server config dir:

sudo cp ~/easy-rsa/pki/crl.pem /etc/openvpn/server/

Update a /etc/openvpn/server/server.conf with:

crl-verify crl.pem

And restart vpn server daemon:

sudo systemctl restart openvpn-server@server.service