Installation Process¶
The installOpenVPN() function is the core of the script. It performs the full server setup in 13 phases.
Phase 1: Package Installation¶
The script first installs the OpenVPN repository to get the latest stable release, then installs packages based on the distribution:
Also sets up the official OpenVPN apt repository with GPG key from swupdate.openvpn.net. Before adding it (installOpenVPNRepo()):
- Any
/etc/apt/sources.list.d/openvpn-aptrepo.listleft over from an interrupted or failed prior installation is removed first, so it can't blockapt-get update. - If the distribution codename (
VERSION_CODENAME) is unavailable, the script logs a warning and falls back to the distribution's ownopenvpnpackage. - Otherwise, the script checks whether the repository publishes packages for that codename (
curlagainst.../dists/<codename>/Release). A404falls back to distribution packages with a warning; any other non-200status is a fatal error.
This mainly affects Debian testing/unstable, which the official repository does not always support — see Compatibility.
After installation, the script verifies:
- ChaCha20-Poly1305 requires OpenVPN 2.5+
- Checks DCO availability (OpenVPN 2.6+ and kernel 6.16+)
Phase 2: User and Group¶
OpenVPN runs as an unprivileged user:
| Distribution | User | Group |
|---|---|---|
| Fedora, RHEL, CentOS, Rocky, Alma, Oracle, Amazon | openvpn | openvpn |
| Arch | openvpn | network |
| Debian, Ubuntu, openSUSE | nobody | nogroup |
The script also checks if the systemd service already handles privilege dropping to avoid a "double drop" error.
Phase 3: Easy-RSA and PKI¶
Downloads Easy-RSA 3.2.6 (if not already present) to a temporary file in /tmp/ (created via mktemp), verifies SHA256 checksum, and extracts to /etc/openvpn/server/easy-rsa/. Using a temporary file instead of $HOME ensures the download works in environments where HOME is unset (e.g., cloud-init user-data scripts).
Configures the vars file:
# ECDSA mode
set_var EASYRSA_ALGO ec
set_var EASYRSA_CURVE prime256v1
# RSA mode
set_var EASYRSA_KEY_SIZE 2048
PKI Mode¶
./easyrsa init-pki
./easyrsa --batch build-ca nopass # 10-year CA
./easyrsa --batch build-server-full <name> nopass # Server cert
./easyrsa gen-crl # Certificate Revocation List
Fingerprint Mode¶
./easyrsa --batch self-sign-server <name> nopass
# Extract and store server fingerprint
openssl x509 -in cert.crt -fingerprint -sha256 -noout
TLS Key Generation¶
| Mode | Command |
|---|---|
| tls-crypt-v2 | openvpn --genkey tls-crypt-v2-server tls-crypt-v2.key |
| tls-crypt | openvpn --genkey secret tls-crypt.key |
| tls-auth | openvpn --genkey secret tls-auth.key |
Phase 4: Server Configuration¶
Generates /etc/openvpn/server/server.conf with all the configured options.
Key sections of the generated config:
Network¶
port 1194
proto udp # or tcp, udp6, tcp6
dev tun
topology subnet
server 10.8.0.0 255.255.255.0 # Always present (even IPv6-only, for leak prevention)
server-ipv6 fd42:42:42:42::/112 # If IPv6 enabled
DNS Push¶
Depending on the selected provider, the script adds push "dhcp-option DNS ..." lines. For Unbound, it points to the VPN gateway IP. This whole section is skipped when internet routing is disabled (--no-route-internet).
Local Network Routes¶
Independent of internet routing, an explicit route is pushed for each --local-network CIDR:
See Network & DNS — Server-Side Network Access.
Gateway and Leak Prevention¶
Pushed only when internet routing is enabled (the default):
# IPv4 clients
push "redirect-gateway def1 bypass-dhcp"
# IPv6 clients
push "redirect-gateway ipv6"
push "route-ipv6 2000::/3"
# IPv4-only: block IPv6 to prevent leaks
push "block-ipv6"
In split-tunnel mode (--no-route-internet), none of these directives are pushed — the client's normal internet route is left alone.
Client-to-Client¶
--client-to-client adds a single client-to-client directive to server.conf, in addition to the matching firewall rules.
Encryption¶
dh none
tls-groups X25519:prime256v1:secp384r1:secp521r1
tls-crypt-v2 /etc/openvpn/server/easy-rsa/tls-crypt-v2.key
auth SHA256
cipher AES-128-GCM
data-ciphers AES-128-GCM
tls-server
tls-version-min 1.2
tls-cipher TLS-ECDHE-ECDSA-WITH-AES-128-GCM-SHA256
Management¶
The management socket enables real-time client monitoring and disconnection.
Phase 5: Sysctl¶
Creates /etc/sysctl.d/99-openvpn.conf. Forwarding for each address family is enabled only if that family's clients need it under the configured access policy — internet routing, a local network, or client-to-client access:
net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1 # If IPv6 enabled and needed by the access policy
Applied immediately with sysctl --system.
Phase 6: SELinux¶
If SELinux is enforcing and a non-standard port is used:
Phase 7: Systemd Service¶
- Finds the package-provided
openvpn-server@.service - Copies it to
/etc/systemd/system/(to avoid modifying package files) - Patches the service:
- Removes
LimitNPROC(causes issues on OpenVZ) - Fixes paths for openSUSE
- Ensures
RuntimeDirectoryis set
- Removes
- On Ubuntu 25.04+: configures AppArmor to allow the management socket
Phase 8: Firewall¶
See Firewall for details. The script auto-detects and configures firewalld, nftables, or iptables, and records the detected backend and access policy in /etc/openvpn/server/openvpn-install.conf (mode 600) so uninstallation can remove the exact rules added.
Phase 9: Client Template¶
Creates /etc/openvpn/server/client-template.txt — the base for all .ovpn files. Contains:
- Connection settings (remote, port, protocol)
- Encryption settings (cipher, auth, TLS)
- Platform-specific options (
block-outside-dnsfor Windows) - Placeholder markers for inline certificates and keys
Phase 10: Service Start and First Client¶
- Starts
openvpn-server@serverservice - In fingerprint mode: delays start until first client is added (server needs client fingerprint)
- Generates the first client
.ovpnfile by callingnewClient()