OpenVPN Community Edition on AWS User Guide
Overview
This guide covers the deployment and use of OpenVPN Community Edition on AWS using cloudimg AWS Marketplace images.
OpenVPN Community Edition is the long-established open-source SSL/TLS VPN daemon. It builds an encrypted, certificate-authenticated tunnel between remote clients and a private network, and it is one of the most widely deployed remote-access VPN implementations in use. It is a headless network service: clients are provisioned by issuing certificates from the command line, and there is no web console.
The image ships OpenVPN Community Edition 2.7.5, the current upstream stable release, installed from OpenVPN Inc's own cryptographically signed software repository rather than from the distribution archive. That choice is deliberate: the distribution's package is fully patched but two upstream point releases behind by version string, and taking the release straight from upstream keeps the shipped version and the patch state in agreement.
Licence and trademark
This product ships the open-source Community Edition only, licensed under the GNU GPL version 2, which the build verifies from the package's own copyright file. It is not OpenVPN Access Server — the commercial, licence-restricted product — and the build fails closed if any Access Server package or path is present.
OpenVPN is a registered trademark of OpenVPN Inc. cloudimg is not affiliated with, endorsed by or sponsored by OpenVPN Inc. The name is used here only to identify the open-source software this image packages.
No key material ships in the image
A VPN server is only as trustworthy as its private keys, so this image contains none.
No certificate authority key, server key, client key, control-channel key or client profile is baked into the AMI. On first boot each instance generates its own elliptic-curve certificate authority, its own server certificate, its own control-channel key and one ready-to-use client profile, then verifies the tunnel by connecting a client to itself before reporting success. Two instances launched from this image have entirely different certificate authorities, so nobody else's clients can ever authenticate to your server.
Before you start
The VPN cannot be reached until you allow its port inbound. This is the single most common reason a first client fails to connect.
| Port | Protocol | Purpose | Who should reach it |
|---|---|---|---|
| 1194 | UDP | The VPN tunnel. You must allow this inbound or no client can connect. | Your VPN clients |
| 22 | TCP | Administration over SSH | Your administrators only |
| 80 | TCP | Unauthenticated plain-text health endpoint at /healthz. No web UI is served here. |
Your load balancer or uptime monitor |
Give the instance a public IP or an Elastic IP so clients have a stable address. The client profile generated at first boot embeds whatever public address the instance had at that moment, so if you attach an Elastic IP afterwards you will need to edit the remote line in the profile to match.
Connecting to your instance
| OS variant | SSH login user | Command |
|---|---|---|
| Ubuntu 24.04 | ubuntu |
ssh -i your-key.pem ubuntu@<public-ip> |
Replace <public-ip> with the public IPv4 address of your instance and your-key.pem with the private key of the EC2 key pair you launched it with.
Verify the service
Confirm the VPN daemon and the health endpoint are running, and check which release you have.
sudo systemctl is-active openvpn-server@server nginx
openvpn --version | awk 'NR==1'
apt-cache policy openvpn | awk 'NR<=3'
The daemon runs under systemd as the packaged template unit openvpn-server@server, reading /etc/openvpn/server/server.conf. Confirm it is bound to the tunnel port and that the tunnel interface is up:
sudo ss -lunp | grep 1194
ip -4 -br addr show tun0
tun0 holds 10.8.0.1, the server's address inside the VPN. Connected clients receive addresses from 10.8.0.0/24.

The certificate authority is yours alone
The certificate authority below was created on this instance, on its first boot. Its subject and fingerprint are unique to this machine.
sudo openssl x509 -in /var/lib/openvpn-pki/pki/ca.crt -noout -subject -dates
sudo openssl x509 -in /var/lib/openvpn-pki/pki/ca.crt -noout -fingerprint -sha256
If you launch a second instance from the same image and run the same command, you will get a different fingerprint. That is the point: the image ships no shared trust anchor.
The matching private keys exist on disk but are readable only by root and are never displayed. You can prove they exist and are correctly permissioned without ever revealing them:
sudo ls -l /var/lib/openvpn-pki/pki/private/ /var/lib/openvpn-pki/tls-crypt.key

The whole PKI lives on a dedicated data volume mounted at /var/lib/openvpn-pki, separate from the operating system disk, so it can be encrypted, snapshotted and restored on its own:
findmnt -no SOURCE,TARGET,FSTYPE,OPTIONS /var/lib/openvpn-pki
df -h /var/lib/openvpn-pki | tail -1
Your first client
First boot writes one ready-to-use client profile at /root/client1.ovpn. It is a complete inline profile — certificate authority, client certificate, client key and control-channel key are all embedded — and its remote line already points at this instance.
Treat that file as a secret. It contains a private key, so anyone who obtains it can connect to your VPN. This guide never prints its contents; you can confirm it exists and check its non-secret settings instead:
sudo ls -l /root/client1.ovpn
sudo grep -E '^(client|dev|proto|remote|data-ciphers|auth|tls-version-min)' /root/client1.ovpn
To get it onto your device, copy it down over SSH from your own machine and then import it into any standard OpenVPN client:
| Step | Command, run on your own machine |
|---|---|
| Copy the profile down | ssh -i your-key.pem ubuntu@<public-ip> "sudo cat /root/client1.ovpn" > client1.ovpn |
| Restrict it locally | chmod 600 client1.ovpn |
| Connect on Linux or macOS | sudo openvpn --config client1.ovpn |
| Connect on Windows, iOS or Android | Import client1.ovpn into the OpenVPN Connect app |
Once connected, your client receives an address in 10.8.0.0/24 and can reach the server at 10.8.0.1.
Proving the tunnel really works
You do not have to take that on trust. The following runs entirely on the instance: it connects a real OpenVPN client to the running server using the first-boot profile, waits for a tunnel address, and passes traffic across the tunnel. It cleans up after itself and changes nothing.
sudo bash -c '
set -e
CONF=$(mktemp /tmp/ovpn-selftest.XXXXXX.ovpn)
sed -e "s/^remote .*/remote 127.0.0.1 1194/" /root/client1.ovpn > "$CONF"
echo route-nopull >> "$CONF"
openvpn --config "$CONF" --dev tun9 --daemon --log /tmp/ovpn-selftest.log --writepid /tmp/ovpn-selftest.pid
for i in $(seq 1 40); do ip -4 -o addr show tun9 >/dev/null 2>&1 && break; sleep 1; done
sleep 3
echo "--- client tunnel address ---"
ip -4 -br addr show tun9
echo "--- handshake ---"
grep "Initialization Sequence Completed" /tmp/ovpn-selftest.log
echo "--- traffic across the tunnel ---"
ping -c 3 10.8.0.1
kill "$(cat /tmp/ovpn-selftest.pid)" 2>/dev/null || true
sleep 1
rm -f "$CONF" /tmp/ovpn-selftest.log /tmp/ovpn-selftest.pid
'
A completed initialization sequence plus replies from 10.8.0.1 means the mutual-TLS handshake succeeded and encrypted traffic is flowing. The same check runs automatically during first boot, and the instance refuses to report ready if it fails.

Health endpoint, and why there is no web console
OpenVPN Community Edition has no web management interface, and this image does not invent one. Port 80 serves a single unauthenticated plain-text health endpoint so a load balancer or uptime monitor has something cheap to poll:
curl -s http://127.0.0.1/healthz
curl -s -o /dev/null -w 'content-type: %{content_type}\n' http://127.0.0.1/
The response is text/plain, not an application UI. All administration — issuing certificates, revoking them, changing pushed routes, reading connection status — is done over SSH.
Full tunnel or split tunnel
By default the server pushes a default route, so all of a connected client's traffic egresses through this instance (a full tunnel). That is why the image ships IP forwarding and a NAT rule:
cat /proc/sys/net/ipv4/ip_forward
sudo nft list table ip openvpn_nat

Whether to keep the full tunnel is your decision, and it is a real one:
- Full tunnel sends everything through the instance. Good for privacy on untrusted networks; it also means every byte your clients transfer is billed as data transfer on this instance and is limited by its network bandwidth.
- Split tunnel sends only your own subnets through the VPN and leaves the rest of the client's traffic on its local connection. Lower cost and lower latency, but no protection for general browsing.
To switch to a split tunnel, edit the server configuration, remove the pushed default route and push only the routes you want, then restart:
sudo nano /etc/openvpn/server/server.conf
# remove this line:
push "redirect-gateway def1 bypass-dhcp"
# and add the routes your clients should reach over the VPN, for example:
push "route 10.0.0.0 255.255.0.0"
sudo systemctl restart openvpn-server@server
Existing clients pick up the change the next time they reconnect.
Adding and revoking clients
Share client1.ovpn with one device only. Issue a separate certificate per device so you can revoke one without disturbing the others.
To add a client, replace <client-name> with a name of your own:
sudo EASYRSA_PKI=/var/lib/openvpn-pki/pki \
/usr/share/easy-rsa/easyrsa build-client-full <client-name> nopass
Then assemble an inline profile for it. Take the header from the existing profile and append the new client's certificate and key, plus the shared certificate authority and control-channel key:
sudo bash -c '
C=<client-name>
P=/var/lib/openvpn-pki/pki
sed "/^<ca>/,\$d" /root/client1.ovpn > /root/$C.ovpn
{ echo "<ca>"; cat $P/ca.crt; echo "</ca>"
echo "<cert>"; openssl x509 -in $P/issued/$C.crt; echo "</cert>"
echo "<key>"; cat $P/private/$C.key; echo "</key>"
echo "<tls-crypt>"; cat /var/lib/openvpn-pki/tls-crypt.key; echo "</tls-crypt>"
} >> /root/$C.ovpn
chmod 600 /root/$C.ovpn'
To revoke a client, revoke its certificate, regenerate the revocation list and tell the server to enforce it:
sudo EASYRSA_PKI=/var/lib/openvpn-pki/pki /usr/share/easy-rsa/easyrsa revoke <client-name>
sudo EASYRSA_PKI=/var/lib/openvpn-pki/pki /usr/share/easy-rsa/easyrsa gen-crl
# add this line to /etc/openvpn/server/server.conf, then restart the service:
crl-verify /var/lib/openvpn-pki/pki/crl.pem
sudo systemctl restart openvpn-server@server
A revocation list expires. If you enable crl-verify, regenerate the list with gen-crl before it lapses, or the server will refuse every connection.
Where things live
| Item | Path |
|---|---|
| Server configuration | /etc/openvpn/server/server.conf |
| Certificate authority and all certificates | /var/lib/openvpn-pki/pki (dedicated data volume) |
| Control-channel key | /var/lib/openvpn-pki/tls-crypt.key |
| Ready-to-use client profile | /root/client1.ovpn |
| Per-instance summary | /root/openvpn-credentials.txt |
| Connection status | /var/log/openvpn/openvpn-status.log |
| Service logs | sudo journalctl -u openvpn-server@server |
The per-instance summary records this instance's endpoint, its certificate authority fingerprint and where everything lives. It contains no password:
sudo grep -E '^openvpn\.(host|endpoint|vpn\.subnet|server\.tun\.ip|pki\.dir|client\.config)=' /root/openvpn-credentials.txt
Staying patched
The image is configured to take unattended security upgrades from both the distribution archive and OpenVPN Inc's own repository, so the VPN daemon keeps receiving upstream fixes after launch:
cat /etc/apt/apt.conf.d/51cloudimg-openvpn-unattended
cat /etc/apt/sources.list.d/openvpn-stable.list
The repository is added with an explicit signing key, so every package apt installs from it is verified against that key.
Backing up your certificate authority
The certificate authority is the one thing you cannot regenerate without re-issuing every client profile. Because it lives on its own EBS volume, the simplest backup is a snapshot of that volume, taken from the EC2 console or the AWS CLI. Restoring the snapshot onto a replacement instance brings back every issued certificate and every client profile continues to work.
Troubleshooting
| Symptom | Most likely cause | What to check |
|---|---|---|
| Client times out with no response | Inbound UDP 1194 is not allowed | The instance security group. This is by far the most common cause. |
TLS key negotiation failed |
The client is reaching something other than this server, or the control-channel key does not match | That the profile came from this instance, and that remote matches its current public address |
| Connects, but nothing is reachable | Routes were not pulled, or NAT is not in place | ip route on the client; sudo nft list table ip openvpn_nat on the server |
| Service will not start | The PKI volume did not mount, so the certificates are missing | findmnt /var/lib/openvpn-pki and sudo journalctl -u openvpn-server@server |
| Every client is rejected after a revocation | The revocation list has expired | Re-run easyrsa gen-crl |
Support
cloudimg support covers deployment and launch problems, security group and routing configuration, certificate issuance and revocation, split and full tunnel setup, client onboarding, data volume expansion and upgrades to newer OpenVPN Community Edition releases.
Support is available 24/7 by email and live chat, and we aim to respond within one business day. Contact support@cloudimg.co.uk.