Deploying OpenConnect SSL VPN Server (ocserv) in Docker for Internal Projects

Welcome! This guide details the deployment of a VPN server based on OpenConnect (ocserv), which operates over HTTPS and is compatible with Cisco AnyConnect clients. We will encapsulate the server in a Docker container for simplicity, portability, and ease of management.

This VPN server serves as a central connectivity hub for networking future projects and services. The deployment process is straightforward, requiring only a few commands.

Disclaimer: The information in this article is for educational purposes only. Any actions taken based on this content are at your own risk and responsibility.

Don’t be deterred by the article’s length—it’s detailed for completeness, but the core deployment is simple (see TL;DR). Client setup may take more time.

TL;DR

  • Update your Linux system.
  • Install Git and clone the repository.
  • Configure environment variables in .env (e.g., domain, port, email).
  • Build and run the Docker container using docker compose.
  • Create users with certificates.
  • Configure clients on various platforms.
  • Optionally set up auto-start, certificate renewal, 2FA, and more.

Table of Contents

Introduction

In the corporate world, Cisco AnyConnect is a proprietary VPN solution for secure point-to-point connections. As is common in IT, demand for closed-source products often leads to open-source alternatives—much like Linux itself.

OpenConnect is an open-source implementation compatible with Cisco AnyConnect. From Wikipedia:

OpenConnect is a free and open-source cross-platform multi-protocol virtual private network (VPN) client software which implements secure point-to-point connections.

The project includes the ocserv (OpenConnect server) for a full client-server VPN solution.

Key advantages:

  • Cross-Platform Support: Clients for Linux, Windows, macOS, Android, iOS, HarmonyOS.
  • Flexible Configuration: Per-user settings.
  • Encryption: SSL/TLS-based.
  • Ease of Use: Simple setup and operation.
  • Authentication Options: Multiple methods, including 2FA.

Preliminary Preparation

Deploy ocserv in a Docker container using Docker Compose. Prerequisites:

  • A Linux server (e.g., Debian 12) with Docker Engine installed and running.
  • Root privileges or Docker group membership.

Since ocserv runs over HTTPS with SSL, a valid domain name is recommended (not required). Configure an A-record DNS entry pointing to your server’s external IP via your domain provider. Example: vpn.development.hhf.technology.

Without a domain, use the server’s IP—clients will see untrusted source warnings.

Project Diagram

For visual reference, here’s the project architecture:

graph LR
    subgraph Devices
        direction TB
        Tablet_iOS[Tablet / iOS]
        Laptop_Linux[Laptop / Linux]
        Desktop_Windows[Desktop / Windows]
        Smartphone_Android[Smartphone / Android]
    end

    Devices --> Internet((INTERNET))

    subgraph Additional_Devices
        direction TB
        Tablet[Tablet]
        Laptop[Laptop]
        Desktop[Desktop]
        Smartphone[Smartphone]
    end

    Additional_Devices --> Internet

    Internet -->|OpenConnect VPN tunnel| Linux_Server

    subgraph Linux_Server[Linux server]
        direction TB
        Docker_Engine[Docker Engine]
        subgraph Docker_Engine
            OpenConnect_Container[OpenConnect container]
        end
        OpenConnect_Container --> development_hhf_technology[development.hhf.technology]
    end

Deploying OpenConnect VPN Server in Docker

Tested on:

  • Host: Debian 12
  • Docker Base Image: Debian sid
  • ocserv Version: 1.3

The project automates setup using Docker, Docker Compose, and Bash scripts:

  • Builds a Docker image with ocserv and utilities.
  • Uses ocserv.sh for initialization (creates configs, scripts, certificates).
  • Persists data in ./data volume.
  • Portable: Transfer ./data to other Docker hosts.
  • Customizable configs.
  • Environment variables from .env (defaults if unset).

System Update

Update the system:

sudo -s
apt update && apt upgrade -y

Clone Repository with Project Source Files

Install Git if needed:

apt install git

Clone and setup:

git clone https://github.com/hhftechnology/hhf-vpn-deployment /tmp/hhf-vpn-deployment
cp -rv /tmp/hhf-vpn-deployment/src/server/v1.3-sid /opt/hhf-vpn-deployment && rm -rf /tmp/hhf-vpn-deployment
cd /opt/hhf-vpn-deployment

Project structure:

  • .env: Environment variables.
  • docker-compose.yml: Defines services (openconnect, certbot).
  • Dockerfile: Image build instructions.
  • ocserv.sh: Initialization script.
  • openconnect.service: systemd unit.
  • ssl_update.sh: SSL renewal script.

Overriding Variables: Domain, Server Name, Email, etc.

Edit .env:

vim .env

Variables:

  • TZ: Timezone (e.g., Europe/Moscow).
  • SRV_PORT: Connection port (default: 443; use 443 for Keenetic routers).
  • SRV_CN: Domain or arbitrary name (e.g., vpn.development.hhf.technology).
  • SRV_CA: CA name for self-signed certs (required).
  • USER_EMAIL: Email for Let’s Encrypt (required with domain).

Save with :wq.

Build Image and Run Container for the First Time Using Docker Compose

With domain:

docker compose up -d && docker compose logs -f

Notes:

  • Resource limits: 0.5 CPU, 200MB RAM (adjust in docker-compose.yml).
  • depends_on: Certbot runs first for SSL.
  • Interrupt logs with Ctrl+C.

Verify:

docker compose ps
ls -l ./data
ls -l ./data/ssl/live/vpn.development.hhf.technology/
ss -tnap | grep -E '43443'

Without domain: Follow similar steps, using IP instead.

Creating Users

ocserv supports various auth methods (login/password, LDAP, PAM, Radius, 2FA). This guide uses certificate-based auth (.p12 files) for security/convenience. Users still get passwords (randomly generated).

Create Users for Linux/Windows/Android

docker exec -it openconnect ocuser hhf 'HHF Technology'
  • Prompts for .p12 filename and password.
  • View users in ./data/ocpasswd.
  • Certificates in ./data/secrets.

Create Users for HarmonyOS/iOS

Use -A for compatible encryption:

docker exec -it openconnect ocuser -A steve 'Steve Jobs'

Without -A, iOS/HarmonyOS imports may fail (e.g., AES-256 unsupported on iOS AnyConnect).

Download .p12 File

Copy to host:

cp ./data/secrets/hhf.p12 /tmp

From client:

scp vpn.development.hhf.technology:/tmp/hhf.p12 .

Optional: ocserv Settings for Each User

Create per-user configs:

mkdir ./data/config-per-user
vim ./data/config-per-user/hhf

Example:

explicit-ipv4 = 10.10.10.5
route = 10.10.10.50/32
route = 64.233.164.139/32
dns = 1.1.1.1

Reload:

docker exec -it openconnect occtl reload

For privacy, consider a custom DNS (e.g., Unbound + Pi-hole).

Autostart OpenConnect Server with systemd

Copy unit file:

cp ./openconnect.service /etc/systemd/system/
systemctl daemon-reload
docker compose down

Enable/start:

systemctl enable --now openconnect
systemctl status openconnect
docker compose ps

Setting Up Automatic Renewal of SSL Certificates (If Using a Domain)

Let’s Encrypt certs expire in 3 months. Schedule renewal:

{ crontab -l; echo "0 3 * * 0 /opt/openconnect/ssl_update.sh"; } | crontab -
crontab -l

Test:

/opt/openconnect/ssl_update.sh
ls -l ./data/ssl/live/vpn.development.hhf.technology/

Uses --keep-until-expiring to avoid rate limits.

Configure Client Connectivity

Configuring OpenConnect Client for Linux

Method #1: Network Connection Manager

Preferred for desktops (avoids DNS conflicts). Install plugin (Debian/Ubuntu):

sudo apt update && sudo apt install network-manager-openconnect-gnome
  • Add connection: Select “Cisco AnyConnect or openconnect”.
  • Gateway: vpn.development.hhf.technology:43443.
  • User certificate: Path to .p12 (drag-and-drop if needed).
  • Connect and enter password.

Alternatively via nmcli:

nmcli connection add type vpn con-name "vpn.development.hhf.technology" ifname '*' vpn-type openconnect vpn.data "gateway=vpn.development.hhf.technology:43443, usercert=/home/hhf/hhf.p12"
nmcli connection up vpn.development.hhf.technology

Verify: ip -c address, nmcli, curl ``ifconfig.me.

Method #2: Command Line Utility – openconnect

For servers/GUI-less:

sudo apt update && sudo apt install openconnect

Connect:

# With domain
sudo openconnect -c /home/hhf/hhf.p12 vpn.development.hhf.technology:43443 <<< $(echo "password"$'\n')

# Without domain (confirm self-signed cert)
sudo openconnect -c /home/hhf/hhf.p12 12.345.67.89:43443 <<< $(echo "password"$'\n'yes$'\n')

For full tunneling on servers, add routes:

ip rule add table 128 from <public-ip>
ip route add table 128 to <public_ip_subnet> dev <interface_name>
ip route add table 128 default via <gateway>

Automate with a Bash script (see related article).

Configuring OpenConnect Client for Windows/MacOS

Download GUI client: Official Site or GitLab Releases.

Install on Windows (standard wizard). For macOS, follow platform-specific instructions.

Configure:

  • Gateway: vpn.development.hhf.technology:43443.
  • Certificate: .p12 path.
  • Disable UDP (server config disables it).

Verify network params.

Configuring OpenConnect Client for Android/HarmonyOS

Install Cisco Secure Client-AnyConnect (closed-source; trust required). Alternative open-source client is outdated.

  • Create connection.
  • Gateway: vpn.development.hhf.technology:43443.
  • Import .p12 certificate.

Follow app screenshots for steps.

Configuring OpenConnect Client for iOS

  • Download .p12 to device.
  • Open in Files, share to AnyConnect.
  • Enter password and import.
  • Select certificate in tunnel settings.

Configuring OpenConnect Client for OpenWrt

Detailed in separate article: Connecting OpenWrt to OpenConnect Server.

Blocking Users by Revoking Certificates

Revoke:

docker exec -it openconnect ocrevoke hhf
docker exec -it openconnect ocrevoke RELOAD

Reset all:

docker exec -it openconnect ocrevoke RESET

For older setups: Manually edit ocserv.conf, append revoked certs to revoked.pem, generate CRL, reload.

Authorization by Login/Password

Enable in ocserv.conf:

enable-auth = "plain[passwd=/etc/ocserv/ocpasswd]"

Restart:

docker compose restart openconnect

Create user:

docker exec -it openconnect ocpasswd exampleuser

Certificate auth remains enabled. Use strong passwords.

Setting Up Two-Factor Authentication (2FA/TOTP) with ocpasswd and PAM

Supports HOTP/TOTP; uses TOTP here.

Cannot combine password methods.

Edit .env:

OTP_ENABLE="true"

Edit ocserv.conf (choose one):

  • ocpasswd: auth = "plain[passwd=/etc/ocserv/ocpasswd,otp=/etc/ocserv/secrets/users.oath]"
  • PAM: auth = "pam" (mount /etc/passwd, /etc/group, /etc/shadow in docker-compose.yml).

Restart:

systemctl restart openconnect

Configure user:

docker exec -it openconnect ocuser2fa hhf

Outputs secret, base32, QR code (saved as PNG). Use authenticator apps (e.g., FreeOTP).

Generate tokens manually:

apt install -y oathtool
oathtool --base32 --totp=SHA1 --time-step-size=30 --digits=6 <totp_base32_secret>

Tokens valid for 10 minutes.

(Optional) Sending TOTP Secrets + QR Code and One-Time Passwords via Email and Telegram

Implemented in container.

Email: Uses msmtp. Set in .env:

MSMTP_HOST="smtp.example.com"
MSMTP_PORT="465"
MSMTP_USER="email@example.com"
MSMTP_PASSWORD="supersecretpassword"
MSMTP_FROM="email@example.com"
OTP_SEND_BY_EMAIL="true"

Username must be email format. Use app passwords.

Telegram: Uses curl to bot API. Set in .env:

TG_TOKEN="1234567890:QWERTYqwerty-QWERTY123_QwErTy123qWeRtY123"
OTP_SEND_BY_TELEGRAM="true"
  • User must message bot within 24h.
  • Username matches Telegram nickname.

Restart service. Test with ocuser2fa.

Protection from Automated Access – Camouflage

From ocserv >=1.23. Add to ocserv.conf:

camouflage = true
camouflage_secret = "secretword"
#camouflage_realm = "My admin panel"  # Fake login page

Restart. Connect with: https://vpn.development.hhf.technology:43443/?secretword.

Base URL returns 404 or fake login.

Useful Commands: systemctl/docker/docker-compose/ocserv

  • systemd: systemctl {stop|start|restart} openconnect
  • Docker images: docker image ls
  • Containers: docker container ls -a
  • Prune: docker system prune -af
  • Compose status: docker compose ps
  • Up: docker compose up -d
  • Down: docker compose down
  • Logs: docker compose logs -f
  • Restart container: docker compose restart openconnect
  • Exec bash: docker exec -it openconnect bash
  • occtl help: docker exec -it openconnect occtl --help
  • Reload config: docker exec -it openconnect occtl reload
  • Status: docker exec -it openconnect occtl show status
  • Users: docker exec -it openconnect occtl show users
  • Sessions: docker exec -it openconnect occtl show sessions all
  • Create user: docker exec -it openconnect ocuser hhf 'HHF Technology'

Conclusion

This guide covered deploying and configuring an ocserv VPN in Docker.