Networking AWS

OpenVPN Community 2.7.8 on AWS User Guide

| Product: OpenVPN Community 2.7.8 on Ubuntu 24.04 on AWS

Overview

This guide covers launching and verifying OpenVPN Community 2.7.8 on Ubuntu 24.04 from cloudimg on Amazon EC2. It is one version of the cloudimg Hardened WireGuard VPN & VPN, Proxy and Remote Access AMIs listing on AWS Marketplace; you pick it as the software version when you subscribe and launch. The other VPN, proxy and remote access servers in the listing are described on the listing's guide hub.

OpenVPN Community is the open-source SSL/TLS VPN daemon. Remote devices connect to it with a certificate, and their traffic travels through an encrypted tunnel to the server, which forwards it on. On this image the EC2 instance is a routed VPN gateway: connected devices get an address on the 10.8.0.0/24 tunnel network and, by default, send all of their internet traffic through the instance (full tunnel).

OpenVPN 2.7.8 is installed from OpenVPN Inc's own signed Ubuntu repository (build.openvpn.net), not the older 2.6 package in the Ubuntu archive. The repository's signing key is pinned by fingerprint and the repository stays enabled, so OpenVPN 2.7 security releases install automatically with Ubuntu's unattended upgrades. The certificates are made with EasyRSA 3.2.7, installed from the upstream release after its signature was checked. Both are licensed GPL-2.0. This is the Community edition only: OpenVPN Access Server is not on the image. Build details are in /usr/share/doc/cloudimg-openvpn/BUILD-PROVENANCE.txt.

What is on the image:

  • OpenVPN Community 2.7.8 (openvpn-server@server.service): a routed VPN server on UDP port 1194, server address 10.8.0.1 on interface tun0
  • A certificate authority of its own: on your instance's first boot an EC (P-384) certificate authority, the server certificate, one client certificate (client1) and the tls-crypt key are generated; no two instances share any of them, and nothing private is in the image
  • A ready-to-import client profile: /root/client1.ovpn already carries your instance's public address and every certificate and key the client needs
  • Modern crypto only: TLS 1.2 or newer, AES-256-GCM or ChaCha20-Poly1305 for the data channel, tls-crypt on the control channel, no compression, and a certificate revocation list checked on every connection
  • No metadata access through the tunnel: a connected client cannot reach the instance metadata service through this server
  • Helpers: openvpn-client (add, revoke and list client certificates), openvpn-selftest (an end-to-end test with a real VPN client) and openvpn-imds-guard (the metadata guard and the VPN NAT rule)

Default user: ubuntu · Architecture: x86_64 · Root volume: 8 GiB gp3

Security posture

  • Per-instance keys. The certificate authority key, the server key, the client1 key and the tls-crypt key are created on your instance at first boot, in /etc/openvpn/pki (mode 0700, root only; every key file 0600). The image ships configuration only.
  • tls-crypt. Every control-channel packet is authenticated and encrypted with the per-instance tls-crypt key. A scanner, or a client without the key, gets no TLS handshake at all: the server drops its packets unanswered.
  • Certificates checked both ways. The server accepts only client certificates signed by your instance's own certificate authority and marked for TLS client use, and checks the revocation list on every new connection. The client profile accepts only the server certificate named server, signed by the same authority.
  • No compression, no client-to-client traffic, one device per certificate. Compression is refused (allow-compression no, VORACLE), clients cannot reach each other through the server, and a certificate is used by one device at a time (a second connection with the same certificate replaces the first).
  • Least privilege. The daemon drops to the nobody user once it has opened the tunnel device and loaded its keys. There is no management interface.
  • Fail-closed start. The server cannot start before first boot has generated the keys and loaded the metadata guard: its unit is gated on a ready marker and a preflight check. If first boot fails, the server stays stopped.
  • Metadata service protection. The OpenVPN daemon is denied the instance metadata service (169.254.169.254, fd00:ec2::254) by a systemd IPAddressDeny rule. Traffic the kernel forwards for VPN clients runs outside that rule, so a separate nftables table (inet cloudimg_openvpn) drops every forwarded packet to the link-local range 169.254.0.0/16 and to fd00:ec2::254. A connected device therefore cannot read the instance's metadata or role credentials through the tunnel.
  • Exactly two open ports. SSH on TCP 22 and OpenVPN on UDP 1194. There is no web server on this image.
  • Hardened SSH. Key-only login, no root login, no X11 forwarding.

Prerequisites

  1. An AWS account and permission to launch EC2 instances
  2. An EC2 key pair in the Region you launch in
  3. A subnet with outbound internet access (a public subnet, or a private subnet with a NAT gateway) and a public IP address or Elastic IP, so devices can reach the server and their traffic can leave through it
  4. An OpenVPN client on each device: OpenVPN Connect, or the OpenVPN Community client (openvpn on Linux and macOS, OpenVPN GUI on Windows)

Recommended instance type: m5.large. OpenVPN is light; the traffic your devices send through the tunnel is what needs bandwidth.

Connecting to your instance

Version Operating system SSH user
OpenVPN Community 2.7.8 on Ubuntu 24.04 Ubuntu 24.04 LTS ubuntu

Step 1: Launch the instance

Option A: AWS Marketplace console

  1. Open the cloudimg Hardened WireGuard VPN & VPN, Proxy and Remote Access AMIs listing on AWS Marketplace and choose View purchase options, then Subscribe. One subscription covers every version in the listing.
  2. Once the subscription is active, choose Launch your software.
  3. Choose One-click launch from AWS Marketplace (or Launch from EC2 Console for the full launch wizard). In Version, choose OpenVPN Community 2.7.8 on Ubuntu 24.04, then choose your Region.
  4. Pick an instance type (m5.large recommended; the software charge is $0.04 per vCPU per hour) and your key pair.
  5. Security group: the listing's default group allows SSH (TCP 22) and OpenVPN (UDP 1194) from anywhere, because devices connecting to a VPN usually have changing addresses. Restrict SSH to your own IP address. If all your devices connect from known addresses, restrict UDP 1194 to them as well.
  6. Choose a subnet with internet access and a public IP, leave the metadata defaults (the image already requires IMDSv2), then choose Launch.

If you want devices to keep the same server address when the instance stops and starts, attach an Elastic IP before first boot, or regenerate the client profiles afterwards (Step 11): the profile records the public address the instance had at first boot.

Option B: AWS CLI

Run this on your own workstation. It creates a security group that allows SSH from your management address and OpenVPN from anywhere, then launches the instance. Replace the AMI ID with the one shown for your Region on the Marketplace launch page, and the VPC, subnet, key pair and address range with your own:

SG=$(aws ec2 create-security-group --group-name openvpn \
  --description "OpenVPN Community server" --vpc-id vpc-0123456789abcdef0 \
  --query GroupId --output text)
aws ec2 authorize-security-group-ingress --group-id "$SG" --protocol tcp --port 22 --cidr <your-mgmt-cidr>
aws ec2 authorize-security-group-ingress --group-id "$SG" --protocol udp --port 1194 --cidr 0.0.0.0/0
aws ec2 run-instances \
  --image-id ami-0123456789abcdef0 \
  --instance-type m5.large \
  --key-name my-key-pair \
  --security-group-ids "$SG" \
  --subnet-id subnet-0123456789abcdef0 \
  --associate-public-ip-address \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=openvpn}]'

You do not need --metadata-options: the AMI already makes IMDSv2 required.

Step 2: Connect and read your server details

Connect as ubuntu with the key pair you chose:

ssh -i ~/.ssh/my-key-pair.pem ubuntu@<public-ip>

First boot finishes within a minute of launch. It generates the certificate authority and keys, writes the client profile, loads the metadata guard and starts the server. A summary is in /root/openvpn-credentials.txt, readable only by root. It holds no key:

sudo cat /root/openvpn-credentials.txt
# OpenVPN Community 2.7.8 - generated on first boot by openvpn-firstboot.service. No key is stored in this file.
# Client profile: /root/client1.ovpn (holds the client1 key: copy it to ONE device, then shred it here).
# More clients: sudo openvpn-client add NAME   Revoke: sudo openvpn-client revoke NAME   Self-test: sudo openvpn-selftest
# Guide: https://www.cloudimg.co.uk/guides/openvpn-community-2-7-8-on-ubuntu-24-04-aws/

openvpn.host=203.0.113.25
openvpn.port=1194/udp
openvpn.private_ipv4=172.31.87.170
openvpn.client_profile=/root/client1.ovpn
openvpn.vpn_subnet=10.8.0.0/24
openvpn.ca_fingerprint_sha256=67:60:70:0E:8C:99:72:EA:D6:98:8A:8B:80:59:BE:79:80:A0:DF:30:29:36:8B:56:6C:00:DD:81:19:87:A6:FB
openvpn.server_fingerprint_sha256=A4:5A:3F:73:71:E9:CF:6F:1B:DE:C3:83:7A:9B:AD:84:46:A6:89:9C:6D:4C:DC:C7:B4:43:AF:F5:8F:A0:29:B4
openvpn.pki=/etc/openvpn/pki

openvpn.host is the address in the client profiles (the instance's public IPv4; the private one if it has none). The two fingerprints identify your certificate authority and server certificate. Your values differ from the example.

Step 3: Confirm the server is running

The server and the metadata guard are active, and OpenVPN listens on UDP 1194:

systemctl is-active openvpn-server@server.service cloudimg-openvpn-guard.service
sudo ss -ulnp 'sport = :1194'
ip -4 -br addr show tun0
active
active
State  Recv-Q Send-Q Local Address:Port Peer Address:PortProcess
UNCONN 0      0            0.0.0.0:1194      0.0.0.0:*    users:(("openvpn",pid=10203,fd=5))
tun0             UNKNOWN        10.8.0.1/24

The version, and the repository it comes from:

openvpn --version | head -1
apt-cache policy openvpn | sed -n 1,4p
OpenVPN 2.7.8 x86_64-pc-linux-gnu [SSL (OpenSSL)] [LZO] [LZ4] [EPOLL] [PKCS11] [MH/PKTINFO] [AEAD] [DCO]
openvpn:
  Installed: 2.7.8-noble1
  Candidate: 2.7.8-noble1
  Version table:

Step 4: Run the self-test

openvpn-selftest connects a real OpenVPN client to the server from inside the instance (in a separate network namespace, using client1.ovpn), checks the tunnel and internet access through it, and checks that the client cannot reach the metadata service. It connects as client1, so a device already connected as client1 is disconnected for a few seconds:

sudo openvpn-selftest
ok: openvpn-server@server.service is active (pid 10203, user nobody)
ok: metadata guard loaded (forward drop 169.254.0.0/16, 168.63.129.16, fd00:ec2::254); NAT 10.8.0.0/24
ok: a real OpenVPN client (client1) connected over tls-crypt; tunnel address 10.8.0.2/24
ok: full tunnel: the client routes 169.254.169.254 and the internet into the tunnel (pushed redirect-gateway)
ok: ping 10.8.0.1 across the tunnel answers
ok: internet egress through the tunnel (ping 1.1.1.1) answers
ok: metadata: 169.254.169.254 is unreachable from a VPN client through the tunnel
ok: metadata: 169.254.169.254 is unreachable from inside openvpn-server@server.service
SELFTEST_OK

On an instance without a route to the internet, the egress line reads info: no internet egress through the tunnel instead; the other checks still pass.

Step 5: Copy the client profile to your device

/root/client1.ovpn contains the client1 private key. Copy it over SSH to the one device that will use it. On your workstation:

ssh -i ~/.ssh/my-key-pair.pem ubuntu@<public-ip> 'sudo cat /root/client1.ovpn' > client1.ovpn
chmod 600 client1.ovpn

Before you rely on it, check that the profile carries your certificate authority: the fingerprint of its <ca> block must equal openvpn.ca_fingerprint_sha256 from Step 2. On a Linux or macOS workstation:

sed -n '/<ca>/,/<\/ca>/p' client1.ovpn | sed '1d;$d' | openssl x509 -noout -fingerprint -sha256

Then delete the server's copy, so the key exists only on your device (you can always issue a new profile with Step 7):

sudo shred -u /root/client1.ovpn

Step 6: Connect a device

  • OpenVPN Connect (Windows, macOS, iOS, Android): choose Import Profile, then File, select client1.ovpn and connect.
  • OpenVPN Community client on Linux or macOS:
sudo openvpn --config client1.ovpn

The connection is up when the client logs Initialization Sequence Completed. The device gets an address in 10.8.0.0/24, the server offers 1.1.1.1 and 9.9.9.9 as its DNS servers, and all of its IPv4 traffic goes through the instance. From the device, ping 10.8.0.1 answers, and a "what is my IP" check shows the instance's public address.

On the server, the connection appears in the log:

client1/udp4:203.0.113.10:51820 Data Channel: cipher 'AES-256-GCM', peer-id: 0

Step 7: Add, list and revoke clients

Give every device its own certificate: a certificate serves one device at a time (a second connection with it replaces the first), and you can revoke a lost device without touching the others. openvpn-client add issues a certificate from your certificate authority and writes a complete profile to /root/<name>.ovpn:

sudo openvpn-client add laptop
sudo openvpn-client list
client profile written to /root/laptop.ovpn (copy it to the device, then delete it here: sudo shred -u /root/laptop.ovpn)
valid    client1
valid    laptop

Copy /root/laptop.ovpn to the device as in Step 5. Names use letters, digits, - and _, up to 32 characters.

To cut a device off, revoke its certificate. The revocation list is refreshed at once and the server refuses that certificate from its next connection attempt:

sudo openvpn-client revoke laptop
sudo openvpn-client list
laptop revoked; the CRL is refreshed and the server refuses laptop from its next connection attempt
(an established session ends at its next renegotiation; sudo systemctl restart openvpn-server@server ends it now)
valid    client1
revoked  laptop

Step 8: Split tunnel, DNS and other configuration

The server configuration is /etc/openvpn/server/server.conf. Common changes:

  • Split tunnel (only your own networks go through the VPN): remove the push "redirect-gateway def1 bypass-dhcp" line and push your networks instead, for example push "route 10.0.0.0 255.255.0.0" for a VPC.
  • DNS: replace the two push "dhcp-option DNS ..." lines. To resolve names in your VPC (including private hosted zones), push the VPC resolver at the VPC base address plus two, for example push "dhcp-option DNS 10.0.0.2". Do not push 169.254.169.253: the metadata guard drops all link-local addresses through the tunnel.
  • Port: change port 1194, and open the new port in the security group.

Apply a change with:

sudo systemctl restart openvpn-server@server

Keep the crypto, tls-crypt, crl-verify and user nobody lines as they are. The preflight check refuses to start the server if compression, the management interface, client-to-client or duplicate-cn is turned on.

Step 9: Metadata access is blocked

openvpn-imds-guard loads two nftables tables when the server starts: the guard, which drops forwarded traffic to the link-local range and to the IPv6 metadata address, and the NAT rule that lets VPN clients reach the internet through the instance:

sudo openvpn-imds-guard show
table inet cloudimg_openvpn {
    chain forward {
        type filter hook forward priority filter - 10; policy accept;
        ip daddr { 168.63.129.16, 169.254.0.0/16 } counter packets 0 bytes 0 drop
        ip6 daddr { fd00:ec2::123, fd00:ec2::253, fd00:ec2::254 } counter packets 0 bytes 0 drop
    }
}
table ip cloudimg_openvpn_nat {
    chain postrouting {
        type nat hook postrouting priority srcnat; policy accept;
        ip saddr 10.8.0.0/24 oifname != "tun0" masquerade
    }
}

The OpenVPN service itself is denied the metadata service as well:

systemctl show -p IPAddressDeny openvpn-server@server.service
IPAddressDeny=168.63.129.16/32 fd00:ec2::254/128 169.254.169.254/32

The server cannot run without the guard: openvpn-server@server.service requires cloudimg-openvpn-guard.service, and if nftables is restarted (which flushes every table) the guard is loaded again.

Step 10: How the fail-closed start works

On first boot, openvpn-firstboot.service generates the keys, writes the client profile, loads the guard and only then writes the ready marker /var/lib/cloudimg/openvpn-ready and starts the server. Before every start, the server runs openvpn-preflight, which checks the marker, the guard, the key files (root-owned, mode 0600) and the configuration:

sudo openvpn-preflight && echo "preflight OK"
systemctl cat openvpn-server@server.service | grep -E '^(ConditionPathExists|Requires|ExecStartPre)='
preflight OK
ConditionPathExists=/var/lib/cloudimg/openvpn-ready
Requires=cloudimg-openvpn-guard.service
ExecStartPre=+/usr/local/sbin/openvpn-preflight

Step 11: Protect the certificate authority key

Anyone with /etc/openvpn/pki/private/ca.key can issue client certificates your server accepts. It is root-only on the instance. To keep it offline, issue the client profiles you need first, then move the key off the instance and keep it somewhere safe:

ssh -i ~/.ssh/my-key-pair.pem ubuntu@<public-ip> 'sudo cat /etc/openvpn/pki/private/ca.key' > ca.key
sudo shred -u /etc/openvpn/pki/private/ca.key

The server keeps running, and restarts, without it (it needs only the CA certificate, its own key, the tls-crypt key and the revocation list). While the CA key is off the instance, openvpn-client add and revoke stop with a message saying the CA is not on this machine; copy it back (/etc/openvpn/pki/private/ca.key, owner root, mode 0600) when you need to issue or revoke a certificate.

To start over with a new certificate authority (for example after the instance's public address changed, or if a key may have leaked), remove the old one and run first boot again. Every existing client profile stops working:

sudo systemctl stop openvpn-server@server
sudo find /etc/openvpn/pki -type f -exec shred -u {} +
sudo rm -rf /etc/openvpn/pki /var/lib/cloudimg/openvpn-ready /var/lib/cloudimg/openvpn-firstboot.done
sudo systemctl restart openvpn-firstboot.service

Step 12: Updates

Ubuntu's unattended upgrades install security updates daily, from both the Ubuntu archive and OpenVPN Inc's repository (the image adds site=build.openvpn.net to the allowed origins in /etc/apt/apt.conf.d/51cloudimg-openvpn-unattended). To update now, and to make sure the server runs the new version:

sudo apt-get update && sudo apt-get upgrade
sudo systemctl restart openvpn-server@server

Step 13: Logs

The server logs connections, certificate checks and errors to the journal (no keys are logged):

sudo journalctl -u openvpn-server@server --no-pager -n 20

A refused device shows why: tls-crypt unwrapping failed (the profile has the wrong tls-crypt key), VERIFY ERROR ... unable to get local issuer certificate (a certificate from another authority) or VERIFY ERROR ... certificate revoked.

Step 14: Security recommendations

  • Restrict SSH (TCP 22) in the security group to your management address. Restrict UDP 1194 to your devices' addresses if they are fixed.
  • Give each device its own certificate (Step 7), copy each profile to its device only, and delete the server's copy.
  • Revoke a lost or retired device at once (Step 7).
  • Move the certificate authority key offline once your clients are issued (Step 11).
  • Keep unattended upgrades on, so OpenVPN and the operating system stay patched.

Support and licensing

OpenVPN Community is licensed under the GNU General Public License version 2 with a linking exception for OpenSSL; EasyRSA is licensed under the GNU General Public License version 2. The licence texts are in /usr/share/doc/openvpn/copyright and /usr/local/share/easy-rsa/COPYING.md, and the source is available from the upstream projects and from cloudimg on request. cloudimg support covers deploying and operating this image; contact support@cloudimg.co.uk.

OpenVPN is a registered trademark of OpenVPN Inc. cloudimg is not affiliated with or endorsed by OpenVPN Inc.