Sign inSign up

azinchen/ocserv-server

By azinchen

β€’Updated about 2 months ago

OpenConnect VPN server (ocserv) in a Docker container β€” automatic NAT/forwarding via nftables, wit

Image
0

8.1K

azinchen/ocserv-server repository overview

⁠OpenConnect VPN Server Docker Container

GitHub release GitHub release date GitHub build
GitHub stars GitHub forks Open issues GitHub last commit
Docker pulls Docker stars Docker image size
Multi-arch

OpenConnect VPN server (ocserv⁠) in a small self-configuring Docker container: it builds ocserv from source on Alpine, sets up NAT/forwarding automatically with nftables, and can disguise itself as an ordinary HTTPS website.

Chaining through a commercial VPN? The companion images azinchen/nordvpn⁠ (OpenVPN) and azinchen/nordvpn-wg⁠ (WireGuard) plug straight into this server's gateway mode β€” your clients connect to your OpenConnect server and exit with NordVPN's IP.

Need the client side in Docker too? azinchen/openconnect-client⁠ is the companion client: it connects to this server (camouflage supported, fail-closed kill switch included) and routes other containers (network_mode: service:vpn) or whole LAN hosts through the tunnel β€” including server-to-server cascades, where it feeds another ocserv node's gateway mode.

⁠✨ Key Features

  • πŸ” Broad client support β€” everything ocserv supports (the image runs it unmodified): the openconnect client, Cisco AnyConnect / Secure Client, mobile apps, and routers such as Keenetic / Netcraze, OpenWrt and GL.iNet (details⁠)
  • πŸš€ Self-configuring networking β€” NAT, forwarding and MSS clamping set up automatically with nftables; just mount a config and go (details⁠)
  • πŸ•΅οΈ Camouflage mode β€” to probes and DPI the server looks like an ordinary HTTPS website; only clients that know the secret reach the VPN (details⁠)
  • πŸšͺ Gateway mode β€” route clients out through upstream VPN containers (e.g. NordVPN) instead of the host's connection (details⁠)
  • πŸ‘₯ Per-user routing β€” map each user to a different upstream exit; sessions steered on connect, no static IPs needed (details⁠)
  • 🎯 Destination bypass β€” route by destination: country pools go direct, chosen services out a specific exit, ad/malware pools blocked (details⁠)
  • πŸ“₯ Auto-fetched IP lists β€” country CIDR lists downloaded on a schedule, validated, and swapped in atomically (details⁠)
  • πŸ›‘οΈ Fail-closed kill switch β€” nftables next-hop guards on every routed path: if an upstream is down, clients lose internet rather than leak (details⁠)
  • πŸ”„ Hot-reload everything β€” user maps, pool lists, DNS-named gateway addresses and TLS certificates all reload live, without dropping sessions (details⁠)
  • πŸ”’ Reverse-proxy & Let's Encrypt friendly β€” share SWAG's certificates; renewals are picked up without a restart (details⁠)
  • πŸ“Š Session accounting β€” session-report shows every session's connect/disconnect times and its traffic split per gateway and bypass pool (details⁠)
  • 🩺 Opt-in health monitor β€” Docker-native HEALTHCHECK: server liveness, routing integrity, and (optionally) real egress probes per gateway (details⁠)
  • πŸ“΅ IPv6 without leaks β€” optional NAT66; when an upstream has no IPv6, client IPv6 is rejected fail-closed instead of escaping (details⁠)
  • πŸ“¦ Multi-arch, from source β€” amd64 / arm64 / riscv64 images, ocserv built from source, supervised by s6-overlay

πŸ“– Full documentation on the Wiki⁠ β€” setup guides, ready-to-use configurations, feature guides, troubleshooting, FAQ, and architecture.


⁠Quick Start

# docker-compose.yml
services:
  ocserv:
    image: azinchen/ocserv-server:latest
    container_name: ocserv-server
    restart: unless-stopped
    cap_add:
      - NET_ADMIN
    devices:
      - /dev/net/tun:/dev/net/tun
    sysctls:
      - net.ipv4.ip_forward=1
    ports:
      - 443:443/tcp
    environment:
      - VPN_SUBNET=10.10.0.0/24
    volumes:
      - ./volumes/config:/etc/ocserv
docker compose up -d
# create a user
docker exec -it ocserv-server ocpasswd -c /etc/ocserv/ocpasswd alice
# connect
sudo openconnect https://vpn.example.com --user=alice

You provide an ocserv.conf and a certificate in the config volume β€” ready-to-use configurations are on the wiki: Basic⁠ Β· Self-Signed⁠ Β· SWAG / Let's Encrypt⁠. Start with Getting Started⁠.

⁠Requirements
SettingWhy
--cap-add=NET_ADMINconfigure interfaces, routes, nftables
--device /dev/net/tuncreate the tunnel device
--sysctl net.ipv4.ip_forward=1forward client traffic to the internet

⁠Common Setups

I want to…Guide
Run a plain standalone VPN serverGetting Started⁠ · Basic config⁠
Share port 443 with websites behind SWAGSWAG integration⁠
Hide the VPN from DPI / censorshipCamouflage Mode⁠
Send clients out through NordVPN (or another VPN container)Gateway Mode⁠
Give each user a different exit countryPer-user gateways⁠
Route by destination (country direct, streaming via US, ads blocked)Destination Bypass⁠
Connect phones, laptops, routersClients and Devices⁠
Route other containers or LAN hosts through this serveropenconnect-client⁠ (companion image)

For example, chaining every client out through a NordVPN container is just:

    environment:
      - VPN_SUBNET=10.20.0.0/24
      - VPN_GATEWAY=172.28.0.2        # the nordvpn container, kill switch included

⁠Environment Variables

Grouped by feature; every variable is one line here β€” the Configuration Reference⁠ has the full descriptions.

⁠Core networking
VariableDefaultDescription
VPN_SUBNET10.10.10.0/24VPN client subnet; must match ipv4-network in ocserv.conf.
WAN_IF(auto)NAT egress interface; auto-detected from the default route.
VPN_IFvpns+Tunnel device pattern; matches device = vpns in ocserv.conf.
MSS(unset)Clamp client TCP MSS (e.g. 1300) when the client path MTU is small and PMTUD is broken.
⁠IPv6
VariableDefaultDescription
IPV6_FORWARD1Enable IPv6 forwarding inside the container.
IPV6_NAT0Enable IPv6 masquerade (NAT66) for IPV6_SUBNET β€” see the wiki before turning on.
IPV6_SUBNETfda9:…::/64ULA subnet to masquerade when IPV6_NAT=1.
⁠Gateway mode β€” details⁠
VariableDefaultDescription
VPN_GATEWAY(unset)Default IPv4 egress for unmapped users: an upstream's IP/DNS name, direct (ISP), or block.
VPN_GATEWAY6(unset)Same for IPv6: IP/DNS name, direct, or block (default β€” no IPv6 leak).
VPN_GATEWAYS(unset)Named gateways for per-user routing, e.g. nl=172.28.0.2,us=172.28.0.4.
VPN_GATEWAYS6(unset)Optional IPv6 per gateway name, e.g. nl=fd00::2.
VPN_GATEWAYS_FILE(unset)Gateways in a file (name ipv4 [ipv6]; block blocks a family).
VPN_GATEWAYS_RESOLVE_INTERVAL0Re-resolve DNS-named gateways every N seconds; sessions survive address moves.
VPN_USER_GATEWAY(unset)Username β†’ gateway map, e.g. alice=nl,bob=us; direct sends a user out the ISP.
VPN_USER_GATEWAY_FILE(unset)User map in a file; hot-reload with vpngw-reload, live sessions re-steered in place.
VPN_USER_GATEWAY_WATCH01 = reload the user map automatically on every file change.
VPN_GATEWAY_TABLE100Routing table for gateway mode (advanced).
VPN_GATEWAY_RULE_PRIO1000Priority of the subnet policy rule (advanced).
VPN_GATEWAY_USER_RULE_PRIO900Priority of per-user policy rules (advanced).
⁠Destination bypass β€” details⁠
VariableDefaultDescription
VPN_BYPASS_POOLS_DIR/etc/ocserv/poolsDirectory of <pool>.list CIDR files (destination pools).
VPN_GATEWAY_BYPASS(unset)Pool(s) for unmapped users, e.g. ru (join with +).
VPN_GATEWAYS_BYPASS(unset)Pools per named gateway, e.g. nl=ru+ads β€” inherited by its users.
VPN_USER_BYPASS(unset)Pools per user (strongest); none opts a user out.
VPN_USER_BYPASS_FILE(unset)Per-user map in a file; hot-reload with vpngw-reload.
VPN_USER_BYPASS_WATCH01 = reload the bypass map automatically on every file change.
VPN_BYPASS_TARGETS(unset)Per-pool target: ru=direct,streaming=us,ads=block (default direct).
VPN_BYPASS_TARGETS_FILE(unset)File alternative (pool target lines); read at startup, file wins.
VPN_BYPASS_WATCH01 = reload a pool automatically when its list file changes.
VPN_BYPASS_SOURCES_FILE(unset)Download sources for the built-in list fetcher (pool url lines).
VPN_BYPASS_UPDATE_INTERVAL0Auto-fetch the lists every N seconds (e.g. 86400).
VPN_BYPASS_RULE_PRIO800Priority of the bypass policy rules (advanced).
VPN_BYPASS_MARK0xbcBase fwmark for bypassed traffic (advanced).
⁠Session accounting β€” details⁠
VariableDefaultDescription
SESSION_HISTORY_FILE(unset)Persist completed-session history on a volume (default: tmpfs, container lifetime).
⁠Health monitor β€” details⁠
VariableDefaultDescription
HEALTH_CHECK_ENABLEDfalsetrue = the Docker HEALTHCHECK probe checks server liveness + routing integrity.
HEALTH_CHECK_EGRESS(unset)Egress paths that also gate health: direct, gateway names, default, all (join with +).
HEALTH_CHECK_URLhttps://1.1.1.1/…URL(s) for the direct egress probe, ;-separated.
⁠Certificate hot-reload β€” details⁠
VariableDefaultDescription
CERT_WATCH01 = watch the cert/key files from ocserv.conf and reload ocserv on renewal, no restart.
CERT_WATCH_INTERVAL0Polling fallback every N seconds for filesystems without inotify (e.g. NFS).

⁠Build

docker build -t ocserv-server .

Base: Alpine Linux Β· Init: s6-overlay Β· VPN: ocserv (built from source) Β· Firewall: nftables

⁠Issues

If you have any problems with or questions about this image, please contact me through a GitHub issue⁠ or email⁠.

Check the Troubleshooting⁠ and FAQ⁠ wiki pages first β€” and attach the output of the built-in diagnostic to any report:

docker exec ocserv-server network-diagnostic

It prints server status, config sanity checks, certificate state (issuer + expiry), a camouflage self-test, gateway/bypass state with live egress probes (public IP through each gateway and bypass target), connected sessions with live traffic counters, routing and firewall state, and [ok]/[warn] verdicts (non-zero exit on warnings). --explain <user> <ip> tells you which path a destination takes; --json emits a machine-readable summary.

⁠License

MIT β€” see LICENSE⁠.

Tag summary

Content type

Image

Digest

sha256:006174a89…

Size

13.1 MB

Last updated

about 2 months ago

docker pull azinchen/ocserv-server