Update Fedora and install OpenVPN and Easy-RSA:
sudo dnf install -y epel-release
sudo dnf update -y
sudo dnf install -y openvpn easy-rsa
Install some useful system amdin tools:
sudo dnf install -y nano tmux htop
Restart the instance:
sudo reboot
sudo mkdir -p /etc/openvpn/easy-rsa
sudo cp -a /usr/share/easy-rsa/3/* /etc/openvpn/easy-rsa/
cd /etc/openvpn/easy-rsa
sudo ./easyrsa init-pki
sudo cp /etc/openvpn/easy-rsa/pki/vars.example /etc/openvpn/easy-rsa/pki/vars
sudo nano /etc/openvpn/easy-rsa/pki/vars
For simple configurations, add to vars
file:
set_var EASYRSA_REQ_CN "vpn1.example.com"
sudo ./easyrsa build-ca
Generate certificates for the server and clients:
sudo ./easyrsa build-server-full server nopass
sudo ./easyrsa build-client-full client1 nopass
sudo ./easyrsa gen-dh
sudo cp /usr/share/doc/openvpn/sample/sample-config-files/server.conf /etc/openvpn/server/server.conf
sudo nano /etc/openvpn/server/server.conf
openvpn --genkey tls-auth /etc/openvpn/ta.key
HMAC Firewall: It creates a cryptographic signature (Hash-based Message Authentication Code) for each packet.
ca /etc/openvpn/easy-rsa/pki/ca.crt
cert /etc/openvpn/easy-rsa/pki/issued/server.crt
key /etc/openvpn/easy-rsa/pki/private/server.key
dh /etc/openvpn/easy-rsa/pki/dh.pem
tls-auth /etc/openvpn/ta.key 0
notice these lines are not defined together in the server.conf
file.
push "redirect-gateway def1 bypass-dhcp"
push "dhcp-option DNS 8.8.8.8"
push "dhcp-option DNS 8.8.4.4"
this step is optional.
#cipher AES-256-CBC data-ciphers AES-256-GCM:AES-256-CBC data-ciphers-fallback AES-256-CBC
comment cipher AES-256-CBC
line.
The default location will be blocked by selinux because it's in the /etc/
directory.
For high traffic and production this log storage location probably needs to be more robust.
status /var/log/openvpn-status.log
server 10.8.0.0 255.255.255.0
sudo systemctl enable --now openvpn-server@server
sudo systemctl status openvpn-server@server
Install firewalld
service and check it's running:
sudo dnf install -y firewalld
sudo systemctl start firewalld
sudo firewall-cmd --state
Allow OpenVPN and Enable NAT:
sudo firewall-cmd --add-service=openvpn --permanent
sudo firewall-cmd --add-masquerade --permanent
Block traffic from the VPN subnet 10.8.0.0/24
to the server’s private subnets:
This is a common example on a typical VPS with two network interfaces.
View interfaces with ip addr
and block them,
10.18.0.8/16
and 10.133.0.7/16
interfaces in this case:
-
Block
10.18.0.0/16
(eth0 private network):sudo firewall-cmd --permanent --direct --add-rule ipv4 filter FORWARD 0 -s 10.8.0.0/24 -d 10.18.0.0/16 -j DROP
-
Block
10.133.0.0/16
(eth1 private network):sudo firewall-cmd --permanent --direct --add-rule ipv4 filter FORWARD 0 -s 10.8.0.0/24 -d 10.133.0.0/16 -j DROP
sudo firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 0 -s 10.8.0.0/24 -j DROP
sudo firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 1 -s 10.8.0.0/24 -p udp --dport 1194 -j ACCEPT
Prevent VPN clients from accessing the server’s IPs (except for VPN traffic):
sudo firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 0 -s 10.8.0.0/24 -j DROP
sudo firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 1 -s 10.8.0.0/24 -p udp --dport 1194 -j ACCEPT
sudo firewall-cmd --reload
echo 'net.ipv4.ip_forward = 1' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
In our VPS example we had the following interface:
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether a2:e6:a3:72:42:2f brd ff:ff:ff:ff:ff:ff
altname enp0s3
altname ens3
inet 134.209.194.59/20 brd 134.209.207.255 scope global noprefixroute eth0
valid_lft forever preferred_lft forever
inet 10.18.0.8/16 brd 10.18.255.255 scope global noprefixroute eth0
valid_lft forever preferred_lft forever
inet6 fe80::a0e6:a3ff:fe72:422f/64 scope link
valid_lft forever preferred_lft forever
We didn’t block 134.209.192.0/20
, and it’s not necessary since it’s the internet-facing range clients use for NAT.
If you want to block it as well, add to firewall rules:
sudo firewall-cmd --permanent --direct --add-rule ipv4 filter FORWARD 0 -s 10.8.0.0/24 -d 134.209.192.0/20 -j DROP
sudo firewall-cmd --reload
It might be good idea to disable selinux for some, but if it makes trouble:
sudo restorecon -Rv /etc/openvpn
sudo journalctl -u openvpn-server@server -f
tail -f /var/log/openvpn-status.log
this files shows who is currently connected, aad resets every X seconds, it does aggregate over time or contain traffic information or data.
sudo firewall-cmd --permanent --direct --get-all-rules
This section guides you through setting up an OpenVPN client config file and using on Windows with OpenVPN GUI.
Create OpenVPN config file client1.ovpn
for the first client:
sudo nano /etc/openvpn/client/client1.ovpn
Put this contents inside and replace [ ... ]
with the file contents.
client
dev tun
proto udp
remote vpn1.example.com 1194
resolv-retry infinite
nobind
persist-key
persist-tun
remote-cert-tls server
data-ciphers AES-256-GCM:AES-256-CBC
data-ciphers-fallback AES-256-CBC
verb 3
key-direction 1
<ca>
[contents of /etc/openvpn/easy-rsa/pki/ca.crt]
</ca>
<cert>
[contents of /etc/openvpn/easy-rsa/pki/issued/client1.crt]
</cert>
<key>
[contents of /etc/openvpn/easy-rsa/pki/private/client1.key]
</key>
<tls-auth>
[contents of /etc/openvpn/ta.key]
</tls-auth>
Download the config file (use Git Bash on Windows):
scp [email protected]:/etc/openvpn/client/client1.ovpn .
On Debian/Ubuntu-based systems:
sudo apt install openvpn
On RHEL/Rocky Linux/CentOS systems:
sudo dnf install openvpn
Copy VPN configuration:
sudo cp client1.ovpn /etc/openvpn/client/
Run inline client:
sudo openvpn --config /etc/openvpn/client/client1.ovpn
So setup as service instead copy the file as client.conf
:
sudo cp client1.ovpn /etc/openvpn/client/client.conf
Enable and start OpenVPN client service:
sudo systemctl enable openvpn-client@client
sudo systemctl start openvpn-client@client
Check OpenVPN client service status:
sudo systemctl status openvpn-client@client
Or, you can import the file into Network Manager GUI:
nmcli connection import type openvpn file client1.ovpn
- Download: Get the OpenVPN Community Client from OpenVPN Downloads.
- Run the installer with default settings. This includes the OpenVPN GUI and TAP adapter.
- Place
client1.ovpn
we downloaded insideC:\Program Files\OpenVPN\config
. - Start OpenVPN GUI program, it should auto-connect to the server.
The following template and script will help you creating unique configuration for each VPN client.
cd /etc/openvpn/client && touch client.ovpn.tpl && touch create.py
Put this config contents inside client.ovpn.tpl
, don't change the file paths:
client
dev tun
proto udp
remote vpn1.example.com 1194
resolv-retry infinite
nobind
persist-key
persist-tun
remote-cert-tls server
data-ciphers AES-256-GCM:AES-256-CBC
data-ciphers-fallback AES-256-CBC
verb 3
key-direction 1
<ca>
[/etc/openvpn/easy-rsa/pki/ca.crt]
</ca>
<cert>
[/etc/openvpn/easy-rsa/pki/issued/client.crt]
</cert>
<key>
[/etc/openvpn/easy-rsa/pki/private/client.key]
</key>
<tls-auth>
[/etc/openvpn/ta.key]
</tls-auth>
Put this script contents inside create.py
and give it execution permission chmod +x create.py
:
#!/usr/bin/python
import re
import os
import sys
import argparse
# Set up argument parser
parser = argparse.ArgumentParser(description="Generate an OpenVPN client configuration file from a template.")
parser.add_argument('output', help='Output .ovpn file name (required)')
parser.add_argument('--template', default='client.ovpn.tpl', help='Path to the template file (default: client.ovpn.tpl)')
parser.add_argument('--ca', help='Path to the CA certificate file (overrides template path)')
parser.add_argument('--cert', help='Path to the client certificate file (overrides template path)')
parser.add_argument('--key', help='Path to the client key file (overrides template path)')
parser.add_argument('--tls-auth', help='Path to the TLS auth key file (overrides template path)')
args = parser.parse_args()
# Read the template file
try:
with open(args.template, 'r') as f:
template_str = f.read()
except FileNotFoundError:
print(f"Error: Template file '{args.template}' not found.")
sys.exit(1)
# Define tags and their corresponding argument values
tags = {
'ca': args.ca,
'cert': args.cert,
'key': args.key,
'tls-auth': args.tls_auth
}
# Function to replace a placeholder with file contents
def replace_placeholder(match, tag, alt_path):
default_path = match.group(1) # Path inside [ ]
path = alt_path if alt_path else default_path
if not os.path.exists(path):
print(f"Error: File '{path}' does not exist.")
sys.exit(1)
with open(path, 'r') as f:
contents = f.read()
return f'<{tag}>\n{contents}\n</{tag}>'
# Process each tag in the template
for tag in tags:
pattern = r'<{tag}>\s*\[(.*?)\]\s*</{tag}>'.format(tag=tag)
template_str = re.sub(
pattern,
lambda m: replace_placeholder(m, tag, tags[tag]),
template_str,
flags=re.DOTALL
)
# Check if the output file exists and handle overwrite
if os.path.exists(args.output):
confirm = input(f"File '{args.output}' already exists. Overwrite? (y/n): ")
if confirm.lower() != 'y':
print("Aborting.")
sys.exit(0)
# Write the processed template to the output file
with open(args.output, 'w') as f:
f.write(template_str)
print(f"Generated '{args.output}' successfully.")
The script will ask you if you overwrite if client file already exists.
sudo ./easyrsa build-client-full client2 nopass
sudo ./create.py client2.ovpn \
--cert /etc/openvpn/easy-rsa/pki/issued/client2.crt \
--key /etc/openvpn/easy-rsa/pki/private/client2.key