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
- Preliminary Preparation
- Project Diagram
- Deploying OpenConnect VPN Server in Docker
- Creating Users
- Autostart OpenConnect Server with systemd
- Setting Up Automatic Renewal of SSL Certificates (If Using a Domain)
- Configure Client Connectivity
- Blocking Users by Revoking Certificates
- Authorization by Login/Password
- Setting Up Two-Factor Authentication (2FA/TOTP) with ocpasswd and PAM
- (Optional) Sending TOTP Secrets + QR Code and One-Time Passwords via Email and Telegram
- Protection from Automated Access – Camouflage
- Useful Commands: systemctl/docker/docker-compose/ocserv
- Conclusion
- References
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
ocservand utilities. - Uses
ocserv.shfor initialization (creates configs, scripts, certificates). - Persists data in
./datavolume. - Portable: Transfer
./datato 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 indocker-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.