1. Overview

OpenVPN‘s blend of strong security, high compatibility, configurability, and robust community support makes it the preferred solution for many VPN users looking to maintain privacy and security online. It’s GPL-licensed free software, suitable for both small-scale personal use and large-scale corporate networks. It can handle thousands of simultaneous connections.

In this tutorial, we’ll set up a Linux VPN server using OpenVPN and connect to it using a Linux client. Our choice is Ubuntu Server 22.04 on the server side and Linux Mint 21 on the client side.

Anyway, we’ll take advantage of a simplified installation script compatible with Ubuntu, Debian, AlmaLinux, Rocky Linux, CentOS, Fedora, and distributions derived from them.

2. VPN Server Setup

For most VPN setups, UDP is the preferred choice due to its efficiency and speed, especially on stable connections. The default port for OpenVPN over UDP is 1194.

However, TCP is a more stable and failsafe protocol for data transfer and may be better for unstable connections due to its error-correction capabilities. TCP port 443 is a common choice for bypassing blocks, at least for firewalls that control where packets come from and where they go, but not their content.

For these reasons, we’ll configure OpenVPN to listen on TCP port 443. Also, for privacy reasons, we’ll resolve DNS requests using Cisco OpenDNS IPs.

2.1. Basic VPS Setup

Our test VPS contains a fresh installation of Ubuntu Server 22.04, SSH access, and a root account only. First, let’s make sure all packages are up to date:

# apt update && apt upgrade
[...]
# reboot

The system upgrade included a kernel update, so we rebooted.

Our server provider equipped our VPS with UFW enabled and preconfigured. Let’s reset and disable it for now. We’ll reconfigure it later:

# ufw status
Status: active
[...]
# ufw reset
[...]
# ufw status
Status: inactive

We are now ready to install OpenVPN on this VPS.

2.2. OpenVPN Installation

Let’s download and run the OpenVPN installation script developed by Nyr and other contributors to make setting up our VPN as easy as possible:

# wget https://git.io/vpn -O openvpn-install.sh
[...]
# chmod +x openvpn-install.sh
# ./openvpn-install.sh

Welcome to this OpenVPN road warrior installer!

Which protocol should OpenVPN use?
   1) UDP (recommended)
   2) TCP
Protocol [1]: 2

What port should OpenVPN listen to?
Port [1194]: 443

Select a DNS server for the clients:
   1) Current system resolvers
   2) Google
   3) 1.1.1.1
   4) OpenDNS
   5) Quad9
   6) AdGuard
DNS server [1]: 4

Enter a name for the first client:
Name [client]: baeldung1

OpenVPN installation is ready to begin.
Press any key to continue...

[...]

Finished!

The client configuration is available in: /root/baeldung1.ovpn
New clients can be added by running this script again.

The steps we followed were self-explanatory and saved us a lot of complexity. Now, let’s focus on the .ovpn files.

2.3. Serve .ovpn Files

The .ovpn extension stands for OpenVPN Configuration File. The baeldung1.ovpn file generated by the installation script is specifically intended for use on a single client device. It contains the server address, port, protocol, encryption details, certificates, and other details.

We can download baeldung1.ovpn using scp from a client that has SSH access to the VPS. Alternatively, we can serve it with a temporary HTTP server using python3, which is included by default in all major Linux distributions:

# mkdir my_http_server
# cp baeldung1.ovpn my_http_server/
# cd my_http_server
# python3 -m http.server 8080

After that, on any device, we can download baeldung1.ovpn from http://[VPS_IP]:8080/baeldung1.ovpn. Of course, we need to replace [VPS_IP] with the actual IP address of the VPS. We could also use HTTPS and implement basic HTTP authentication to restrict downloads to authorized users, but for simplicity’s sake, we won’t.

Our HTTP server logs all downloads in real-time, which is very useful for monitoring access:

# python3 -m http.server 8080
Serving HTTP on 0.0.0.0 port 8080 (http://0.0.0.0:8080/) ...
1.48.25.229 - - [06/Jan/2024 17:02:28] "GET /baeldung1.ovpn HTTP/1.1" 200 -
[...]

Let’s not forget to stop the server by pressing CTRL+C once we’re done to prevent unauthorized access.

To add more clients, let’s just run the script again:

# ./openvpn-install.sh

OpenVPN is already installed.

Select an option:
   1) Add a new client
   2) Revoke an existing client
   3) Remove OpenVPN
   4) Exit

Option: 1

Provide a name for the client:
Name: baeldung2

[...]

baeldung2 added. Configuration available in: /root/baeldung2.ovpn

As a final note, commercial VPN services can provide the same .ovpn files to all of their customers. This is because they use .ovpn files with username/password-based authentication, which is suitable for their large user base and diverse server infrastructure. In contrast, a self-hosted OpenVPN setup like ours uses certificate-based authentication with unique .ovpn files for each client, providing more control and a security model tailored to smaller, more controlled environments.

2.4. Fine-Tuning the OpenVPN Configuration

The server.conf file contains the configuration of our OpenVPN server:

# cat /etc/openvpn/server/server.conf
[...]
port 443
proto tcp
[...]
verb 3
[...]

In our case, server.conf has 24 lines. Explaining them all would take us far and is not necessary anyway, as this configuration already sets up a robust and secure OpenVPN server. However, if we want to study each parameter, we can compare our server.conf with the sample OpenVPN 2.0 config file, where each line is explained.

For demonstration purposes, let’s use sed to change the server log verbosity from verb 3 to verb 4 and reload the new configuration:

# sed -i 's/verb 3/verb 4/' /etc/openvpn/server/server.conf
# systemctl restart openvpn-server@server

We can check the logs using journalctl:

# journalctl --identifier openvpn
[...]
Jan 06 14:36:51 openvpn openvpn[2747]: OpenVPN 2.5.9 [...]
[...]

This is just an example. Let’s keep in mind that increasing the verbosity level provides more detailed logs, which can be useful for troubleshooting, but can also generate a large amount of log data.

2.5. Firewall Configuration

To increase security, let’s enable UFW so that our VPS only listens on the ports we need according to our configuration, namely 22/TCP (SSH) and 443/TCP (OpenVPN). Optionally, we can leave port 8080/TCP (Python3 HTTP server) open and disable UFW logging to reduce system resource usage:

# ufw status
Status: inactive

# ufw default deny incoming
# ufw default allow outgoing
# ufw allow 22/tcp
# ufw allow 443/tcp
# ufw allow 8080/tcp
# ufw logging off
# ufw enable
[...]
Firewall is active and enabled on system startup

# ufw status verbose
Status: active
Logging: off
Default: deny (incoming), allow (outgoing), deny (routed)
New profiles: skip

To                         Action      From
--                         ------      ----
22/tcp                     ALLOW IN    Anywhere                  
443/tcp                    ALLOW IN    Anywhere                  
8080/tcp                   ALLOW IN    Anywhere                  
[...]

Optionally, we can further prevent unauthorized access to our VPS with various strategies:

Each layer of security we add reduces the likelihood of a successful attack but also increases the complexity of our setup. In most cases, however, the basic use of UFW we’ve just set up is sufficient.

3. VPN Client

While OpenVPN has its own dedicated client for various operating systems, the Network Manager package included in most mainstream Linux distributions provides a sufficient and efficient alternative to the official OpenVPN client.

3.1. VPN With Network Manager

Network Manager is designed to be versatile and is used in various desktop environments such as GNOME, KDE Plasma, XFCE and others. These environments often come with their own graphical front-ends for Network Manager, making it easy to connect to our OpenVPN server through a user-friendly interface. The appearance and specific features may vary between environments, but our baeldung1.ovpn is all we need.

In the following example, we’ll download the .ovpn file served as described above and set up the Network Manager to use it. After that, we’ll also use dnsleaktest.com to verify that our public IP matches that of our OpenVPN server, in this example 45.77.248.116, and that the client is using the DNS we set up earlier during the OpenVPN server installation, namely Cisco OpenDNS:

However, no client is required to use the DNS pushed by OpenVPN. We can change the DNS IPs from the Network Manager GUI or by manually editing the .ovpn file and adding the dhcp-option DNS parameter.

3.2. Autoreconnect on Unstable Connections

Unfortunately, we didn’t find any functionality in our test distribution to achieve automatic client reconnection to OpenVPN. Such functionality is crucial for unstable connections, especially mobile ones, and can greatly improve the stability of connections over a VPN.

We can achieve automatic reconnection by leaving the following script running in a terminal:

#!/bin/bash

# Configuration
vpnName="baeldung1"

# Last message variable
last_message=""

# Function to log messages with a timestamp, only if the message has changed
log_message() {
    if [[ "$1" != "$last_message" ]]; then
        echo "$(date '+%Y-%m-%d %H:%M:%S') - $1"
        last_message=$1
    fi
}

# Main loop
while true; do
    # Check VPN status
    if nmcli con show --active | grep -q "$vpnName"; then
        log_message "VPN $vpnName is connected."
    else
        log_message "VPN $vpnName disconnected, attempting to reconnect..."
        if nmcli con up id "$vpnName"; then
            log_message "Reconnected to VPN $vpnName successfully."
        else
            log_message "Failed to reconnect to VPN $vpnName."
        fi
    fi

    # Wait before checking again
    sleep 1
done

This script acts as a watchdog for our OpenVPN connection. Let’s take a look at a sample output:

$ ./autoreconnect.sh
[...]
2024-01-07 18:42:18 - VPN baeldung1 disconnected, attempting to reconnect...
Connection successfully activated (D-Bus active path: /org/freedesktop/NetworkManager/ActiveConnection/35)
2024-01-07 18:42:22 - Reconnected to VPN baeldung1 successfully.
2024-01-07 18:42:23 - VPN baeldung1 is connected.
[...]

Our Bash code uses nmcli, a command-line tool for NetworkManager, to check the VPN status and reconnect if necessary. The log_message function prevents redundancy by ensuring that only new status messages are logged.

4. Conclusion

In this article, we’ve seen how to set up a robust and secure OpenVPN Linux server and connect to it using a Linux client.

We’ve chosen to use TCP port 443 for its stability and ability to bypass certain types of network restrictions. The use of Nyr’s installation script greatly simplified the setup process, making it easy even for those with limited VPN technical knowledge. We emphasized the need for a separate .ovpn file for each client and how to use it with Network Manager.

As a final note, it’s important to understand that maintaining a VPN server comes with the responsibility of keeping it secure and up to date. Regular monitoring, updating, and applying best security practices are essential to protect against potential threats.

Comments are closed on this article!