How to Install OpenConnect Server (ocserv) on Ubuntu 24.04 VPS: Cisco AnyConnect-Compatible SSL VPN
Running your own SSL VPN on port 443 is the most reliable way to stay connected from hotel Wi-Fi, conference networks, airport lounges, and restrictive corporate firewalls. This guide walks you through installing OpenConnect Server (ocserv) on an Ubuntu 24.04 VPS from scratch: fresh install, Let's Encrypt certificate, TCP and UDP on port 443, DTLS acceleration, TOTP-based two-factor authentication, split-tunnel and full-tunnel routing, kernel forwarding, nftables NAT, and client connection tests with both the open-source OpenConnect client and the official Cisco Secure Client (AnyConnect).
Looking for an easy starting point? A CloudCore Starter VPS is more than enough horsepower to run ocserv for a small team, and you get a clean static IP that you fully control.
Table of Contents
What is OpenConnect / ocserv?
ocserv is the OpenConnect VPN server: a free, GPL-licensed SSL/TLS VPN server originally written as a compatible reimplementation of the Cisco AnyConnect protocol. It speaks the same HTTPS-over-TLS handshake and DTLS data channel that Cisco's commercial AnyConnect Secure Mobility Client uses, which means you can connect to it from any official Cisco client or from the open-source OpenConnect client on practically every desktop and mobile platform.
Under the hood, ocserv listens on a single TCP port (usually 443) and negotiates the control channel over HTTPS. Once the session is established, it opens a parallel UDP channel (also on 443 in this guide) using DTLS for the bulk data path. DTLS avoids the TCP-over-TCP "meltdown" problem where a VPN tunnel stalls because both the outer and inner protocols retransmit simultaneously. If UDP is blocked, ocserv transparently falls back to TLS over TCP so the tunnel still works, just with lower peak throughput.
Typical deployments include: remote-worker access to an internal subnet, secure browsing from hostile Wi-Fi networks, bypassing captive portals and restrictive firewalls that whitelist only HTTPS, provisioning managed-device VPN profiles for iPhones and Androids using the ubiquitous AnyConnect client, and acting as a drop-in open-source replacement for legacy Cisco ASA head-ends.
Why Self-Host a Port 443 SSL VPN?
Many VPN protocols work great on a home network but fall apart the moment you step into a hotel, coworking space, airport lounge, client site, or country with aggressive traffic filtering. Running ocserv on TCP 443 solves this:
- Port 443 is never blocked. If HTTPS were blocked, the network would be useless. ocserv rides the exact same TLS envelope that every website on the planet uses, so deep packet inspection sees what looks like normal encrypted web traffic.
- True AnyConnect client compatibility. Cisco Secure Client is pre-approved by corporate IT departments, notarized by Apple, signed for Windows, and available in both the App Store and Google Play. You can onboard users without installing anything "weird."
- DTLS performance, TLS reliability. You get the throughput of UDP-based VPNs when the network allows it, and graceful fallback to TCP when it doesn't.
- Built-in user management and 2FA. ocserv ships with
ocpasswd, supports PAM, RADIUS, LDAP, and certificates out of the box, and natively integrates OATH/TOTP so you can require Google Authenticator for every login. - Fine-grained routing. Push specific routes, DNS servers, and split-DNS domains per user or group. Force full-tunnel for some users, split-tunnel for others, and use the same server for both.
- Full control of exit IP. A self-hosted VPN gives you a stable, dedicated static IP that you can whitelist in internal dashboards, banking portals, cloud consoles, and geofenced services.
Why a dedicated VPS beats a "free" VPN
| Factor | Commercial VPN | Self-hosted ocserv on VPS |
|---|---|---|
| Port 443 availability | Varies by provider | Always available |
| AnyConnect compatibility | Almost never | Yes (native) |
| Logs policy | "No-log" (trust us) | You own the logs |
| Static, dedicated IP | Usually extra cost | Included with VPS |
| Custom routes / split-DNS | Rarely configurable | Fully configurable |
| Typical cost for 1 team | EUR 8-15/user/month | EUR 7.99/month flat |
Prerequisites
Before starting, make sure you have:
- A VPS running Ubuntu 24.04 LTS with root or sudo access.
- A public IPv4 address that is not already serving HTTPS on port 443 (ocserv will take that port).
- A domain or subdomain pointing to the VPS public IP (for example,
vpn.example.com). An A record pointing to your VPS is sufficient. - DNS propagated before you run certbot. Verify with
dig +short vpn.example.com. - SSH access to the server.
- Ports 443/tcp and 443/udp reachable from the internet (open them in the provider's firewall panel if applicable).
Recommended Plan: CloudCore Starter>
For a VPN serving a small team (up to roughly 30 concurrent users), we recommend the CloudCore Starter plan:>
- 2 vCPU cores
- 4 GB RAM
- 50 GB NVMe SSD
- Unmetered bandwidth
- Static, dedicated IPv4>
ocserv is very lightweight and spends most of its CPU time on TLS and DTLS crypto, so even the smallest plan handles dozens of concurrent users comfortably. If you expect more than 100 simultaneous clients, step up one tier for additional vCPUs.
Connect to the VPS:
ssh root@your-server-ipStep 1: Update Ubuntu and Harden the Server
Start with a clean, patched system:
sudo apt update && sudo apt upgrade -y
sudo apt install -y curl ca-certificates ufw gnupg lsb-releaseSet a proper hostname so certificate issuance and logs make sense:
sudo hostnamectl set-hostname vpn.example.comIf the kernel was updated, reboot:
sudo rebootReconnect via SSH after a minute.
Step 2: Install ocserv
ocserv is packaged directly in the Ubuntu 24.04 repositories, so you do not need a third-party PPA.
sudo apt install -y ocserv gnutls-bin iptablesThis installs:
ocserv-- the server binary and default systemd unit.gnutls-bin-- providescerttoolfor generating test certificates andocpasswdfor user management.iptables-- used as a fallback for some ocserv scripts even when you run nftables as your primary firewall.
ocserv --version
systemctl list-unit-files | grep ocservExpected output (abbreviated):
OpenConnect VPN Server 1.3.0
ocserv.service enabled enabled
Do not start the service yet -- the default configuration ships with placeholder certificates and will fail to authenticate real clients. We will configure everything before the first start.
Step 3: Obtain a Let's Encrypt Certificate
ocserv runs fine with self-signed certificates for lab use, but self-signed certs trigger scary warnings in the Cisco AnyConnect client and flat-out refuse to connect on strict mobile clients. A free Let's Encrypt certificate removes that friction.
Install certbot:
sudo apt install -y certbotBecause certbot's standalone mode needs to bind port 80 briefly, and ocserv is not running yet, you can safely request the certificate right now:
sudo certbot certonly --standalone \
--preferred-challenges http \
--agree-tos \
--email [email protected] \
-d vpn.example.comExpected output:
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/vpn.example.com/fullchain.pem
Key is saved at: /etc/letsencrypt/live/vpn.example.com/privkey.pem
This certificate expires on 2026-07-15.Automatic renewal hook
Let's Encrypt certificates last 90 days. Configure a renewal hook so ocserv picks up the new certificate automatically:
sudo mkdir -p /etc/letsencrypt/renewal-hooks/deploy
sudo tee /etc/letsencrypt/renewal-hooks/deploy/ocserv.sh > /dev/null <<'EOF'
#!/bin/sh
systemctl restart ocserv
EOF
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/ocserv.shCertbot already installs a systemd timer (certbot.timer) that runs twice daily. Verify it is enabled:
systemctl list-timers | grep certbotStep 4: Configure /etc/ocserv/ocserv.conf
The default config at /etc/ocserv/ocserv.conf is heavily commented and over 700 lines long. Rather than edit in place, make a backup and write a clean, production-ready configuration.
sudo cp /etc/ocserv/ocserv.conf /etc/ocserv/ocserv.conf.orig
sudo tee /etc/ocserv/ocserv.conf > /dev/null <<'EOF'
--- ocserv 1.3.x production config ---
Authentication: local password file created with ocpasswd
auth = "plain[passwd=/etc/ocserv/ocpasswd]"Listen on port 443 for both the TLS control channel (TCP)
and the DTLS data channel (UDP). Clients fall back to TCP
automatically if UDP is filtered by the network.
tcp-port = 443
udp-port = 443TLS certificate (Let's Encrypt)
server-cert = /etc/letsencrypt/live/vpn.example.com/fullchain.pem
server-key = /etc/letsencrypt/live/vpn.example.com/privkey.pemConnection limits
max-clients = 128
max-same-clients = 4
keepalive = 32400
dpd = 90
mobile-dpd = 1800Run as an unprivileged user after binding port 443
run-as-user = nobody
run-as-group = daemonProcess isolation
isolate-workers = trueSocket for the occtl management tool
socket-file = /run/ocserv.sockPID
pid-file = /run/ocserv.pidCookie & auth lifetimes
auth-timeout = 240
cookie-timeout = 300
rekey-time = 172800
rekey-method = sslCompression off (security best practice, CRIME attack mitigation)
compression = falseTLS hardening (modern profile, no TLS 1.0/1.1, no SSLv3)
tls-priorities = "NORMAL:%SERVER_PRECEDENCE:%COMPAT:-VERS-SSL3.0:-VERS-TLS1.0:-VERS-TLS1.1:-ARCFOUR-128"MTU — 1400 is a safe default over most internet paths.
Clients fragment above this and you avoid path MTU discovery black holes.
try-mtu-discovery = true
default-mtu = 1400Hostname advertised to clients
default-domain = vpn.example.comIPv4 pool handed out to clients (must not collide with your LAN)
ipv4-network = 10.10.10.0
ipv4-netmask = 255.255.255.0DNS pushed to clients (Cloudflare + Quad9)
dns = 1.1.1.1
dns = 9.9.9.9Uncomment to force full-tunnel (all traffic via VPN).
Commented = split-tunnel; add explicit routes below.
route = default
Example split-tunnel routes (clients only send these through the VPN):
route = 10.0.0.0/8
route = 172.16.0.0/12
route = 192.168.0.0/16Do not route these subnets through the VPN (e.g. client's LAN)
no-route = 192.168.1.0/24Ban brute-force attackers
max-ban-score = 80
ban-points-wrong-password = 10
ban-points-connection = 1
ban-points-kkdcp = 1
min-reauth-time = 300
ban-reset-time = 1200Client stats & logging (disable to reduce disk I/O if not needed)
stats-report-time = 360
log-level = 1Optional: per-user and per-group config
config-per-user = /etc/ocserv/config-per-user/
config-per-group = /etc/ocserv/config-per-group/
EOFReplace vpn.example.com with your actual hostname. The important pieces:
tcp-port = 443+udp-port = 443-- ocserv binds TLS on TCP 443 and DTLS on UDP 443. This is what makes the VPN survive restrictive firewalls.server-cert/server-key-- paths to the Let's Encrypt files. Do not symlink; point directly.ipv4-network/ipv4-netmask-- the virtual IP pool handed out to clients. Pick something that does not overlap any LAN you plan to connect from.route-- each line is a subnet pushed to the client's routing table. Without anyroutelines the client gets no routes at all. Withroute = defaultit becomes a full-tunnel VPN.default-mtu = 1400-- starting MTU. ocserv also negotiates path MTU dynamically whentry-mtu-discovery = true.tls-priorities-- explicitly disables TLS 1.0/1.1 and RC4. This matches Mozilla's modern profile.
sudo mkdir -p /etc/ocserv/config-per-user /etc/ocserv/config-per-groupStep 5: Enable IP Forwarding and NAT with nftables
Without IP forwarding and NAT, your VPN clients will connect but have no internet access.
Enable kernel IP forwarding
echo 'net.ipv4.ip_forward = 1' | sudo tee /etc/sysctl.d/99-ocserv.conf
echo 'net.ipv6.conf.all.forwarding = 1' | sudo tee -a /etc/sysctl.d/99-ocserv.conf
sudo sysctl --systemConfirm:
sysctl net.ipv4.ip_forwardExpected output:
net.ipv4.ip_forward = 1Configure nftables for NAT and firewall
Ubuntu 24.04 ships with nftables by default. Identify your primary outbound interface:
ip -br route | grep defaultExpected output (the interface name after dev):
default via 203.0.113.1 dev eth0 proto staticNote eth0 (or ens3, enp0s3, etc.) for your system. Write the nftables configuration:
sudo tee /etc/nftables.conf > /dev/null <<'EOF' #!/usr/sbin/nft -f flush rulesettable inet filter { chain input { type filter hook input priority 0; policy drop;
ct state established,related accept iif "lo" accept
# SSH (change the port if you moved SSH) tcp dport 22 accept
# ocserv on 443 — both TCP (TLS) and UDP (DTLS) tcp dport 443 accept udp dport 443 accept
# ICMP / ping icmp type echo-request accept icmpv6 type { echo-request, nd-neighbor-solicit, nd-router-advert, nd-neighbor-advert } accept }
chain forward { type filter hook forward priority 0; policy drop;
ct state established,related accept
# Allow VPN clients out iifname "vpns+" accept oifname "vpns+" ct state new accept }
chain output { type filter hook output priority 0; policy accept; } }
table ip nat { chain postrouting { type nat hook postrouting priority 100;
# NAT the VPN pool behind the server's public IP. # Change eth0 to match your primary interface. ip saddr 10.10.10.0/24 oifname "eth0" masquerade } } EOF
Replace eth0 if your interface is named differently. Then apply and enable:
sudo nft -c -f /etc/nftables.conf # syntax check
sudo systemctl enable --now nftables
sudo systemctl restart nftablesVerify:
sudo nft list ruleset | head -40ocserv creates virtual interfaces named vpns0, vpns1, etc. -- the vpns+ wildcard in the forward chain matches all of them.
Step 6: Create VPN Users
With auth = "plain[passwd=/etc/ocserv/ocpasswd]" in the config, user accounts live in a flat file managed by the ocpasswd utility.
Create the password file and add a user:
sudo touch /etc/ocserv/ocpasswd
sudo chmod 600 /etc/ocserv/ocpasswd
sudo ocpasswd -c /etc/ocserv/ocpasswd aliceYou will be prompted for a password twice. To add a second user, omit the -c flag (which means "create new file"):
sudo ocpasswd /etc/ocserv/ocpasswd bobTo list users:
sudo cat /etc/ocserv/ocpasswdExpected output:
alice:*:$5$rounds=...
bob:*:$5$rounds=...To disable a user without deleting them:
sudo ocpasswd -l /etc/ocserv/ocpasswd alice # lock
sudo ocpasswd -u /etc/ocserv/ocpasswd alice # unlockTo delete:
sudo ocpasswd -d /etc/ocserv/ocpasswd alicePer-user configuration
You can override any config setting per user. For example, to force full-tunnel only for alice while other users keep split-tunnel, create:
sudo tee /etc/ocserv/config-per-user/alice > /dev/null <<'EOF'
route = default
dns = 1.1.1.1
EOFStep 7: Start and Verify ocserv
Test the configuration first:
sudo ocserv -f -c /etc/ocserv/ocserv.confThe -f flag runs in the foreground so you can see startup errors. If it prints listening on ... without errors, press Ctrl+C and start it as a service:
sudo systemctl enable --now ocserv
sudo systemctl status ocservExpected output:
● ocserv.service - OpenConnect SSL VPN server
Loaded: loaded (/lib/systemd/system/ocserv.service; enabled; preset: enabled)
Active: active (running) since Wed 2026-04-16 10:00:00 UTC; 5s ago
Main PID: 12345 (ocserv-main)
Tasks: 2
Memory: 8.2MCheck that the ports are listening:
sudo ss -tulpn | grep 443Expected output:
tcp LISTEN 0 128 0.0.0.0:443 0.0.0.0:* users:(("ocserv-main",pid=12345))
udp UNCONN 0 0 0.0.0.0:443 0.0.0.0:* users:(("ocserv-main",pid=12345))Both TCP and UDP on 443 are correct. The management tool occtl gives you real-time insight:
sudo occtl show status
sudo occtl show users
sudo occtl show ip bansStep 8: Connect from an OpenConnect Client
On any Linux, macOS, or Windows machine, install the OpenConnect client:
Ubuntu / Debian:
sudo apt install -y openconnectmacOS (Homebrew):
brew install openconnectWindows: Download the openconnect-gui installer.
Connect from the command line:
sudo openconnect --protocol=anyconnect --user=alice vpn.example.comThe client negotiates TLS, you enter Alice's password, it switches to DTLS over UDP if reachable, and assigns a virtual IP from the pool.
Expected output (abbreviated):
POST https://vpn.example.com/
Connected to HTTPS on vpn.example.com with ciphersuite (TLS1.3)-(ECDHE-SECP256R1)-(CHACHA20-POLY1305)
XML POST enabled
Please enter your username and password.
Username: alice
Password:
Got CONNECT response: HTTP/1.1 200 CONNECTED
CSTP connected. DPD 90, Keepalive 32400
Connected tun0 as 10.10.10.2, using SSL + DTLSv1.2Verify you have a VPN-assigned address and that the server is reachable from inside the tunnel:
ip addr show tun0
ping -c 3 10.10.10.1
curl ifconfig.mecurl ifconfig.me returns the VPS's public IP when full-tunnel is on, or your own ISP's IP in split-tunnel mode.
GUI client (NetworkManager)
On GNOME, install the plugin:
sudo apt install -y network-manager-openconnect-gnomeAdd a connection in Settings -> Network -> VPN -> + and pick Cisco AnyConnect Compatible VPN (openconnect). Fill in:
- Gateway:
vpn.example.com - CA Certificate: leave blank (Let's Encrypt is in the system trust store)
Step 9: Connect from Cisco AnyConnect
This is where ocserv really shines -- you get full compatibility with the official Cisco Secure Client (AnyConnect) on every OS Cisco supports.
vpn.example.comocpasswd (for example, alice).utun (macOS/iOS) or Ethernet (Windows) virtual adapter.Because you issued a real Let's Encrypt certificate in Step 3, AnyConnect trusts the server without any manual CA import or security override. This is critical for iOS and Android, which refuse to save VPN profiles with self-signed certificates.
Pushing a preconfigured profile
For managed device fleets, you can pre-seed the connection by distributing a profile.xml through MDM (Jamf, Intune, Workspace ONE). Minimal example:
<?xml version="1.0" encoding="UTF-8"?>
<AnyConnectProfile xmlns="http://schemas.xmlsoap.org/encoding/">
<ServerList>
<HostEntry>
<HostName>Company VPN</HostName>
<HostAddress>vpn.example.com</HostAddress>
</HostEntry>
</ServerList>
</AnyConnectProfile>Step 10: Enable TOTP 2FA
Passwords alone are not enough for anything internet-facing. ocserv's plain backend supports TOTP via the gnutls-bin / oath-toolkit integration.
Install oath-toolkit:
sudo apt install -y oath-toolkit libpam-oathGenerate a TOTP secret for user Alice:
head -c 20 /dev/urandom | xxd -p -c 20Expected output (example):
9f1a2b3c4d5e6f708192a3b4c5d6e7f809112233Save that hex secret and convert it to Base32 (what Google Authenticator accepts):
echo -n "9f1a2b3c4d5e6f708192a3b4c5d6e7f809112233" | xxd -r -p | base32Add Alice to /etc/users.oath:
sudo tee -a /etc/users.oath > /dev/null <<'EOF'
HOTP/T30/6 alice - 9f1a2b3c4d5e6f708192a3b4c5d6e7f809112233
EOF
sudo chmod 600 /etc/users.oath
sudo chown root:root /etc/users.oathSwitch ocserv from plain passwd auth to stacked auth -- password and TOTP:
sudo sed -i 's|^auth = .*|auth = "plain[passwd=/etc/ocserv/ocpasswd,otp=/etc/users.oath]"|' /etc/ocserv/ocserv.conf
sudo systemctl restart ocservNow when Alice connects, the client prompts first for her password, then for the 6-digit code from her authenticator app. Add the Base32 secret to her Google Authenticator, Authy, or 1Password, and you have full 2FA.
Step 11: PAM, RADIUS, and LDAP Authentication
For larger deployments with existing identity, swap the plain backend for one of these.
PAM (use system Unix users)
auth = "pam"Add ocserv to PAM's config:
sudo tee /etc/pam.d/ocserv > /dev/null <<'EOF'
auth required pam_unix.so
account required pam_unix.so
EOFAdd system users with adduser alice --shell /usr/sbin/nologin.
RADIUS (FreeRADIUS, Duo Security, Microsoft NPS)
Install the ocserv RADIUS support:
sudo apt install -y ocserv-radiusIn ocserv.conf:
auth = "radius[config=/etc/radiusclient/radiusclient.conf,groupconfig=true]"
acct = "radius[config=/etc/radiusclient/radiusclient.conf]"Edit /etc/radiusclient/servers and /etc/radiusclient/radiusclient.conf to point at your RADIUS server. This is how you integrate with Duo Security, Microsoft NPS, Okta RADIUS, or JumpCloud.
LDAP / Active Directory (via SSSD or PAM)
Install and configure SSSD against Active Directory, then use auth = "pam". SSSD handles the LDAP or AD lookup transparently through PAM's pam_sss.so module. This is the cleanest approach for AD-joined servers.
You can stack multiple backends:
auth = "pam"
auth = "certificate"With both lines present, ocserv accepts either a valid password or a valid client certificate.
Step 12: Split-Tunnel vs Full-Tunnel
The difference comes down to one config directive.
Split-tunnel (default in our config)
Only the routes you list are pushed to clients; the rest of the client's traffic uses its normal internet connection.
# In ocserv.conf
route = 10.0.0.0/8
route = 172.16.0.0/12
route = 192.168.0.0/16
no-route = 192.168.1.0/24 # exclude client LANUse when: you only need clients to reach internal resources (databases, admin panels, monitoring) and want their general web traffic to stay local.
Full-tunnel
Every packet from the client flows through the VPN.
# In ocserv.conf
route = defaultUse when: you want to protect users on hostile Wi-Fi, enforce egress through a known static IP, or bypass ISP-level filtering.
You can mix modes: leave the global config in split-tunnel and override per user:
sudo tee /etc/ocserv/config-per-user/ceo > /dev/null <<'EOF'
route = default
EOFThe CEO now gets full-tunnel; everyone else stays split-tunnel.
DNS in split-tunnel: split-DNS
When split-tunneling, you usually want internal hostnames (e.g. intranet.example.com) resolved by the VPN's DNS and everything else by the client's normal DNS. Use split-dns:
split-dns = example.com
split-dns = internal.lanOnly queries for those domains go through the tunneled DNS; everything else uses the client's normal resolver.
Post-Install Tuning
MTU tuning
If users on certain networks report slow transfers or stalled downloads, lower the MTU:
default-mtu = 1360Mobile carriers (especially on IPv6 transition networks) often have effective MTUs below 1400. Start at 1400, drop by 40 at a time if needed.
DTLS tuning
DTLS dramatically improves throughput for large transfers. If clients keep falling back to TCP-only mode, double-check:
- UDP 443 is open on both the VPS firewall and the provider's network firewall.
udp-port = 443is present inocserv.conf.- The client's network isn't rate-limiting or reordering UDP (common on cheap hotel Wi-Fi).
Logging and monitoring
ocserv logs to the journal by default:
sudo journalctl -u ocserv -fTo see live session data:
sudo occtl show users
sudo occtl show sessions all
sudo occtl show iroutesTo disconnect an abusive user:
sudo occtl disconnect user aliceFail2ban
Add a basic fail2ban jail to block IPs hammering the login page:
sudo apt install -y fail2ban
sudo tee /etc/fail2ban/jail.d/ocserv.local > /dev/null <<'EOF'
[ocserv]
enabled = true
port = 443
filter = ocserv
logpath = /var/log/syslog
maxretry = 5
findtime = 600
bantime = 3600
EOFocserv already has built-in brute-force protection through its max-ban-score settings, but fail2ban operates at the firewall layer for defense in depth.
Troubleshooting
| Problem | Cause | Solution |
|---|---|---|
Bad file descriptor on startup | Certificate files unreadable by nobody | sudo chmod -R 755 /etc/letsencrypt/live /etc/letsencrypt/archive |
| Clients connect but have no internet | IP forwarding off or NAT missing | sudo sysctl net.ipv4.ip_forward must be 1; verify nft list table ip nat |
| AnyConnect on iOS rejects the server | Self-signed cert, or cert does not cover hostname | Use a real Let's Encrypt certificate; the hostname in the client must exactly match the certificate CN/SAN |
| Tunnel works but is very slow | Falling back to TCP because UDP 443 is blocked | Check with occtl show users — using: TLS means no DTLS; verify UDP 443 is open end to end |
error: Could not open tunnel device | tun module not loaded (rare on modern kernels) | sudo modprobe tun and add tun to /etc/modules |
| Connects, drops after a few minutes | DPD misconfig or NAT timeout on carrier | Lower dpd to 60 and mobile-dpd to 900 |
| MTU errors / partial page loads | Path MTU discovery broken | Set default-mtu = 1360 and restart ocserv |
Error: failed to verify peer on certbot renewal | Port 80 still bound by nftables drop | Allow 80/tcp temporarily or use DNS-01 challenge |
| TOTP always fails | System clock drift > 30s | Install and enable chrony or systemd-timesyncd |
Useful debug commands
sudo journalctl -u ocserv -n 100 --no-pager
sudo occtl show status
sudo occtl show users
sudo nft list ruleset
sudo ss -tulpn | grep 443FAQ
Is OpenConnect ocserv compatible with the official Cisco AnyConnect / Secure Client?
Yes. ocserv implements the AnyConnect protocol faithfully enough that the official Cisco Secure Client connects, authenticates, and passes traffic without any modification. This is useful in environments where only "approved" VPN clients are allowed on endpoints. You can also use the open-source OpenConnect client, NetworkManager's OpenConnect plugin, or the mobile OpenConnect apps on iOS and Android.
Why run the VPN on port 443 specifically?
Port 443 carries HTTPS, which is essentially impossible to block on a modern network -- blocking it would disable every website. Running ocserv on 443 means that from the network's perspective the VPN looks like normal encrypted web traffic. Hotel Wi-Fi, airport networks, corporate guest SSIDs, and most country-level firewalls all allow outbound 443, so your VPN works where OpenVPN on 1194 or WireGuard on 51820 would fail.
How is ocserv different from OpenVPN, WireGuard, or strongSwan?
OpenVPN is another SSL VPN but uses its own custom protocol on UDP 1194 by default. You can force it onto TCP 443, but that loses performance and still does not interoperate with AnyConnect clients.
WireGuard is a much newer, smaller, faster UDP-only protocol. It is excellent for point-to-point tunnels and personal use, but does not work over TCP, has no official corporate client, and is blocked on many restrictive networks that only allow 443.
strongSwan is an IPsec/IKEv2 implementation that integrates with the native VPN clients on iOS, macOS, and Windows. Great for always-on device VPNs, but IPsec uses UDP 500/4500 and ESP, which are frequently filtered on hostile networks.
ocserv is the right choice when AnyConnect compatibility, port-443 firewall traversal, or drop-in replacement of a Cisco ASA head-end is important.
Can I combine password auth with TOTP or certificates?
Yes. ocserv supports stacked authentication with auth = "plain[passwd=...,otp=...]" for password+TOTP, or multiple auth = lines to allow either a password or a client certificate. For enterprise SSO, point the radius backend at Duo, Okta RADIUS, or Microsoft NPS.
How many concurrent users can a small VPS handle?
ocserv is very efficient. A 2 vCPU / 4 GB VPS comfortably handles 30-50 active users with mixed traffic patterns; the bottleneck is usually TLS/DTLS crypto on the vCPUs and available bandwidth, not memory. For 100+ concurrent users, scale to 4 vCPUs or more.
Does ocserv support IPv6?
Yes. Add ipv6-network directives to hand out IPv6 addresses and ensure net.ipv6.conf.all.forwarding = 1. You will also need an ip6 nat table (or native IPv6 routing if your VPS has a /64 delegated).
Next Steps
You now have a production-ready SSL VPN running on port 443 with AnyConnect compatibility, a trusted Let's Encrypt certificate, TOTP 2FA, and NAT out through your VPS's static IP. From here you can:
- Add more users with
ocpasswdand distribute connection instructions via a password manager. - Harden SSH further and consider moving it off port 22.
- Layer on strongSwan for IPsec/IKEv2 if you also want native iOS and Windows VPN profiles.
- Compare with OpenVPN if you want an alternative SSL VPN that uses a custom protocol rather than AnyConnect's.
- Or use WireGuard in parallel for maximum speed on networks that allow UDP.
- Read the upstream ocserv documentation for advanced options like client certificate CA hierarchies, RADIUS accounting, group-based routing, and per-session scripting.
Skip the manual setup>
If you just want a clean VPS with a real static IPv4 to run ocserv on, deploy a CloudCore Starter from EUR 7.99/month. Two vCPUs, 4 GB RAM, 50 GB NVMe, unmetered bandwidth, and a dedicated IP -- enough to run this entire guide and still have room for a few more services on the same box.