Skip to main contentSkip to navigation
[email protected]
Client AreaSupport
Hosting Mammoth
HostingMammothYour Data, Our Responsibility
Home
Solutions
Hosting Services
Store
Pricing
About
Blog
API
Contact

Stay Ahead of the Curve

Get the latest insights on cybersecurity, AI innovations, and enterprise data solutions delivered to your inbox.

Hosting Mammoth
HostingMammothEnterprise Solutions

Enterprise-grade data solutions. Hosting, recovery, cybersecurity, and AI-powered services for businesses worldwide.

[email protected]
Sun - Fri, 9:00am - 5:00pm

Services

  • Cloud Hosting
  • Data Recovery
  • Cybersecurity
  • Legal Support
  • MSP Services
  • Web Development
  • AI Services
  • Free Server Migration

Hosting

  • VPS Hosting (NVMe SSD)
  • VDS Hosting (NVMe)
  • Storage VPS (High SSD)
  • GPU Servers
  • Managed Services
  • Cloud Firewall
  • Load Balancer
  • One-Click Apps
  • n8n Hosting
  • Object Storage
  • FAQ

Company

  • Store
  • Pricing
  • About Us
  • Locations
  • Blog
  • Testimonials
  • Contact
  • Affiliate Program
  • White-Label
  • Terms of Service
  • Privacy Policy
  • Browser Cookies
  • SLA

Support

  • Client Area
  • Submit Ticket
  • Knowledge Base
  • Server Status
  • API Documentation

© 2026 Hosting Mammoth. All rights reserved.

Knowledge Base
Getting StartedAccount ManagementVPS HostingGPU ServersStorage VPSCloud FirewallLoad BalancerServer ManagementBilling & PaymentsSupport & TicketsAffiliate ProgramReseller ProgramMarketplace & Appsn8n HostingManaged ServicesServer MigrationAPI & DevelopersSecurityTroubleshootingGlossaryInstall Guides
  1. Home
  2. /
  3. Support
  4. /
  5. Install Guides
  6. /
  7. How To Install Openvpn Ubuntu
GUIDEInstall Guides

How to Install OpenVPN Server on Ubuntu 24.04 VPS: Easy-RSA PKI, Client Profiles, Hardening

22 min read

How to Install OpenVPN Server on Ubuntu 24.04 VPS: Easy-RSA PKI, Client Profiles, Hardening

A self-hosted OpenVPN server gives you a private, encrypted gateway to the internet or to a remote office — without trusting a commercial VPN provider with your traffic logs, bandwidth, or jurisdiction. This tutorial walks you through building a production-grade OpenVPN server on Ubuntu 24.04 LTS from scratch: a complete Easy-RSA 3 PKI, TLS-crypt wrapping the control channel, AES-256-GCM on the data channel, nftables NAT, a reusable client.ovpn template, split-tunnel support, CRL-based revocation, and a hardened systemd unit.

You will see both the quick path (the community-maintained angristan/openvpn-install script, which gets you to a working VPN in five minutes) and the manual path, which is what you want for any deployment you intend to operate long-term.

Recommended plan: the CloudCore Starter VPS is more than enough for a personal or small-team OpenVPN server — 2 vCPU, 8 GB RAM, AES-NI support, and unmetered bandwidth for well under the cost of a commercial VPN subscription.

Table of Contents

  • Why Self-Host OpenVPN Instead of Using a Commercial VPN?
  • Prerequisites
  • Step 1: Update the System
  • Quick Path: angristan/openvpn-install
  • Step 2: Install OpenVPN and Easy-RSA
  • Step 3: Initialize the Easy-RSA PKI
  • Step 4: Build the CA, Server Cert, and DH Parameters
  • Step 5: Generate the TLS-Crypt Key
  • Step 6: Write /etc/openvpn/server.conf
  • Step 7: Enable IP Forwarding and nftables NAT
  • Step 8: Start the systemd Service
  • Step 9: Generate Client Certificates and .ovpn Profiles
  • Step 10: Split-Tunnel vs Full-Tunnel
  • Step 11: Revoke a Client (CRL)
  • Step 12: Monitoring and Logging
  • Hardening Checklist
  • Troubleshooting
  • FAQ
  • Next Steps
  • Why Self-Host OpenVPN Instead of Using a Commercial VPN?

    Commercial VPN services (NordVPN, ExpressVPN, Surfshark, Mullvad and the rest) are convenient, but they make four unavoidable compromises you do not have to accept when you run your own server:

    • Trust. Every commercial VPN operator can see your unencrypted metadata (IPs, destinations, timestamps) at their exit node. "No-logs" is a policy, not a technical guarantee. On your own VPS, the only operator is you.
    • Shared exit IPs. VPN provider IPs are aggressively rate-limited or outright blocked by banks, Cloudflare, Google, streaming services, and Stripe. A dedicated VPS IP is clean and not blacklisted.
    • Bandwidth contention. Consumer VPN servers are heavily oversubscribed. A CloudCore Starter gives you unmetered bandwidth that no one else shares.
    • Jurisdiction and cost. A commercial VPN at USD 10/month for a family pack costs more per year than a CloudCore Starter VPS that can host dozens of users, a WireGuard tunnel, a strongSwan IPsec gateway, and an OpenConnect/AnyConnect server at the same time.
    Self-hosted OpenVPN is also the right choice when you need:

    • Site-to-site connectivity between a home lab and a cloud VPC without paying for managed VPN gateways.
    • A stable, whitelisted egress IP for scraping, third-party APIs that require IP allow-lists, or remote database access.
    • Certificate-based access control with per-user revocation — something no consumer VPN exposes.
    • Compatibility with restrictive networks where TCP/443 masquerading is required; OpenVPN handles this natively, WireGuard does not.

    Prerequisites

    • Ubuntu 24.04 LTS VPS with root or sudo access.
    • A public IPv4 address — note it down, you will put it in remote on every client profile.
    • UDP port 1194 open at any upstream firewall (most VPS providers leave all ports open by default).
    • SSH access via key auth (recommended) and 5 minutes to generate a Certificate Authority that will live for the next 10 years.
    • A CloudCore Starter or larger plan. The CloudCore Starter at 2 vCPU / 8 GB RAM handles 50+ concurrent OpenVPN clients comfortably thanks to AES-NI hardware acceleration.
    Connect to your server:

    bash
    ssh root@your-server-ip

    If you are logged in as a non-root user, prefix every apt, mkdir, cp, systemctl and nft command below with sudo.

    Step 1: Update the System

    bash
    sudo apt update && sudo apt upgrade -y

    If the kernel was updated, reboot before continuing — OpenVPN uses the tun kernel module and you want it matched to the running kernel:

    bash
    sudo reboot

    Reconnect after a minute.

    Quick Path: angristan/openvpn-install

    Before you dive into the manual install, here is the shortest path that actually works for production. The community script at github.com/angristan/openvpn-install has been maintained for a decade, has thousands of GitHub stars, and configures all the same hardening (tls-crypt, AES-256-GCM, compress disabled, SHA-256 digests) that we describe manually below.

    Run it:

    bash
    curl -O https://raw.githubusercontent.com/angristan/openvpn-install/master/openvpn-install.sh
    chmod +x openvpn-install.sh
    sudo ./openvpn-install.sh

    The script asks interactive questions:

    text
    IP address: <your public IPv4>
    Port: 1194
    Protocol: UDP
    DNS: 9 (Cloudflare 1.1.1.1)
    Compression: N
    Customize encryption: N (accept defaults: AES-256-GCM, tls-crypt, secp521r1)
    Client name: laptop
    Password-protect client? 1 (no)

    When it finishes you get a ~/laptop.ovpn file ready to import into any OpenVPN client. To add more clients later, re-run the same script and choose Add a new user. To revoke, re-run and choose Revoke existing user.

    This is a completely acceptable endpoint for a home or small-team VPN. If that is all you need, copy the .ovpn, move on to Step 12: Monitoring, and you are done. The rest of this guide builds the same thing by hand so you understand exactly what is on disk and can customize every layer.

    Step 2: Install OpenVPN and Easy-RSA

    Starting from a clean Ubuntu 24.04 (remove the angristan install first with ./openvpn-install.sh → Remove, if you ran it):

    bash
    sudo apt install -y openvpn easy-rsa

    Verify the versions — Ubuntu 24.04 ships OpenVPN 2.6.x and Easy-RSA 3.1.x:

    bash
    openvpn --version | head -n 1
    

    OpenVPN 2.6.x x86_64-pc-linux-gnu ...

    /usr/share/easy-rsa/easyrsa --version

    EasyRSA Version Information Version: 3.1.x

    Create a dedicated Easy-RSA working directory outside of /etc/openvpn (the CA private key should never live next to running services):

    bash
    sudo mkdir -p /etc/openvpn/easy-rsa
    sudo ln -s /usr/share/easy-rsa/* /etc/openvpn/easy-rsa/
    cd /etc/openvpn/easy-rsa

    Step 3: Initialize the Easy-RSA PKI

    bash
    sudo ./easyrsa init-pki

    Expected output:

    text
    Notice
    ------
    'init-pki' complete; you may now create a CA or requests.
    Your newly created PKI dir is: /etc/openvpn/easy-rsa/pki

    Create a vars file to set defaults for certificate fields (otherwise you will be prompted for each):

    bash
    sudo tee /etc/openvpn/easy-rsa/vars > /dev/null <<'EOF'
    set_var EASYRSA_ALGO           ec
    set_var EASYRSA_CURVE          secp384r1
    set_var EASYRSA_DIGEST         sha256
    set_var EASYRSA_CA_EXPIRE      3650
    set_var EASYRSA_CERT_EXPIRE    825
    set_var EASYRSA_CRL_DAYS       180
    set_var EASYRSA_REQ_COUNTRY    "US"
    set_var EASYRSA_REQ_PROVINCE   "N/A"
    set_var EASYRSA_REQ_CITY       "N/A"
    set_var EASYRSA_REQ_ORG        "VPS-Server.host"
    set_var EASYRSA_REQ_EMAIL      "[email protected]"
    set_var EASYRSA_REQ_OU         "OpenVPN"
    EOF

    Key choices explained:

    • EASYRSA_ALGO=ec and EASYRSA_CURVE=secp384r1. Elliptic-curve keys are faster to generate, verify, and transmit than 2048-bit RSA while providing stronger security. secp384r1 is the industry sweet spot.
    • EASYRSA_CERT_EXPIRE=825. The current browser/CA baseline is 825 days. Anything longer risks validation surprises if you later pivot to a TLS-terminating proxy.
    • EASYRSA_CRL_DAYS=180. The CRL must be re-signed and re-published at least this often, so we will automate it later.

    Step 4: Build the CA, Server Cert, and DH Parameters

    Create the Certificate Authority (no password — nopass — because the CA lives on the same host; for high-security setups, move the CA offline):

    bash
    sudo ./easyrsa build-ca nopass

    Expected output:

    text
    Notice
    ------
    CA creation complete. Your new CA certificate is at:
    
    • pki/ca.crt
    Generate the server certificate and key (also unencrypted so OpenVPN can start unattended):

    bash
    sudo ./easyrsa build-server-full server nopass

    Generate Diffie-Hellman parameters. With EC keys, OpenVPN 2.6 uses ECDH automatically and classic DH is no longer strictly required, but we generate a file anyway for compatibility with older client configs:

    bash
    sudo ./easyrsa gen-dh

    This takes 1–3 minutes on a 2 vCPU VPS and produces pki/dh.pem.

    Copy the server-side artifacts into OpenVPN's runtime directory:

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

    Step 5: Generate the TLS-Crypt Key

    tls-crypt wraps the entire TLS control channel in an additional symmetric cipher. This both authenticates and encrypts control packets — so port scanners see only random noise, and DoS floods against the TLS handshake are dropped before they hit OpenSSL.

    bash
    sudo openvpn --genkey secret /etc/openvpn/server/tc.key
    sudo chmod 600 /etc/openvpn/server/tc.key

    You can inspect it — it is a 256-bit pre-shared key in the OpenVPN static-key format.

    Step 6: Write /etc/openvpn/server.conf

    This is the heart of the install. Every directive below is there for a reason and the combination reflects current OpenVPN 2.6 best practice.

    bash
    sudo tee /etc/openvpn/server/server.conf > /dev/null <<'EOF'
    

    --- Listener ---

    port 1194 proto udp dev tun topology subnet

    --- PKI ---

    ca /etc/openvpn/server/ca.crt cert /etc/openvpn/server/server.crt key /etc/openvpn/server/server.key dh /etc/openvpn/server/dh.pem tls-crypt /etc/openvpn/server/tc.key

    --- Client addressing ---

    server 10.8.0.0 255.255.255.0 ifconfig-pool-persist /var/log/openvpn/ipp.txt client-config-dir /etc/openvpn/ccd

    --- Push to clients ---

    push "redirect-gateway def1 bypass-dhcp" push "dhcp-option DNS 1.1.1.1" push "dhcp-option DNS 9.9.9.9" push "block-outside-dns"

    --- Data-channel crypto ---

    data-ciphers AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305 data-ciphers-fallback AES-256-GCM auth SHA256 tls-version-min 1.2 tls-cipher TLS-ECDHE-ECDSA-WITH-AES-256-GCM-SHA384:TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384

    --- Compression (off is deliberate; VORACLE attack) ---

    compress stub-v2

    --- Keepalive and tuning ---

    keepalive 10 120 persist-key persist-tun explicit-exit-notify 1

    --- Drop privileges ---

    user nobody group nogroup

    --- Logging ---

    status /var/log/openvpn/openvpn-status.log 30 log-append /var/log/openvpn/openvpn.log verb 3 mute 20

    --- Revocation ---

    crl-verify /etc/openvpn/server/crl.pem EOF

    Key decisions:

    • proto udp on port 1194. UDP is the default and correct choice: lower overhead and no TCP-over-TCP meltdown when both tunneled and underlying connections retransmit.
    • topology subnet. Modern addressing; every client gets one IP inside 10.8.0.0/24 instead of the legacy /30 pairs.
    • data-ciphers AES-256-GCM:...CHACHA20-POLY1305. AES-GCM is hardware-accelerated on any VPS with AES-NI. ChaCha20 is advertised as a fallback for older mobile clients without AES-NI.
    • compress stub-v2. Compression is disabled due to the VORACLE attack. stub-v2 tells the client explicitly "no framing, no compression" and is required for interop with OpenVPN 2.4 clients.
    • tls-crypt (not tls-auth). Newer, encrypts as well as authenticates; protects against active probing.
    • user nobody / group nogroup. The daemon drops to an unprivileged user immediately after binding UDP/1194.
    Create the required runtime directories:

    bash
    sudo mkdir -p /etc/openvpn/ccd /var/log/openvpn
    sudo touch /etc/openvpn/server/crl.pem
    sudo chown -R nobody:nogroup /var/log/openvpn

    The empty crl.pem placeholder prevents OpenVPN from failing to start before you have revoked anyone. We will populate it in Step 11.

    Step 7: Enable IP Forwarding and nftables NAT

    OpenVPN delivers packets into the tun0 interface, but without IP forwarding the kernel will not route them out to the public interface, and without NAT the destination servers will have no idea how to return replies.

    Enable IPv4 forwarding permanently:

    bash
    echo 'net.ipv4.ip_forward=1' | sudo tee /etc/sysctl.d/99-openvpn.conf
    sudo sysctl --system | grep ip_forward
    

    net.ipv4.ip_forward = 1

    Detect your public interface name — usually eth0, ens3, or enp1s0:

    bash
    ip -o -4 route show to default | awk '{print $5}'
    

    ens3

    Configure nftables with a masquerade rule for the 10.8.0.0/24 subnet. Ubuntu 24.04 ships nftables by default (iptables is available in iptables-nft compat mode):

    bash
    sudo tee /etc/nftables.conf > /dev/null <<'EOF'
    #!/usr/sbin/nft -f
    flush ruleset

    table inet filter { chain input { type filter hook input priority 0; policy drop; ct state established,related accept iifname "lo" accept iifname "tun0" accept tcp dport 22 accept udp dport 1194 accept ip protocol icmp accept } chain forward { type filter hook forward priority 0; policy drop; iifname "tun0" oifname "ens3" accept iifname "ens3" oifname "tun0" ct state established,related accept } chain output { type filter hook output priority 0; policy accept; } }

    table ip nat { chain postrouting { type nat hook postrouting priority 100; ip saddr 10.8.0.0/24 oifname "ens3" masquerade } } EOF

    Replace ens3 with your actual public interface name in both places. Apply and persist:

    bash
    sudo nft -f /etc/nftables.conf
    sudo systemctl enable --now nftables
    sudo nft list ruleset | head -n 30

    Step 8: Start the systemd Service

    OpenVPN ships a templated systemd unit — openvpn-server@<config-name>.service — that reads /etc/openvpn/server/<config-name>.conf. Because we named our file server.conf:

    bash
    sudo systemctl enable --now [email protected]
    sudo systemctl status [email protected]

    Expected output:

    text
    ● [email protected] - OpenVPN service for server
         Loaded: loaded (/lib/systemd/system/[email protected]; enabled)
         Active: active (running) since Thu 2026-04-16 10:05:22 UTC
       Main PID: 4123 (openvpn)
         Status: "Initialization Sequence Completed"
          Tasks: 1 (limit: 9500)
         Memory: 3.8M

    Confirm the tun0 interface appeared:

    bash
    ip -4 addr show tun0
    

    inet 10.8.0.1/24 ...

    And that UDP/1194 is listening:

    bash
    sudo ss -ulnp | grep 1194
    

    UNCONN 0 0 0.0.0.0:1194 0.0.0.0:* users:(("openvpn",pid=4123,fd=6))

    Step 9: Generate Client Certificates and .ovpn Profiles

    Every client gets its own certificate. Do not share a single client cert across devices — it defeats revocation.

    Back in /etc/openvpn/easy-rsa:

    bash
    cd /etc/openvpn/easy-rsa
    sudo ./easyrsa build-client-full laptop nopass

    This produces pki/issued/laptop.crt and pki/private/laptop.key.

    Now assemble a single-file .ovpn profile. A helper script makes this repeatable:

    bash
    sudo tee /usr/local/sbin/make-ovpn > /dev/null <<'EOF'
    #!/bin/bash
    

    Usage: make-ovpn <client-name> <server-public-ip-or-hostname>

    set -euo pipefail CLIENT="$1" SERVER="$2" EASY_RSA=/etc/openvpn/easy-rsa OUT="/root/${CLIENT}.ovpn"

    cat > "$OUT" <<CFG client dev tun proto udp remote ${SERVER} 1194 resolv-retry infinite nobind persist-key persist-tun remote-cert-tls server data-ciphers AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305 data-ciphers-fallback AES-256-GCM auth SHA256 verb 3 compress stub-v2 CFG

    echo '<ca>' >> "$OUT" cat "${EASY_RSA}/pki/ca.crt" >> "$OUT" echo '</ca>' >> "$OUT"

    echo '<cert>' >> "$OUT" sed -ne '/BEGIN CERTIFICATE/,/END CERTIFICATE/p' \ "${EASY_RSA}/pki/issued/${CLIENT}.crt" >> "$OUT" echo '</cert>' >> "$OUT"

    echo '<key>' >> "$OUT" cat "${EASY_RSA}/pki/private/${CLIENT}.key" >> "$OUT" echo '</key>' >> "$OUT"

    echo '<tls-crypt>' >> "$OUT" sed -ne '/BEGIN OpenVPN Static key/,/END OpenVPN Static key/p' \ /etc/openvpn/server/tc.key >> "$OUT" echo '</tls-crypt>' >> "$OUT"

    chmod 600 "$OUT" echo "Wrote $OUT" EOF sudo chmod +x /usr/local/sbin/make-ovpn

    Run it:

    bash
    sudo make-ovpn laptop YOUR.PUBLIC.IP
    

    Wrote /root/laptop.ovpn

    Transfer laptop.ovpn to the client securely (never email it — the private key is embedded):

    bash
    scp [email protected]:/root/laptop.ovpn ./

    Import it into:

    • Linux: sudo openvpn --config laptop.ovpn
    • macOS: Tunnelblick or OpenVPN Connect
    • Windows: OpenVPN GUI or OpenVPN Connect
    • iOS / Android: OpenVPN Connect (store apps)
    The connection should establish in under 2 seconds and you will see the client appear in /var/log/openvpn/openvpn-status.log.

    Step 10: Split-Tunnel vs Full-Tunnel

    The server config above pushes redirect-gateway def1 — every byte the client sends goes through the VPN (full-tunnel). That is what you want for privacy and geo-egress.

    For split-tunnel (VPN only reaches specific subnets, normal traffic stays local), remove that push and push explicit routes instead. Override it per-client with a CCD file rather than editing the server config:

    bash
    sudo tee /etc/openvpn/ccd/laptop > /dev/null <<'EOF'
    push-reset
    push "route 10.8.0.0 255.255.255.0"
    push "route 192.168.50.0 255.255.255.0"
    push "dhcp-option DNS 1.1.1.1"
    EOF

    push-reset cancels any inherited pushes; then you list only the subnets that should traverse the tunnel. Reload OpenVPN:

    bash
    sudo systemctl reload [email protected]

    The next time laptop reconnects, only 10.8.0.0/24 and 192.168.50.0/24 go through the VPN; everything else uses the client's normal ISP.

    Step 11: Revoke a Client (CRL)

    Say the laptop was stolen. Revoke it immediately:

    bash
    cd /etc/openvpn/easy-rsa
    sudo ./easyrsa revoke laptop
    

    type 'yes' when prompted

    sudo ./easyrsa gen-crl sudo cp pki/crl.pem /etc/openvpn/server/crl.pem sudo chmod 644 /etc/openvpn/server/crl.pem sudo systemctl reload [email protected]

    Because server.conf contains crl-verify /etc/openvpn/server/crl.pem, OpenVPN consults the CRL on every new connection and refuses any cert listed there — the laptop can no longer reconnect even if the attacker has the .ovpn file.

    Because CRLs expire (we set EASYRSA_CRL_DAYS=180), automate a monthly regeneration with cron:

    bash
    sudo tee /etc/cron.d/openvpn-crl > /dev/null <<'EOF'
    

    Regenerate the OpenVPN CRL on the 1st of every month

    0 3 1 root cd /etc/openvpn/easy-rsa && ./easyrsa gen-crl && cp pki/crl.pem /etc/openvpn/server/crl.pem && systemctl reload [email protected] EOF

    Step 12: Monitoring and Logging

    OpenVPN writes two files we care about:

    • /var/log/openvpn/openvpn.log — full server log (connections, renegotiations, errors).
    • /var/log/openvpn/openvpn-status.log — refreshed every 30 seconds with the current client table.
    Live tail:

    bash
    sudo tail -f /var/log/openvpn/openvpn.log

    Current connected clients:

    bash
    sudo cat /var/log/openvpn/openvpn-status.log

    Example output:

    text
    OpenVPN CLIENT LIST
    Updated,2026-04-16 10:30:05
    Common Name,Real Address,Bytes Received,Bytes Sent,Connected Since
    laptop,203.0.113.15:54231,18432,91827,2026-04-16 10:12:44
    ROUTING TABLE
    Virtual Address,Common Name,Real Address,Last Ref
    10.8.0.2,laptop,203.0.113.15:54231,2026-04-16 10:30:02

    For production, feed these into Prometheus via openvpn_exporter and graph in Grafana — or into Loki for log search. Log rotation is handled by the Ubuntu openvpn package's default /etc/logrotate.d/openvpn rule.

    Enable the service health check in your uptime monitor (Uptime Kuma, Zabbix, or similar) by polling UDP/1194 — nmap -sU -p1194 your.server.ip should return open|filtered.

    Hardening Checklist

    Once it is running, walk through these:

    • ufw or nftables drop on everything except 22/TCP and 1194/UDP. Already done in Step 7.
    • SSH with key-only auth, PermitRootLogin prohibit-password, fail2ban on sshd.
    • Drop root privileges in OpenVPN — already set via user nobody / group nogroup.
    • Separate CA host. For anything beyond a personal VPN, take the Easy-RSA PKI off the VPN server entirely: generate certs on a laptop, transfer only ca.crt, server.crt, server.key, and crl.pem back.
    • tls-crypt-v2. If you expect probing from state-level adversaries, upgrade from tls-crypt to tls-crypt-v2, which gives each client a unique wrap key.
    • Disable the legacy compress directive entirely if all your clients are OpenVPN 2.5+. (compress stub-v2 is only for 2.4 compat.)
    • Review DHCP options you push — avoid leaking internal DNS suffixes.
    • systemd unit hardening. Drop a unit override:
    bash
    sudo systemctl edit [email protected]

    Add:

    text
    [Service]
    NoNewPrivileges=yes
    ProtectSystem=strict
    ProtectHome=yes
    PrivateTmp=yes
    ReadWritePaths=/var/log/openvpn /etc/openvpn /run
    CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_SETUID CAP_SETGID CAP_DAC_OVERRIDE

    Then sudo systemctl daemon-reload && sudo systemctl restart [email protected].

    Troubleshooting

    ProblemLikely causeFix
    Client times out connectingUDP/1194 blocked upstreamsudo nft list ruleset on the server; check provider firewall; try TCP/443 as fallback.
    TLS handshake fails repeatedlytls-crypt key mismatchThe <tls-crypt> block inside .ovpn must be identical to /etc/openvpn/server/tc.key.
    VERIFY ERROR: depth=0, error=CRL has expiredCRL regeneration skippedRe-run easyrsa gen-crl, copy to /etc/openvpn/server/crl.pem, reload service.
    Client connects but no internetIP forwarding off or NAT rule missingsysctl net.ipv4.ip_forward must be 1; verify nftables masquerade rule references the right interface.
    "TLS key negotiation failed to occur within 60 seconds"Clock skew > 5 min between client and serversudo timedatectl set-ntp true.
    Slow throughput (< 30 Mbps) on a fast VPSAES without AES-NIgrep -o aes /proc/cpuinfo — if empty, force data-ciphers CHACHA20-POLY1305.
    RESOLVE: Cannot resolve host address on clientremote in .ovpn is wrongEnsure you passed the actual public IP to make-ovpn.
    Enable verbose logging temporarily by changing verb 3 to verb 5 in server.conf, reloading, and tailing the log.

    FAQ

    Should I use OpenVPN or WireGuard?

    Use WireGuard if you control every client and want the simplest, fastest option. Use OpenVPN when you need TCP fallback for restrictive networks (hotels, corporate firewalls, airports), certificate-based authentication with per-user revocation, LDAP/RADIUS integration, or compatibility with corporate VPN clients that only speak OpenVPN or IPsec. Many of our customers run both on the same VPS — OpenVPN on UDP/1194 for field laptops, WireGuard on UDP/51820 for server-to-server tunnels.

    Is OpenVPN on UDP 1194 fast enough for streaming?

    Yes. On a CloudCore Starter VPS with AES-NI, OpenVPN 2.6 with AES-256-GCM sustains 100–300 Mbps per client and saturates a 1 Gbps uplink with 4–5 concurrent users. The bottleneck is almost never the server — it is the client CPU or the last-mile ISP. For maximum throughput on hardware without AES-NI (older ARM devices), use data-ciphers CHACHA20-POLY1305.

    How do I revoke a lost client certificate?

    cd /etc/openvpn/easy-rsa && sudo ./easyrsa revoke <name> && sudo ./easyrsa gen-crl, copy pki/crl.pem into /etc/openvpn/server/crl.pem, and reload the service. The server rejects the revoked cert immediately on the next connection attempt. See Step 11 for the full procedure and automated CRL refresh.

    Can I run OpenVPN and WireGuard on the same VPS?

    Yes, and it is a common pattern. They use different UDP ports by default (1194 vs 51820) and different kernel interfaces (tun0 vs wg0). Keep the subnets distinct — for example 10.8.0.0/24 for OpenVPN and 10.10.0.0/24 for WireGuard — and add a second masquerade rule in your nftables config for the WireGuard subnet.

    How many clients can one VPS support?

    The CloudCore Starter (2 vCPU, 8 GB RAM) comfortably handles 50–75 concurrent users for browsing and work-from-home loads. OpenVPN's per-client memory footprint is small (a few MB) but the user-space crypto on a single process is the ceiling. For 200+ concurrent users, scale vertically (more vCPU with AES-NI) before scaling horizontally.

    Is the angristan script safe to use?

    Yes. It is widely audited, used in production by thousands of operators, and produces effectively the same config we wrote by hand. The only reasons to prefer the manual path are (a) you want to version-control your config in git, (b) you need custom network topology (site-to-site, CCD overrides, multiple subnets), or (c) you need to move the CA off-host.

    Next Steps

    Now that OpenVPN is up:

    • Add a complementary tunnel. Install WireGuard alongside for low-overhead server-to-server links, or strongSwan for IPsec/IKEv2 on iOS/macOS native clients, or OpenConnect if your users already have Cisco AnyConnect.
    • Monitor it. Wire openvpn_exporter into Prometheus and add a Grafana dashboard so you can see bandwidth per user and alert on abnormal connection patterns.
    • Back up the PKI. Tar up /etc/openvpn/easy-rsa/pki and encrypt it offline. Losing the CA private key means every client must be reissued.
    • Review upstream docs. OpenVPN's own community resources are the canonical reference for every directive and edge case.
    • Harden the host. Add Fail2Ban, enable unattended-upgrades, and run a CIS-benchmark scan.

    Ready to host your own VPN? The CloudCore Starter is optimized for VPN workloads — AES-NI-capable CPUs, unmetered egress, a clean static IP, and DDoS protection included. Spin one up in under a minute and have OpenVPN running before your coffee gets cold.

    Was this article helpful?

    ← Back to Install GuidesBrowse all categories →

    Still have questions?

    Contact Support →Submit a Ticket