OCserv 是 OpenConnect 协议的参考服务器实现,而 OpenConnect 本身则是逆向工程、开放版本的 Cisco AnyConnect SSL VPN 替代品。因为客户端就是原生的 AnyConnect 或 OpenConnect 应用程序,你可以获得一个在 iOS、Android、macOS、Windows 和 Linux 上开箱即用的 VPN,客户端不用安装任何奇怪的东西,也不用信任第三方 VPN 供应商——服务器就是一台你自己掌控的 Linux 主机。
本文涵盖:获取一台服务器、用快速方式(包管理器)和灵活方式(源代码编译)安装 ocserv、配置文件与证书,以及大多数教程会跳过的部分——用 Nginx 把 ocserv 前置,让它通过基于 SNI 的 TCP 路由和一般网站共用 443 端口,而不需要单独开放一个端口。
1. 获取一台远程服务器
这里所说的远程服务器,指的是一台 24/7 运行、几乎都是 Linux 的主机,并且能从你实际想绕过限制的客户端网络连接到。
1.1 去哪里获取
Virtual Private Server(虚拟私有服务器):
含免费方案的云计算:
- Oracle Cloud,提供含 2 个计算实例的 Always Free 方案。
两者注册时都需要 Visa 或 Mastercard 卡片。
1.2 基础加固
在安装任何面向公网的服务之前,先做基本的加固:SSH 密钥认证、非 root 的 sudo 用户、Fail2Ban 和防火墙。完整流程可参考 Linux 云服务器初始化与安全加固。
2. 安装 OCserv
2.1 快速安装(包管理器)
最快的路径,如果你不需要特定版本或自定义编译参数,这样就够了。
# RHEL 家族 (Rocky Linux, AlmaLinux, ...)
sudo dnf install epel-release -y
sudo dnf install ocserv -y
# Debian/Ubuntu
sudo apt update && sudo apt install ocserv -y
这样立刻就能拿到一个可用的可执行文件和一个 systemd unit;如果这就是你需要的全部,可以直接跳到 2.3 配置文件。
2.2 从源代码编译
当你想要比发行版软件包更新的最新版本,或者需要软件包没有提供的编译期选项(seccomp 沙箱、特定的 GnuTLS 版本等等)时,就从源代码编译。
安装编译所需的依赖包。
# Debian/Ubuntu
sudo apt update
sudo apt install -y build-essential autoconf automake libtool pkg-config gperf \
libgnutls28-dev libnl-route-3-dev libseccomp-dev libpam0g-dev \
libprotobuf-c-dev protobuf-c-compiler libreadline-dev liblz4-dev
# RHEL 家族
sudo dnf install -y gcc make autoconf automake libtool pkgconfig gperf \
gnutls-devel libnl3-devel libseccomp-devel pam-devel \
protobuf-c-devel readline-devel lz4-devel
如果之后 ./configure 抱怨缺少某个包,就安装它提到的那个包对应的 -dev/-devel 版本,然后重新执行——上面这份清单已经涵盖常见情况(GnuTLS 用于 TLS/DTLS,libnl3 用于 tun 设备管理,libseccomp 用于 worker 沙箱,PAM 用于身份验证,protobuf-c 用于内部 main/worker 的 IPC,readline 用于 occtl 的交互式 shell)。
**下载并编译。**先到发布列表确认当前版本,不要写死一个旧版本号:
OCSERV_VERSION=1.3.0 # 换成当前的发布版本
wget "https://ocserv.gitlab.io/www/download/ocserv-${OCSERV_VERSION}.tar.xz"
tar -xf "ocserv-${OCSERV_VERSION}.tar.xz"
cd "ocserv-${OCSERV_VERSION}"
./configure \
--prefix=/usr \
--sysconfdir=/etc \
--localstatedir=/var \
--with-systemd-unitdir=/usr/lib/systemd/system
make -j"$(nproc)"
sudo make install
使用 --prefix=/usr --sysconfdir=/etc --localstatedir=/var 可以让安装的文件结构和发行版软件包一致(配置文件在 /etc/ocserv 下,unit 文件通过 --with-systemd-unitdir 安装),因此不管你走哪条安装路径,本文后面的内容都适用,不需要调整。
创建服务账号与目录(软件包安装会自动帮你做这件事;从源代码安装的话,你需要自己执行一次):
sudo groupadd -r ocserv 2>/dev/null || true
sudo useradd -r -M -s /usr/sbin/nologin -g ocserv ocserv 2>/dev/null || true
sudo mkdir -p /etc/ocserv/ssl /var/lib/ocserv
sudo chown -R ocserv:ocserv /var/lib/ocserv
sudo systemctl daemon-reload
/var/lib/ocserv 是 worker 进程被关进去的 chroot-dir(见下面的配置)——它必须存在,而且 ocserv 用户必须有写入权限,服务才能正常启动。
检查安装是否成功:
ocserv --version
occtl --version
2.3 配置文件
把下面内容存成 /etc/ocserv/ocserv.conf。不管你是用软件包安装还是从源代码安装,配置文件的结构都一样:
ocserv.conf
# --- 监听配置 ---
tcp-port = 9001
# udp-port 未配置时默认等于 tcp-port。只有在你要用 Nginx 前置 TCP 端口时
# (见第 3 节)才需要单独设置它——那种情况下这里会变成 udp-port = 443,
# 而 tcp-port 则保持为仅供内部使用的端口。
# --- 进程 / 沙箱 ---
run-as-user = ocserv
run-as-group = ocserv
socket-file = ocserv.sock
chroot-dir = /var/lib/ocserv
isolate-workers = true
pid-file = /var/run/ocserv.pid
# --- 限制与速率限制 ---
max-clients = 16
max-same-clients = 4
rate-limit-ms = 100
server-stats-reset-time = 604800
max-ban-score = 80
ban-reset-time = 1200
# --- 会话 / 保活计时器 ---
keepalive = 32400
dpd = 90
mobile-dpd = 1800
switch-to-tcp-timeout = 25
try-mtu-discovery = false
auth-timeout = 240
min-reauth-time = 300
cookie-timeout = 300
rekey-time = 172800
rekey-method = ssl
deny-roaming = false
# --- TLS / 兼容性 ---
tls-priorities = "NORMAL:%SERVER_PRECEDENCE"
compression = true
cisco-client-compat = true
dtls-legacy = true
# --- 身份验证 ---
# 用 ocpasswd 管理的 htpasswd 风格文件做密码验证。
# 如果想改用客户端证书验证,把 auth 改成 "certificate",
# 这种情况下下面的 cert-user-oid/cert-group-oid 才会有意义。
auth = "plain[passwd=/etc/ocserv/ocpasswd]"
cert-user-oid = 0.9.2342.19200300.100.1.1
cert-group-oid = 2.5.4.11
# --- 服务器证书 ---
ca-cert = /etc/ocserv/ssl/ca-cert.pem
server-cert = /etc/ocserv/ssl/server-cert.pem
server-key = /etc/ocserv/ssl/server-key.pem
# --- 网络 ---
device = vpns
predictable-ips = true
default-domain = example.com
ipv4-network = 10.10.11.0/24
dns = 1.1.1.1
dns = 8.8.4.4
ping-leases = false
use-occtl = true
# 从隧道中排除的路由,例如避免 VPN 自己的子网段或本地网络
# 又被绕回隧道本身:
no-route = 10.10.10.0/24
no-route = 192.168.192.0/24
这份配置和原本的示例是同一组指令,只是把先前版本中重复、彼此矛盾的 cert-user-oid 那一行移除了,并把一些指令分组整理,让它从头到尾读起来比较清楚,而不是一份没有分类的清单。完整的指令参考可查阅官方手册。
2.4 TLS 证书
ocserv 需要自己的 CA 和服务器证书——这和你网页服务器所用的证书是分开的两件事,因为 ocserv 自己终结它自己的 TLS/DTLS。
自签证书(个人使用没问题;客户端第一次连接时需要接受/固定它一次):
sudo mkdir -p /etc/ocserv/ssl && cd /etc/ocserv/ssl
# CA
sudo certtool --generate-privkey --outfile ca-key.pem
sudo certtool --generate-self-signed --load-privkey ca-key.pem \
--template <(printf 'cn = "ocserv-ca"\norganization = "homelab"\nserial = 1\nexpiration_days = 3650\nca\nsigning_key\ncert_signing_key\ncrl_signing_key\n') \
--outfile ca-cert.pem
# 服务器证书,由上面的 CA 签署
sudo certtool --generate-privkey --outfile server-key.pem
sudo certtool --generate-certificate --load-privkey server-key.pem \
--load-ca-certificate ca-cert.pem --load-ca-privkey ca-key.pem \
--template <(printf 'cn = "vpn.example.com"\norganization = "homelab"\nexpiration_days = 3650\nsigning_key\nencryption_key\ntls_www_server\n') \
--outfile server-cert.pem
公开受信任的证书——如果你打算用 Nginx 把 ocserv 前置在一个真实域名上(第 3 节),建议使用这种方式,这样客户端完全不用接受自签证书。用一般方式(例如通过 Certbot/ACME)为 vpn.example.com 申请一张证书,然后把 ocserv.conf 中的 server-cert/server-key 指向那张证书的 fullchain.pem/privkey.pem,而不是自签的那一对,并去掉 ca-cert(或者把它指向公共的 CA 套件),因为客户端本来就已经信任该证书的签发 CA 了。
2.5 创建 VPN 用户并启动服务
sudo ocpasswd -c /etc/ocserv/ocpasswd # 会提示输入用户名与密码
sudo systemctl enable --now ocserv
sudo systemctl status ocserv
确认它确实在监听,并检查有没有错误:
sudo ss -lntup | grep -E ':9001'
sudo journalctl -u ocserv -f
sudo occtl show status
2.6 防火墙与网络调优
启用 IP 转发并开启 BBR 拥塞控制,因为 VPN 的吞吐量同时受这两者制约:
echo "net.ipv4.ip_forward = 1" | sudo tee /etc/sysctl.d/60-ocserv.conf
echo "net.core.default_qdisc=fq" | sudo tee -a /etc/sysctl.d/60-ocserv.conf
echo "net.ipv4.tcp_congestion_control=bbr" | sudo tee -a /etc/sysctl.d/60-ocserv.conf
sudo sysctl -p /etc/sysctl.d/60-ocserv.conf
开放端口,并对离开 VPN 子网段的流量做 NAT:
sudo firewall-cmd --add-port=9001/tcp --zone=public --permanent
sudo firewall-cmd --add-port=9001/udp --zone=public --permanent
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.10.11.0/24" masquerade'
sudo firewall-cmd --reload
(如果你的发行版不是用 firewalld,对应的 ufw/iptables 写法:ufw allow 9001/tcp、ufw allow 9001/udp,再加上在你的对外接口上为 10.10.11.0/24 配置一条 MASQUERADE 规则。)
2.7 云厂商层级的防火墙
有些 VPS 供应商(包括 Vultr)在操作系统本身的防火墙之上,还有另外一层独立的防火墙。记得也要在供应商的控制台里开放同一个端口(上面示例中的 9001),否则操作系统层级的规则就算开了也没用。
到这一步,你已经有一个可以通过 your_server_ip:9001 连上的 VPN 了。下一节是可选的,但如果这台服务器同时也在跑一个一般网站,就很值得做:它能让 VPN 共用 443 端口,而不需要单独开放一个端口。
3. 用 Nginx 前置 OCserv:TCP 层级的 SNI 路由
3.1 为什么要这么做
把 ocserv 跑在自己的端口上(上面的 9001)是可行的,但这是一个明显、容易被指纹识别出来的 VPN 端点,而且还多开了一个要顾防火墙的端口。如果同一台服务器已经在用 Nginx 跑一个 443 端口的网站,你可以让 Nginx 去看 TLS ClientHello 的 SNI 字段——完全不需要解密任何东西——把 vpn.example.com 的连接直接路由到 ocserv,其他所有流量则导向网站,全部都走同一个 443 端口。
这之所以可行,是因为 Nginx 的 stream 模块可以在任何 TLS 握手真正发生之前,先偷看一眼 SNI 主机名(ssl_preread),然后把原始的 TCP 字节转发到匹配的后端——Nginx 从来不会去终结 ocserv 的 TLS,那仍然是 ocserv 自己在做的事。
3.2 前提条件:ssl_preread
确认你的 Nginx 有编译进这个模块:
nginx -V 2>&1 | grep -o with-stream_ssl_preread_module
大多数发行版软件包(Debian/Ubuntu、Fedora、官方 nginx.org 仓库)都已经内置这个模块。如果你是自己从源代码编译 Nginx,就在 ./configure 加上 --with-stream_ssl_preread 然后重新编译。
3.3 拆分 ocserv 的 TCP 和 UDP 端口
这是让整个设置真正能用在 VPN 上、而不只是一个普通 HTTPS 网站的关键技巧:TCP 和 UDP 在操作系统层级是各自独立的端口空间,所以 Nginx 绑定 TCP/443 并不会和 ocserv 直接绑定 UDP/443 发生冲突。在 ocserv.conf 中设置:
tcp-port = 9001 # 仅供内部使用;Nginx 会把匹配到的 SNI 转发到这里
udp-port = 443 # ocserv 直接绑定这个端口,完全绕过 Nginx
绑定 443 端口需要 root 权限或 CAP_NET_BIND_SERVICE——不管是软件包安装还是第 2.2 节从源代码编译安装的 systemd unit,都已经让 ocserv 带有它所需要的这个能力,不用额外处理。改完配置后重启 ocserv:
sudo systemctl restart ocserv
3.4 Nginx 的 stream {} 区块
stream {} 是一个最上层的 context,和 http {} 是平行关系,不是嵌套在它里面——把下面内容加到 /etc/nginx/nginx.conf 或它 include 的某个文件里:
stream {
map $ssl_preread_server_name $vpn_upstream {
vpn.example.com ocserv_backend;
default website_backend;
}
upstream ocserv_backend {
server 127.0.0.1:9001;
}
upstream website_backend {
server 127.0.0.1:8443; # 你网站真正的 TLS 监听端口,已经从 443 移开
}
server {
listen 443;
listen [::]:443;
proxy_pass $vpn_upstream;
ssl_preread on;
}
}
加上这个监听器之后,网站自己在 http {} 里原本的 server { listen 443 ssl; ... } 区块,就必须搬到一个仅供内部使用的端口(上面例子中的 8443),这样才能让 stream {} 是唯一真正绑定在公开的 443 TCP socket 上的东西;map 里的 website_backend 分支则会把所有不是 vpn.example.com 的流量原封不动地转发到那个内部端口——网站自己的 TLS 处理完全不需要改变,只是它监听的端口不一样了。
验证并重新加载:
sudo nginx -t
sudo systemctl reload nginx
3.5 更新防火墙
有了 Nginx 前置 TCP 这一侧之后,ocserv 的 TCP 端口就不再需要从外部连得到——只有同一台主机上的 Nginx 会去连它。相对地,UDP 端口仍然需要直接开放,因为 Nginx 完全不会碰它:
sudo firewall-cmd --remove-port=9001/tcp --zone=public --permanent # 不再需要对外开放
sudo firewall-cmd --add-port=443/udp --zone=public --permanent # ocserv 直接使用的 UDP 路径
sudo firewall-cmd --reload
443/tcp 应该早就为了网站而开放了,这部分不需要变动。
3.6 一个限制:这个做法只覆盖 TCP
ssl_preread/SNI 路由是一个只适用于 TCP 的技巧——它靠的是在 TCP 连接一开始就读取明文的 ClientHello。OpenConnect 的数据通道为了吞吐量会优先使用 UDP 上的 DTLS,而 Nginx 在 UDP 上并没有等价的”偷看主机名”机制,所以那部分流量没办法用同样的方式做 SNI 路由。3.3 节的端口拆分是绕过这个限制,而不是真正解决它:TCP(443)通过 Nginx 转发到 9001 上的 ocserv,UDP(443)则直接进 ocserv,完全不经过 Nginx。如果直连的 UDP 路径哪天被挡掉了(比如上游防火墙更严格,或客户端网络只允许对外连 443/TCP),第 2.3 节里 ocserv 的 switch-to-tcp-timeout 会让客户端自动退回成只用 TCP——速度会变慢,但走的仍然是同一条 Nginx 前置的路径,所以连接不会直接断掉。
4. 客户端连接
如果你用了第 3 节的 Nginx 配置,直接连 vpn.example.com,不用加端口号(默认就是 443)。否则就连 your_server_ip:9001。
4.1 iOS/Android
AnyConnect,即使在中国大陆,App Store 里也能找到。
- 服务器地址:
vpn.example.com(如果没有做 Nginx 配置,则是your_server_ip:9001)。 - 用户名与密码:就是你用
ocpasswd设置的那组。
4.2 Mac/Linux/Windows
AnyConnect 在 macOS 上也有,只是安装包要花点功夫才找得到。连接方式和 iOS 上一样。
另一个选择,也是 Linux 上最简单的方式,是 OpenConnect——在 macOS 上用 brew install openconnect 安装,在 Linux 上用你发行版的包管理器安装,然后这样连接:
sudo openconnect vpn.example.com
现在你就有了自己的 VPN,如果照着第 3 节配置好,从外面看起来和一个普通的 HTTPS 网站没有两样。