English | 简体中文 | 繁體中文 | Русский
A Docker image to run an OpenVPN server. Based on Alpine Linux with OpenVPN and EasyRSA. Designed to be simple, modern, and maintainable.
- Automatically generates PKI, server certificates, and a client config on first start
- Client management via a helper script (
ovpn_manage) - Modern cipher suite: AES-128-GCM, SHA256, tls-crypt
- IPv6 support when the server has a public IPv6 address (see requirements)
- Persistent data via a Docker volume
- Multi-arch:
linux/amd64,linux/arm64,linux/arm/v7
Also available: Docker images for WireGuard, IPsec VPN, and Headscale.
Step 1. Start the OpenVPN server:
docker run \
--name openvpn \
--restart=always \
-v openvpn-data:/etc/openvpn \
-p 1194:1194/udp \
-d --cap-add=NET_ADMIN \
--device=/dev/net/tun \
--sysctl net.ipv4.ip_forward=1 \
--sysctl net.ipv6.conf.all.forwarding=1 \
hwdsl2/openvpn-serverOn first start, the server automatically generates a PKI, server certificate, TLS crypt key, and a client configuration named client.ovpn.
Step 2. Copy the client configuration to your local machine:
docker cp openvpn:/etc/openvpn/clients/client.ovpn .Import client.ovpn into your OpenVPN client to connect.
- A Linux server with a public IP address or DNS name
- Docker installed
- VPN port open in your firewall (UDP 1194 by default, or your configured port/protocol)
Get the trusted build from the Docker Hub registry:
docker pull hwdsl2/openvpn-serverAlternatively, you may download from Quay.io:
docker pull quay.io/hwdsl2/openvpn-server
docker image tag quay.io/hwdsl2/openvpn-server hwdsl2/openvpn-serverSupported platforms: linux/amd64, linux/arm64 and linux/arm/v7.
To update the Docker image and container, first download the latest version:
docker pull hwdsl2/openvpn-serverIf the Docker image is already up to date, you should see:
Status: Image is up to date for hwdsl2/openvpn-server:latest
Otherwise, it will download the latest version. Remove and re-create the container using instructions from Quick Start. Your data is preserved in the openvpn-data volume.
All variables are optional. If not set, secure defaults are used automatically.
This Docker image uses the following variables, that can be declared in an env file (see example):
| Variable | Description | Default |
|---|---|---|
VPN_DNS_NAME |
Fully qualified domain name (FQDN) of the server | Auto-detected public IP |
VPN_PUBLIC_IP |
Public IPv4 address of the server | Auto-detected |
VPN_PUBLIC_IP6 |
Public IPv6 address of the server | Auto-detected |
VPN_PROTO |
VPN protocol: udp or tcp |
udp |
VPN_PORT |
VPN port (1–65535) | 1194 |
VPN_CLIENT_NAME |
Name of the first client config generated | client |
VPN_DNS_SRV1 |
Primary DNS server pushed to clients | 8.8.8.8 |
VPN_DNS_SRV2 |
Secondary DNS server pushed to clients | 8.8.4.4 |
Note: In your env file, DO NOT put "" or '' around values, or add space around =. If you change VPN_PORT or VPN_PROTO, update the -p flag in the docker run command accordingly.
Example using an env file:
docker run \
--name openvpn \
--env-file ./vpn.env \
--restart=always \
-v openvpn-data:/etc/openvpn \
-p 1194:1194/udp \
-d --cap-add=NET_ADMIN \
--device=/dev/net/tun \
--sysctl net.ipv4.ip_forward=1 \
--sysctl net.ipv6.conf.all.forwarding=1 \
hwdsl2/openvpn-serverUse docker exec to manage clients with the ovpn_manage helper script.
Add a new client:
docker exec openvpn ovpn_manage --addclient alice
docker cp openvpn:/etc/openvpn/clients/alice.ovpn .Export a client config (prints to stdout):
docker exec openvpn ovpn_manage --exportclient alice > alice.ovpnList clients:
docker exec openvpn ovpn_manage --listclientsRevoke a client (will prompt for confirmation):
docker exec -it openvpn ovpn_manage --revokeclient alice
# Or revoke without confirmation prompt:
docker exec openvpn ovpn_manage --revokeclient alice -yAll server and client data is stored in the Docker volume (/etc/openvpn inside the container):
/etc/openvpn/
├── server/
│ ├── server.conf # OpenVPN server configuration
│ ├── ca.crt # CA certificate
│ ├── server.crt/key # Server certificate and key
│ ├── tc.key # TLS crypt key
│ ├── dh.pem # DH parameters
│ ├── crl.pem # Certificate revocation list
│ ├── client-common.txt # Client config template
│ ├── ipp.txt # IP pool persistence
│ └── easy-rsa/pki/ # Full PKI directory
└── clients/
├── client.ovpn # First client config
└── alice.ovpn # Additional clients
Back up the Docker volume to preserve all keys and client configurations.
If the Docker host has a public (global unicast) IPv6 address and the requirements below are met, IPv6 support is automatically enabled when the container starts. No manual configuration is needed.
Requirements:
- The Docker host must have a routable global unicast IPv6 address (starting with
2or3). Link-local (fe80::/10) addresses are not sufficient. - IPv6 must be enabled for the Docker container. See Enable IPv6 support in Docker.
To enable IPv6 for the Docker container, first enable IPv6 in the Docker daemon by adding the following to /etc/docker/daemon.json on the Docker host, then restart Docker:
{
"ipv6": true,
"fixed-cidr-v6": "fddd:1::/64"
}After that, re-create the Docker container. To verify that IPv6 is working, connect to the VPN and check your IPv6 address, e.g. using test-ipv6.com.
cp vpn.env.example vpn.env
# Edit vpn.env if needed, then:
docker compose up -d
docker cp openvpn:/etc/openvpn/clients/client.ovpn .- Base image:
alpine:3.23 - OpenVPN: latest available from Alpine packages
- EasyRSA: 3.2.6 (bundled at build time)
- Cipher: AES-128-GCM
- Auth: SHA256
- Key exchange: tls-crypt (HMAC + encrypt)
- DH parameters: pre-defined ffdhe2048 group (RFC 7919)
- Client certificates: 10-year validity
- VPN subnet:
10.8.0.0/24 - IPv6 VPN subnet:
fddd:1194:1194:1194::/64(when server has IPv6)
Note: The software components inside the pre-built image (such as OpenVPN and EasyRSA) are under the respective licenses chosen by their respective copyright holders. As for any pre-built image usage, it is the image user's responsibility to ensure that any use of this image complies with any relevant licenses for all software contained within.
Copyright (C) 2026 Lin Song
This work is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License.
This project is based in part on the work of Nyr and contributors, licensed under the MIT License.
