Servidor OpenVPN usando Docker (imagen kylemanna/openvpn) y Docker Compose.
Además del servidor principal, este proyecto incluye un stack separado para cliente OpenVPN site-to-site (docker-compose.s2s.yml).
- Docker + Docker Compose v2 (
docker compose). - Ejecutar estos comandos en el host donde corre Docker (típicamente tu Debian/Ubuntu).
Dependencias opcionales (automatizaciones):
- Para empaquetar
.zip:zip. - Para usar el Makefile (
make health,make client-export, etc.):make. - Para backups/restore:
tar(normalmente ya viene instalado). - Para hashes:
sha256sum(en Debian/Ubuntu viene encoreutils).
En Debian/Ubuntu:
sudo apt update
sudo apt install -y zip makeNota: el servicio usa
network_mode: host, así que el contenedor expone OpenVPN directamente en el host.
- Servidor OpenVPN (PKI/CA propia):
docker-compose.yml+ovpn-data/ - Cliente site-to-site (separado):
docker-compose.s2s.yml+ovpn-s2s-data/
Esto permite mantener en un solo proyecto los dos modos sin mezclar configuraciones.
Edita el archivo .env con tus valores:
PUBLIC_ENDPOINT: endpoint público donde se conectarán los clientes (IP pública o DDNS). Formato esperado por OpenVPN:udp://host:puerto.OVPN_PORT/OVPN_PROTO: puerto y protocolo (típicamente1194/udp).OVPN_CA_PASSPHRASE: passphrase local de la CA para automatizaciones Easy-RSA no interactivas.LAN_SUBNET/LAN_MASK: red LAN a la que quieres dar acceso desde la VPN (ruta a empujar al cliente).
Ejemplo:
PUBLIC_ENDPOINT=udp://tu-dominio-ddns:1194
OVPN_PORT=1194
OVPN_PROTO=udp
LAN_SUBNET=192.168.1.0
LAN_MASK=255.255.255.0Esto genera la configuración base y el PKI en ./ovpn-data/.
Si estás en bash, puedes cargar variables desde .env para no reescribirlas:
set -a
source ./.env
set +aGenerar config (ajusta rutas si tu LAN es distinta):
docker compose run --rm openvpn ovpn_genconfig \
-u "$PUBLIC_ENDPOINT" \
-p "route ${LAN_SUBNET} ${LAN_MASK}"Inicializar PKI/CA (te pedirá passphrase para la CA):
docker compose run --rm openvpn ovpn_initpkidocker compose up -dVerificar interfaz TUN:
ip -br a | grep tunDebería aparecer tun0.
Verificar puerto UDP:
sudo ss -ulnp | grep "$OVPN_PORT"Chequeo de salud (2 niveles):
- Docker healthcheck (estado
healthy/unhealthy):
docker inspect -f '{{.State.Health.Status}}' openvpn- Health del host (túnel + puerto):
make healthTip: si tu interfaz VPN no es
tun0, puedes definirVPN_INTERFACEen tu.env(ej:VPN_INTERFACE=tun1).
Este stack corre un cliente OpenVPN persistente independiente del servidor.
- Preparar configuración cliente:
cp ovpn-s2s-data/client.conf.example ovpn-s2s-data/client.conf-
Copiar credenciales cliente (
ca.crt,*.crt,*.key,ta.key) dentro deovpn-s2s-data/. -
Ajustar
ovpn-s2s-data/client.conf(remote, rutas, archivos). -
Levantar cliente S2S:
docker compose -f docker-compose.s2s.yml up -dComandos útiles:
docker compose -f docker-compose.s2s.yml ps
docker compose -f docker-compose.s2s.yml logs -f --tail=100 openvpn-s2s
make s2s-up
make s2s-statusNotas:
- No reutilizar PKI del servidor para el cliente S2S.
- Este modo está pensado para despliegue portable (por ejemplo, descargar en Valpo2 y levantar solo
openvpn-s2s).
Hay 2 formas: manual o usando el script.
El script valida el nombre, crea/revoca/lista clientes y exporta perfiles a ./clients/<cliente>.ovpn.
Menú interactivo:
./scripts/ovpn.shEjemplos no interactivos (útil para Make):
./scripts/ovpn.sh create-export lechuga
./scripts/ovpn.sh create lechuga --pass
./scripts/ovpn.sh export lechuga --out ./clients/lechuga.ovpn
./scripts/ovpn.sh package lechuga
./scripts/ovpn.sh list
./scripts/ovpn.sh show lechuga
./scripts/ovpn.sh revoke lechugaNotas de seguridad / overwrite:
exportyqrno sobrescriben archivos existentes a menos que uses--force.- Si usas
--out, la ruta debe quedar dentro de./clients(el script rechaza rutas absolutas o con..). - Si usas
--out,exportexige que termine en.ovpn. --forcesignifica "sobrescribir archivos de salida" (no recrea/rota credenciales).- Para rotar credenciales:
revoke --remove+create-export.
Ejemplos con --force:
./scripts/ovpn.sh export lechuga --out ./clients/lechuga.ovpn --force
./scripts/ovpn.sh package lechuga --forceEjemplos usando Make (equivalentes):
make client-export name=lechuga force=1
make client-export name=lechuga out=./clients/lechuga.ovpn force=1
make client-create name=lechuga-pc ip=auto owner=lechuga device=pc
make client-create-export name=lechuga-note ip=auto owner=lechuga device=notebook
make client-package name=lechuga pass=1 force=1Nota: export/package fallan con un mensaje claro si el CN no existe o está revocado.
El proyecto puede llevar un registro de IPs fijas en ovpn-data/ip-assignments.json.
Comandos utiles:
./scripts/ovpn.sh ip-list
./scripts/ovpn.sh ip-assign lechuga-pc --vpn-ip auto --owner lechuga --device pc
./scripts/ovpn.sh create-export lechuga-note --vpn-ip auto --owner lechuga --device notebookEquivalentes en make:
make client-ip-list
make client-ip-assign name=lechuga-pc ip=auto owner=lechuga device=pc
make client-create-export name=lechuga-note ip=auto owner=lechuga device=notebookNotas:
--vpn-ip autotoma la siguiente IP libre del rango definido en el JSON.- La IP fija se escribe en
ovpn-data/ccd/<cliente>. - El JSON sirve como inventario de asignaciones por perfil/dispositivo.
Sobre package: genera un .zip con el perfil .ovpn, metadata.json y hashes (SHA256SUMS + hash del zip).
Cliente sin password:
./scripts/ovpn.sh create-export lechugaCliente con password (interactivo):
./scripts/ovpn.sh create-export lechuga --passdocker compose run --rm openvpn easyrsa build-client-full lechuga nopass
docker compose run --rm openvpn ovpn_getclient lechuga > lechuga.ovpnLuego copia el archivo .ovpn al PC/dispositivo del usuario y conéctate usando un cliente OpenVPN.
Los backups guardan ./ovpn-data (incluye PKI/CA), así que trátalos como secretos.
Menú interactivo:
./scripts/backup.shEjemplos no interactivos (útil para Make):
./scripts/backup.sh create --name pre-upgrade
./scripts/backup.sh list
./scripts/backup.sh verify ./backups/openvpn-YYYYmmdd-HHMMSS.tar.gz
./scripts/backup.sh restore ./backups/openvpn-YYYYmmdd-HHMMSS.tar.gz- Si
./ovpn-data/pkino existe, primero debes correr la inicialización (sección “Inicializar configuración”). - Para que los clientes accedan a tu LAN, además de empujar la ruta, el host debe permitir forwarding y (según tu caso) NAT/reglas firewall. Esto depende de tu distro/topología.
Para entender y operar las reglas del host (VPNSITE_FORWARD, puertos permitidos, DROP final tun->tun, persistencia y troubleshooting), revisa: