OpenVPN Community on Ubuntu 24.04 on Azure User Guide
Overview
This guide covers the deployment and configuration of OpenVPN Community on Ubuntu 24.04 on Azure using cloudimg Azure Marketplace images. OpenVPN Community Edition is the classic open source SSL/TLS VPN daemon: a routed, certificate authenticated VPN server that gives remote clients a secure encrypted tunnel into your Azure network and out to the internet.
This image ships only the GPL 2.0 Community Edition (never the proprietary Access Server). It installs the current stable OpenVPN 2.6 line from the Ubuntu 24.04 archive, which is actively security patched through the Ubuntu update stream, so unattended security upgrades keep the daemon patched on your running VM.
Security by design — no shipped keys, a per instance PKI. A VPN image that ships baked in private keys would let anyone with the image impersonate the server or decrypt traffic, so this image never ships any key material. On the very first boot of every VM a fresh per instance easy-rsa PKI is generated (an elliptic curve CA, a server certificate and key, a tls-crypt control channel key, and an initial client certificate). No two VMs ever share key material, and nothing private exists in the captured image. The certificate authority, server and client certificates, and the tls-crypt key live on a dedicated data disk at /var/lib/openvpn-pki.
Ready to connect out of the box. First boot also assembles a complete, inline client configuration at /root/client1.ovpn (mode 0600, root only) whose remote line already points at this VM public IP. Copy that file to a client machine and connect immediately, then add your own per client certificates for real users.
What is included:
-
OpenVPN 2.6 (current stable line) from the Ubuntu 24.04 archive, run under systemd as the packaged template unit
openvpn-server@server, dropping to the unprivilegednobodyuser after reading its keys -
A routed UDP VPN on port
1194with modern crypto:data-ciphers AES-256-GCM, atls-cryptprotected control channel, TLS 1.2 minimum, and an elliptic curve (secp384r1) PKI -
A per instance PKI generated on first boot (elliptic curve CA, server and client certificates, tls-crypt key) — no shared or shipped keys — stored on a dedicated 20 GiB Azure data disk at
/var/lib/openvpn-pki -
A ready to use inline client config at
/root/client1.ovpnwhoseremotealready points at your VM public IP -
Internet egress for VPN clients baked in as static configuration: IP forwarding plus an nftables masquerade rule, both persisted across reboot
-
A tiny nginx on port
:80serving only an unauthenticated/healthzendpoint (HTTP 200) for Azure Load Balancer health probes — this is not a VPN management UI -
Ubuntu 24.04 LTS base with latest security patches applied at build time, and unattended security upgrades kept enabled
-
24/7 cloudimg support with a guaranteed 24 hour response SLA
Prerequisites
-
Active Azure subscription, SSH public key, VNet + subnet in target region
-
Subscription to the OpenVPN Community listing on Azure Marketplace
-
Network Security Group rules allowing TCP 22 (admin) and UDP 1194 (the VPN) from the clients that will connect
Recommended virtual machine size: Standard_B2s (2 vCPU, 4 GB RAM) for a small remote access VPN. Higher throughput or many concurrent clients should use Standard_D2s_v5 or larger.
Step 1: Deploy from the Azure Portal
Search OpenVPN Community in Marketplace, select the cloudimg publisher, and click Create. Configure the Network Security Group to allow UDP 1194 (the VPN) from your client networks and TCP 22 for administration.
Step 2: Deploy from the Azure CLI
RG="vpn-prod"; LOCATION="eastus"; VM_NAME="vpn1"
GALLERY_IMAGE_ID="/subscriptions/<sub-id>/resourceGroups/azure-cloudimg/providers/Microsoft.Compute/galleries/cloudimgGallery/images/openvpn-ubuntu-24-04/versions/<version>"
SSH_KEY="$(cat ~/.ssh/id_rsa.pub)"
az group create --name "$RG" --location "$LOCATION"
az vm create \
--resource-group "$RG" --name "$VM_NAME" \
--image "$GALLERY_IMAGE_ID" \
--size Standard_B2s \
--admin-username azureuser \
--ssh-key-values "$SSH_KEY" \
--public-ip-sku Standard
# Open the VPN and admin ports on the VM NSG (OpenVPN uses UDP 1194 by default):
az vm open-port --resource-group "$RG" --name "$VM_NAME" --port 1194 --priority 1001
az vm open-port --resource-group "$RG" --name "$VM_NAME" --port 22 --priority 1002
OpenVPN uses UDP 1194 by default. Make sure the NSG rule uses the UDP protocol, not TCP.
Step 3: First boot
On first boot the image resolves the VM public IP, generates a fresh per instance easy-rsa PKI (CA, server and client certificates, and the tls-crypt key), brings up the openvpn-server@server service, proves the tunnel works end to end, assembles the ready to use /root/client1.ovpn, and writes /root/openvpn-credentials.txt. This completes within a minute. SSH in as azureuser and read the summary:
sudo cat /root/openvpn-credentials.txt
The summary records this VM public IP, the VPN subnet (10.8.0.0/24), the CA fingerprint, and the location of the client config and PKI.
Step 4: Confirm the VPN server is running
The openvpn-server@server service and the nginx health endpoint are both active, the server holds the VPN endpoint address 10.8.0.1 on tun0, and the daemon is listening on UDP 1194:
systemctl is-active openvpn-server@server nginx.service
ip -4 addr show tun0
ss -lunp | grep ':1194 '

The unauthenticated health endpoint used by Azure Load Balancer probes returns HTTP 200:
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1/healthz
Step 5: Inspect the per instance PKI
The certificate authority, server and client certificates, and the tls-crypt key were generated on this VM first boot and live on the dedicated data disk at /var/lib/openvpn-pki. Private keys are root only (mode 0600). Nothing here was shipped in the image:
sudo findmnt -no SOURCE,TARGET,FSTYPE,SIZE /var/lib/openvpn-pki
sudo openssl x509 -in /var/lib/openvpn-pki/pki/ca.crt -noout -subject -dates -fingerprint -sha256
sudo stat -c '%n %a %U:%G' /var/lib/openvpn-pki/pki/private/server.key /var/lib/openvpn-pki/tls-crypt.key

Step 6: The ready to use client config
A complete inline client configuration for this VM is waiting at /root/client1.ovpn. Its remote line already points at this VM public IP, and the CA, client certificate, client key, and tls-crypt key are all embedded inline. Copy it to a client machine and connect:
sudo cat /root/client1.ovpn

The <key> and <tls-crypt> blocks in the screenshot above are redacted for display only. The real file contains the live per instance private material and is why it is stored root only.
Step 7: Prove the tunnel works
From any client that has client1.ovpn, connect with sudo openvpn --config client1.ovpn and then, in a second terminal, ping 10.8.0.1 to reach the server across the tunnel. The client completes the TLS handshake, receives a 10.8.0.x address on its tun interface, and reaches the server VPN address 10.8.0.1 inside the encrypted tunnel.
The block below runs that whole exchange as a self contained quick self test: it connects a client to the server over loopback on a separate tun9 interface, confirms the client received a VPN address, pings the server across the tunnel, then disconnects:
sudo bash -c '
CONF=$(mktemp /tmp/selftest.XXXX.ovpn)
sed -e "s/^remote .*/remote 127.0.0.1 1194/" /root/client1.ovpn > "$CONF"; echo route-nopull >> "$CONF"
LOG=$(mktemp /tmp/selftest.XXXX.log)
openvpn --config "$CONF" --dev tun9 --daemon --log "$LOG" --writepid /tmp/selftest.pid
for i in $(seq 1 30); do ip -4 addr show tun9 2>/dev/null | grep -q 10.8.0. && break; sleep 1; done
CIP=$(ip -4 addr show tun9 2>/dev/null | grep -oE "10\.8\.0\.[0-9]+" | head -1)
echo "client connected: tun9 = $CIP"
ping -c 3 10.8.0.1
kill "$(cat /tmp/selftest.pid 2>/dev/null)" 2>/dev/null; rm -f "$CONF" "$LOG" /tmp/selftest.pid
'

Because the server pushes redirect-gateway, a normally connected client also sends its internet traffic through the VPN, egressing via this VM (IP forwarding and an nftables masquerade are configured in the image).
Step 8: Add and revoke clients
Sharing client1 is fine for a quick test, but give each real user their own certificate. Build a new client certificate with easy-rsa against the per instance PKI, then assemble an inline .ovpn for it:
export EASYRSA_PKI=/var/lib/openvpn-pki/pki
sudo -E /usr/share/easy-rsa/easyrsa build-client-full alice nopass
To revoke a client and stop it connecting, revoke the certificate, regenerate the CRL, enable crl-verify in the server config, and restart the service:
export EASYRSA_PKI=/var/lib/openvpn-pki/pki
sudo -E /usr/share/easy-rsa/easyrsa revoke alice
sudo -E /usr/share/easy-rsa/easyrsa gen-crl
echo "crl-verify /var/lib/openvpn-pki/pki/crl.pem" | sudo tee -a /etc/openvpn/server/server.conf
sudo systemctl restart openvpn-server@server
Step 9: Harden for production
-
Give every user their own certificate (Step 8) and never share
client1beyond an initial test. -
Scope what is routed. The default pushes
redirect-gateway(full tunnel). For split tunnel, remove that push and push only the routes your clients need in/etc/openvpn/server/server.conf. -
Set your own DNS. The image pushes public resolvers; push your internal DNS with
push "dhcp-option DNS <ip>"for split horizon name resolution. -
Restrict the NSG. Allow UDP 1194 only from the client networks that need it, and keep TCP 22 limited to your admin range.
-
Keep the PKI safe. It lives on the data disk at
/var/lib/openvpn-pki; back up the CA and snapshot the disk as part of your DR plan.
Support
cloudimg provides 24/7 support with a guaranteed 24 hour response SLA. Contact support@cloudimg.co.uk.
OpenVPN is a registered trademark of OpenVPN Inc. cloudimg is not affiliated with or endorsed by OpenVPN Inc. This image ships the open source OpenVPN Community Edition (GPL 2.0), not the proprietary Access Server. All product and company names are trademarks or registered trademarks of their respective holders and are used only to identify the software.