Sign inSign up

azinchen/ocserv-server

By azinchen

β€’Updated about 12 hours ago

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

Image
0

10K+

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 β€” openconnect, Cisco AnyConnect, mobile apps, Keenetic, OpenWrt (details⁠)
  • πŸš€ Self-configuring networking β€” NAT, forwarding and MSS clamping set up automatically (details⁠)
  • πŸ•΅οΈ Camouflage mode β€” looks like a plain HTTPS site; only clients with the secret get in (details⁠)
  • πŸšͺ Gateway mode β€” route clients out through upstream VPN containers such as NordVPN (details⁠)
  • πŸ‘₯ Per-user routing β€” map each user to a different upstream exit, steered on connect (details⁠)
  • 🎯 Destination bypass β€” per-destination routing: direct, via a chosen exit, or blocked (details⁠)
  • πŸ“₯ Auto-fetched IP lists β€” country CIDR lists fetched on a schedule, swapped in atomically (details⁠)
  • πŸ›‘οΈ Fail-closed kill switch β€” if an upstream is down, clients lose internet rather than leak (details⁠)
  • πŸ”„ Hot-reload everything β€” user maps, pools, gateways and certs reload live, sessions kept (details⁠)
  • πŸ”’ Reverse-proxy & Let's Encrypt friendly β€” share SWAG's certs; renewals need no restart (details⁠)
  • πŸ“Š Session accounting β€” per-session times and traffic split per gateway and bypass pool (details⁠)
  • 🩺 Opt-in health monitor β€” HEALTHCHECK for liveness, routing integrity and egress probes (details⁠)
  • πŸ“΅ IPv6 without leaks β€” optional NAT66; dropped fail-closed when an upstream has no IPv6 (details⁠)
  • πŸ“¦ Multi-arch, from source β€” amd64 / arm64 / riscv64, 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
      - 443:443/udp
    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.
TTL_SET(unset)Rewrite the TTL/hop-limit of client traffic on egress (e.g. 64); hides every hop behind this server from client traceroutes.
⁠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:40f543ffe…

Size

13.2 MB

Last updated

about 12 hours ago

docker pull azinchen/ocserv-server