MHOSTMHOST

OpenVPN server

OpenVPN is a battle-tested protocol for running your own VPN server: it encrypts all traffic and routes it through your VPS, with client access controlled by individual certificates rather than a shared password. In this article you'll set up an OpenVPN server on an mHost VPS by hand — build a certificate authority with Easy-RSA from scratch, configure server.conf, assemble a client .ovpn file, and open the right port in the firewall.

What you'll need

  • An mHost VPS on Ubuntu 22.04/24.04, Debian 11/12, or AlmaLinux/Rocky Linux 8/9.
  • SSH access as root or a user with sudo rights. The commands below are written for root: if you're working as a sudo user, prefix each command with sudo — except for commands of the form cat > file <<EOF, where you should use sudo tee file <<EOF ... EOF instead (otherwise the redirect itself doesn't run as root and fails with a permission error).
  • Port 1194/udp open in the firewall — how to do that is covered in Step 5.
Tip: VMmanager 6 has a ready-made "Openvpn" recipe (see the article "Ready VMmanager 6 recipes") — it brings up an OpenVPN server in one action, without a single manual command. The recipe places the client key for connecting at /etc/openvpn/easy-rsa/keys — that's the path used by the classic Easy-RSA 2.x layout (a flat keys/ folder). This article uses the current Easy-RSA 3, which lays files out differently — under pki/issued/ and pki/private/ (see Step 2) — so the paths below differ from the recipe's. If you want more than a working VPN, and also want to understand exactly what got configured and why, read on.
Tip: there are also third-party install scripts — for example, angristan/openvpn-install (supports Ubuntu/Debian as well as AlmaLinux/Rocky) or its predecessor Nyr/openvpn-install. They interactively ask a few questions and do exactly what's described in the steps below — in one command. Handy when you need a result fast; here, the same actions are done by hand, with an explanation of what and why.

Step 1. Install OpenVPN and Easy-RSA

Ubuntu / Debian

bash
apt update
apt install -y openvpn easy-rsa

AlmaLinux / Rocky Linux

bash
dnf install -y epel-release
dnf install -y openvpn easy-rsa

The openvpn package isn't in AlmaLinux/Rocky's base repositories — it comes from the EPEL repository, which is why the first command enables it.

Check that OpenVPN installed:

bash
openvpn --version

Step 2. Build the PKI: certificate authority and server/client keys

Easy-RSA is a set of scripts for running your own certificate authority (CA): it's what issues the server certificate and the client certificates that trust each other. Set up a working copy of Easy-RSA in its own directory.

Ubuntu / Debian

bash
make-cadir /etc/openvpn/easy-rsa
cd /etc/openvpn/easy-rsa

AlmaLinux / Rocky Linux

bash
mkdir -p /etc/openvpn/easy-rsa
cp -r /usr/share/easy-rsa/3/* /etc/openvpn/easy-rsa/
cd /etc/openvpn/easy-rsa

From here on, the commands are the same on every system:

bash
./easyrsa init-pki
./easyrsa build-ca nopass
./easyrsa build-server-full server nopass
./easyrsa gen-dh
./easyrsa build-client-full client1 nopass
  • init-pki creates an empty PKI in the current folder — a pki/ directory with its own structure.
  • build-ca nopass creates the root certificate authority: the CA's private key (pki/private/ca.key) and its self-signed certificate (pki/ca.crt). It will ask for a Common Name — you can just press Enter and accept the default.
  • build-server-full server nopass generates a key and certificate for the server named server, signed by that same CA right away: pki/private/server.key and pki/issued/server.crt.
  • gen-dh generates Diffie-Hellman parameters (pki/dh.pem); on a low-power VPS this command can take a couple of minutes.
  • build-client-full client1 nopass does the same for the first client: pki/private/client1.key and pki/issued/client1.crt. Repeat this command with a new name (client2, phone, and so on) for every new device — give each client its own certificate, and don't reuse the same one across multiple devices.

build-ca only asks for a Common Name. build-server-full and build-client-full additionally show the request details and ask you to confirm them — type yes.

Tip: the nopass flag means "don't encrypt the private key with a passphrase." Without it, OpenVPN would ask for the key's password every time the service starts — inconvenient for a server that's supposed to come up automatically after a reboot.

Step 3. Configure server.conf

Create a dedicated system user for OpenVPN to run as after startup (it starts as root, but is then supposed to drop its privileges):

bash
id -u openvpn &>/dev/null || useradd --system --no-create-home --shell /usr/sbin/nologin openvpn

Copy the CA certificate, the server certificate and key, and the Diffie-Hellman parameters next to the server.conf you're about to create:

bash
mkdir -p /etc/openvpn/server
cp /etc/openvpn/easy-rsa/pki/ca.crt             /etc/openvpn/server/ca.crt
cp /etc/openvpn/easy-rsa/pki/issued/server.crt  /etc/openvpn/server/server.crt
cp /etc/openvpn/easy-rsa/pki/private/server.key /etc/openvpn/server/server.key
cp /etc/openvpn/easy-rsa/pki/dh.pem             /etc/openvpn/server/dh.pem
chmod 600 /etc/openvpn/server/server.key

Create the config itself:

bash
cat > /etc/openvpn/server/server.conf <<'EOF'
port 1194
proto udp
dev tun

ca ca.crt
cert server.crt
key server.key
dh dh.pem

topology subnet
server 10.8.0.0 255.255.255.0
ifconfig-pool-persist ipp.txt

push "redirect-gateway def1 bypass-dhcp"
push "dhcp-option DNS 8.8.8.8"
push "dhcp-option DNS 8.8.4.4"

keepalive 10 120

user openvpn
group openvpn

persist-tun

status openvpn-status.log
verb 3
explicit-exit-notify 1
EOF

A quick rundown of the key directives:

  • port 1194 / proto udp — OpenVPN's standard port and protocol (you'll need this again in Step 5 when opening the firewall).
  • ca / cert / key / dh — paths to the PKI files, given without a leading slash because systemd starts OpenVPN with its working directory set to /etc/openvpn/server (see Step 6), where these files live.
  • server 10.8.0.0 255.255.255.0 — a dedicated subnet for VPN clients; the server itself takes address 10.8.0.1 in it.
  • push "redirect-gateway def1 bypass-dhcp" — tells the client to route all of its traffic through the tunnel, not just requests to the server itself.
  • push "dhcp-option DNS ..." — which DNS servers the client should use while the VPN is active.
  • user openvpn / group openvpn — after initializing, OpenVPN drops its root privileges to the user created above.
  • explicit-exit-notify 1 — only works with proto udp: the client learns faster that the server restarted and reconnects sooner.

Step 4. Enable IP forwarding

By default the Linux kernel doesn't forward packets between network interfaces — and without that, the server can't pass VPN clients' traffic on to the internet. Turn forwarding on persistently, with a separate file under /etc/sysctl.d:

bash
cat > /etc/sysctl.d/99-openvpn.conf <<'EOF'
net.ipv4.ip_forward = 1
EOF

sysctl --system

Check that the setting took effect:

bash
sysctl net.ipv4.ip_forward

Expected output is net.ipv4.ip_forward = 1. A setting under /etc/sysctl.d/ survives a server reboot — there's nothing extra to configure for it to persist.

Step 5. Open UDP 1194 in the firewall and enable NAT

Important: without this step, clients will either fail to connect at all (the port is closed) or connect but get no internet access through the tunnel (NAT isn't set up). If ufw isn't installed and enabled on the server yet, start with the article "The ufw firewall: managing ports" — it installs ufw, allows SSH (which must be done before enabling the firewall), and turns it on. Below are only the rules specific to OpenVPN.

Ubuntu / Debian (ufw)

Allow the VPN port itself:

bash
ufw allow 1194/udp

Find the name of the server's main network interface — you'll need it for the NAT rule:

bash
ip route show default

In the output (something like default via 45.156.21.1 dev eth0 proto static), look at the dev field — that's the interface name. The examples below use eth0; substitute your own if it differs.

Open /etc/ufw/before.rules:

bash
nano /etc/ufw/before.rules

and add these four lines at the very top of the file — before the *filter line:

*nat
:POSTROUTING ACCEPT [0:0]
-A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE
COMMIT

Allow packet forwarding at the ufw level too:

bash
sed -i 's/DEFAULT_FORWARD_POLICY="DROP"/DEFAULT_FORWARD_POLICY="ACCEPT"/' /etc/default/ufw

Apply the changes:

bash
ufw reload

AlmaLinux / Rocky Linux (firewalld)

These distributions use firewalld by default instead of ufw:

bash
firewall-cmd --permanent --add-port=1194/udp
firewall-cmd --permanent --add-masquerade
firewall-cmd --reload
Tip: if you changed the server 10.8.0.0 255.255.255.0 subnet in server.conf back in Step 3, use your own subnet in the NAT rule too — they need to match.

Step 6. Start OpenVPN

The configuration lives at /etc/openvpn/server/server.conf — the openvpn-server@ systemd unit takes its instance name from the file name without the extension (server), so it's started like this:

bash
systemctl enable --now openvpn-server@server
systemctl status openvpn-server@server

The status should show active (running). If something's wrong, check the journal:

bash
journalctl -u openvpn-server@server -n 50 --no-pager

Confirm the virtual interface came up:

bash
ip addr show tun0

You should see a tun0 interface with address 10.8.0.1.

Step 7. Assemble the .ovpn client profile and connect

OpenVPN can bundle the CA, certificate, and key right inside a single .ovpn file — using <ca>, <cert>, and <key> blocks. That keeps the profile a single file that's easy to hand to a device.

bash
cd /etc/openvpn/easy-rsa

{
cat <<'EOF'
client
dev tun
proto udp
remote YOUR_SERVER_IP 1194
resolv-retry infinite
nobind
persist-tun
remote-cert-tls server
verb 3
<ca>
EOF
cat pki/ca.crt
echo '</ca>'
echo '<cert>'
cat pki/issued/client1.crt
echo '</cert>'
echo '<key>'
cat pki/private/client1.key
echo '</key>'
} > /root/client1.ovpn

Replace YOUR_SERVER_IP with your VPS's public IP address (or domain).

Tip: the file pki/issued/client1.crt starts with a human-readable dump of the certificate (Certificate: Data: ...), and only then comes the actual -----BEGIN CERTIFICATE----- block. That's normal: when reading an inline block, OpenVPN looks specifically for the BEGIN/END markers and just ignores any text before them.

Copy the file to the client device, for example with scp:

bash
scp root@YOUR_SERVER_IP:/root/client1.ovpn .

From here it depends on the device:

  • Windows / macOS / iOS / Android — install the official OpenVPN Connect app and import the client1.ovpn file into it.
  • Linux — import the profile into NetworkManager (nmcli connection import type openvpn file client1.ovpn) or run it directly: openvpn --config client1.ovpn.

Check on the server that the client connected:

bash
cat /etc/openvpn/server/openvpn-status.log
Done: openvpn-status.log now shows a line with the client's name (client1) and its virtual IP from the 10.8.0.0/24 subnet, and on the client device all traffic is going through the VPN.

What's next

Need more clients — repeat build-client-full with a new name (Step 2) and assemble a new .ovpn for it (Step 7); give every device its own certificate, and don't reuse the same one across multiple devices. A device is lost or it's time to cut it off — revoke its certificate (./easyrsa revoke-issued client1, then ./easyrsa gen-crl) and reference the resulting pki/crl.pem in server.conf with the crl-verify crl.pem directive.

The ready-made "Openvpn" recipe in VMmanager 6, or third-party scripts like angristan/openvpn-install, get you the same result in one action (see the start of this article). Want a protocol that's simpler to configure — see the article "WireGuard VPN" (also available as a ready recipe in VMmanager 6). And to keep track of whatever else is open on the server — see "The ufw firewall: managing ports".