The most common OpenVPN deployment scenario is letting remote users
access the server's LAN (office/home network). This requires:
1. net.ipv4.ip_forward=1
2. iptables MASQUERADE on the LAN-facing interface
3. push 'route <LAN-net>' so clients know to send LAN traffic through VPN
Previously this was only mentioned in the FAQ row 'ping不通服务端',
with no actionable instructions. Users (including the project's own
first deployment) hit this exact issue and had to figure it out from
forum posts.
Changes:
README.md:
- New section 'IP 转发与内网访问' before 防火墙与公网暴露
- Covers: enabling ip_forward (immediate + persistent via sysctl.d),
MASQUERADE rules for one/multiple LAN interfaces, push routes,
verification commands, troubleshooting table, full checklist
- FAQ table: add explicit row for 'gateway reachable, LAN not'
- Features table: add '内网转发' row
- TOC: link new section
scripts/install.sh:
- Auto-enable ip_forward on install (idempotent, persistent via
/etc/sysctl.d/99-openvpn-manager.conf)
- Skip silently in containerized environments (no /proc/sys write)
- Print reminder about manual MASQUERADE rule with link to docs
OpenVPN Manager
一个开箱即用的 OpenVPN Web 管理控制台 —— 多实例托管、客户端证书一键签发、流量审计、证书到期提醒、自动备份与恢复。 基于 Go (Gin) + Vue 3 + Element Plus + ECharts,单二进制部署。
目录
功能特性
| 模块 | 说明 |
|---|---|
| 多实例管理 | 同一台机器上跑多个 OpenVPN 实例,每个独立端口/协议/子网/PKI |
| 客户端证书 | 一键签发,自动生成 .ovpn(内嵌 CA/Cert/Key/TLS-Auth),无需额外文件 |
| 固定 IP | 通过 CCD (client-config-dir) 为指定用户分配固定 VPN IP |
| 访问控制(白名单) | 实例/用户两层 allow_networks,合并生效;服务端 iptables 强制隔离 |
| 多账号管理 | db.json 中存 bcrypt 哈希,可创建多个 admin/operator 账号,首登强制改密 |
| 启停控制 | Web 一键启动/停止实例,显示 PID 与状态 |
| 流量审计 | 解析 status-version 3 输出,记录上下行字节/连接时长 |
| 证书到期提醒 | 仪表盘统计 30 天内到期的证书,单独证书管理页查看完整清单 |
| 自动备份 | 一键打包 PKI + 实例配置 + 客户端配置为 tar.gz |
| 一键恢复 | 上传/选择已有备份,直接覆盖还原 |
| 操作审计 | 所有写操作(创建/修改/删除/吊销/备份)持久化,记录操作者/IP/结果 |
| JWT 鉴权 | 登录后 12 小时有效 token,无状态可水平扩展 |
| 内网转发 | 文档化 IP 转发 + NAT 一键配置,客户端可访问服务端 LAN |
架构概览
┌──────────────────────────┐
│ 浏览器 (Vue 3 SPA) │
│ Element Plus + ECharts │
└──────────────┬───────────┘
│ HTTPS/HTTP (8089)
▼
┌──────────────────────────┐ ┌────────────────────────┐
│ Go HTTP 服务 (Gin) │ ◄──┐ │ systemd: │
│ - JWT 鉴权 │ │ │ openvpn-manager.service│
│ - REST API │ │ └────────────────────────┘
│ - OpenVPN 进程管理 │ │
│ - OpenSSL 证书签发/吊销 │ │ ┌────────────────────────┐
└──────────────┬───────────┘ │ │ /usr/sbin/openvpn │
│ ├───►│ --config <conf> │
│ │ │ --cd <dir> │
▼ │ │ → status.log │
┌──────────────────────────┐ │ └────────────────────────┘
│ <DataDir> │ │
│ pki/ │ │
│ instances/<name>/... │ │
│ clients/<inst>/<user>/ │ │
│ backups/ │ │
│ db.json │ │
└──────────────────────────┘ │
│
┌────────────────┘
│
▼
(所有写操作) → audit_logs[]
一、服务端部署
系统要求
- 操作系统:Ubuntu 20.04+ / Debian 11+ / CentOS Stream 9+ / Rocky 9+
- CPU:1 核即可
- 内存:最低 512 MB
- 磁盘:1 GB(证书 + 用户越多占用越大)
- 网络:开放
8089端口(管理界面) + 每个 OpenVPN 实例一个 UDP/TCP 端口(默认 1194) - 权限:必须以
root启动(OpenVPN 需要TUN/TAP设备权限) - 依赖工具(脚本自动安装):
openvpn≥ 2.5openssl≥ 1.1nodejs≥ 18npmgolang≥ 1.21
一键安装
git clone ssh://git@git.cnbugs.com:10022/AI-Agent/openvpn-manager.git
cd openvpn-manager
chmod +x scripts/install.sh
sudo ./scripts/install.sh
脚本会做这些事:
- 识别发行版,自动选 apt/dnf/yum
- 安装系统依赖
npm install+npm run build构建前端go build编译后端二进制- 拷贝到
/opt/openvpn-manager - 注册 systemd 单元并启动
- 健康检查
/api/health - 打印访问地址和默认账号
成功后会看到:
================================================================
OpenVPN Manager 安装完成
================================================================
访问地址 : http://<服务器IP>:8089
用户名 : admin
密码 : admin123
安装目录 : /opt/openvpn-manager
数据目录 : /opt/openvpn-manager/data
配置单元 : /etc/systemd/system/openvpn-manager.service
================================================================
安装参数
sudo ./scripts/install.sh \
--port 9090 \
--user admin \
--pass 'StrongPass!2026' \
--dir /opt/openvpn-manager
| 参数 | 默认值 | 说明 |
|---|---|---|
--port |
8089 |
Web 管理端口 |
--user |
admin |
初始管理员用户名 |
--pass |
admin123 |
初始管理员密码,生产环境必须改 |
--dir |
/opt/openvpn-manager |
安装目录 |
-u, --uninstall |
- | 卸载(停服务、删除安装目录、清理 systemd) |
JWT 签名密钥会在安装时自动生成 64 位随机十六进制串写入
/etc/systemd/system/openvpn-manager.service的OVPNMGR_JWT_SECRET环境变量,不要复制生产环境的这个文件。
手动安装
适合不想用 systemd 的场景(如 Docker):
# 1. 装依赖
apt-get install -y openvpn openssl nodejs npm golang-go git # Debian/Ubuntu
# 或
dnf install -y openvpn openssl nodejs npm golang git # RHEL/Fedora
# 2. 编译
git clone <this-repo> && cd openvpn-manager
cd frontend && npm install --include=dev && npm run build && cd ..
cd backend && go build -o ../bin/openvpn-manager ./cmd/server && cd ..
# 3. 运行
mkdir -p data
OVPNMGR_PORT=8089 \
OVPNMGR_ADMIN_USER=admin \
OVPNMGR_ADMIN_PASS='your-password' \
OVPNMGR_JWT_SECRET=$(openssl rand -hex 32) \
./bin/openvpn-manager --data ./data --dist ./dist
目录结构
/opt/openvpn-manager/
├── bin/openvpn-manager # Go 编译后的二进制 (~21MB)
├── dist/ # Vue 构建产物 (index.html + assets/)
├── data/ # 数据目录,定期备份
│ ├── db.json # 元数据(JSON):实例/用户/审计
│ ├── pki/
│ │ ├── ca.crt # CA 证书(全局共享)
│ │ ├── ca.key # CA 私钥(权限 0600)
│ │ ├── dh.pem # DH 参数
│ │ └── ta.key # TLS-Auth 共享密钥
│ ├── instances/
│ │ └── <instance-name>/
│ │ ├── server.conf # OpenVPN 服务端配置
│ │ ├── status.log # OpenVPN 状态文件(10s 周期)
│ │ ├── ipp.txt # IP 池持久化
│ │ ├── ccd/ # 客户端配置目录(固定 IP)
│ │ ├── logs/openvpn.log
│ │ └── pki/
│ │ ├── issued/<cn>.crt
│ │ └── private/<cn>.key
│ ├── clients/
│ │ └── <instance>/<user>.ovpn
│ └── backups/ # 自动备份目录
└── .env # 环境变量(权限 0600)
systemd 管理
systemctl status openvpn-manager # 状态
systemctl restart openvpn-manager # 重启
systemctl stop openvpn-manager # 停止
journalctl -u openvpn-manager -f # 跟踪日志(ctrl+c 退出)
journalctl -u openvpn-manager -n 200 # 最近 200 行
修改配置后需要:
systemctl edit openvpn-manager # 改环境变量(创建 override.conf)
systemctl daemon-reload
systemctl restart openvpn-manager
或者直接编辑主单元:
systemctl edit --full openvpn-manager
升级
cd /path/to/openvpn-manager
git pull
sudo ./scripts/install.sh
install.sh 会:
- 重新编译并覆盖二进制
- 保留
data/、backups/、dist/(原压缩产物) - 重启服务
重要:升级前请先在 Web 界面"备份与恢复"页面手动做一次备份,以防万一。
卸载
sudo ./scripts/install.sh -u
此命令会:停服务、禁用自启、删除 /etc/systemd/system/openvpn-manager.service、删除 /opt/openvpn-manager。
源码目录不会被删除,如需彻底清理请手动 rm -rf。
二、首次配置
登录 Web 控制台
浏览器访问 http://<服务器IP>:8089/。
默认账号:
- 用户名:
admin - 密码:
admin123
修改默认密码
⚠️ 生产环境第一步。
首次登录强制改密
首次访问用默认账号 admin/admin123 登录后,系统强制弹出"修改初始密码"对话框,
必须改成 ≥6 位的强密码才能继续使用,无法跳过。这一步密码修改后会写入 data/db.json,
之后不再需要重启服务,密码以 bcrypt 哈希持久化。
通过 Web 修改自己的密码
右上角 admin 下拉 → "修改密码" → 填写当前密码 + 新密码 → 保存,立即生效。
创建/管理其他账号
Web → 左侧"管理员"菜单(admin role 才可见):
| 操作 | 说明 |
|---|---|
| 新建账号 | 设置用户名、密码、角色(admin / operator) |
| 重置密码 | 不知道旧密码也能强制改密,新密码立即生效 |
| 启用/禁用 | 禁用后该账号无法登录(已登录的 token 仍可短期使用) |
| 删除 | 不能删除自己、不能删除最后一个 admin |
所有账号操作都进入审计日志。
通过 systemd 环境变量重置(忘记密码时)
如果你把所有 admin 都禁用/删除了,或者忘了所有密码,可以临时用环境变量密码 覆盖(只用于"首次登录然后改密",不持久):
systemctl edit --full openvpn-manager
# 找到 OVPNMGR_ADMIN_PASS 一行,改成你临时用的密码
systemctl daemon-reload
systemctl restart openvpn-manager
# 用新密码登录,系统会触发 SeedDefaultAdminIfEmpty
# 注:此路径只在 db.json 中无任何 admin 时生效;否则会校验 db.json 中的账号
更可靠的做法:直接编辑 data/db.json,把 password_hash 替换为 bcrypt 哈希
(用 htpasswd -bnBC 10 "" yourpass | tr -d ':\n' 生成)后重启服务。
环境变量里的
OVPNMGR_ADMIN_USER/OVPNMGR_ADMIN_PASS仅在数据库为空时作为初始 seed 使用。
创建第一个实例
Web → "实例管理" → "新建实例":
| 字段 | 推荐值 | 说明 |
|---|---|---|
| 名称 | prod |
字母数字下划线,作为目录名,不可重复 |
| 端口 | 1194 |
OpenVPN 监听端口,不能与已有服务冲突 |
| 协议 | udp |
udp 性能好,tcp 穿透性强 |
| 设备 | tun |
tun 路由模式(常用),tap 桥接模式 |
| 子网 | 10.8.0.0/24 |
给客户端分配的 VPN 内网网段 |
| 加密 | AES-256-GCM |
AES-128-GCM 更快,CHACHA20-POLY1305 适合 ARM |
| 摘要 | SHA256 |
|
| 推送 DNS | dhcp-option DNS 1.1.1.1dhcp-option DNS 8.8.8.8 |
一行一条 |
| 推送路由 | 192.168.1.0 255.255.255.0 |
让客户端能访问内网,每行一条 CIDR |
点"保存"会自动:
- 创建实例目录
- 用全局 CA 签发服务端证书
- 生成
server.conf
然后点列表里的"启动"按钮即可。如果失败,看 journalctl -u openvpn-manager -n 50。
创建客户端用户并下载配置
Web → "用户管理":
- 顶部下拉框选择实例
- "新建用户":
- 用户名(CN):
alice(字母数字下划线,作为证书 CN) - 备注:
Alice 张三 - 邮箱:
alice@example.com - 固定 IP:留空为动态;若填
10.8.0.10,Alice 每次连上都是这个 VPN IP
- 用户名(CN):
- 保存后表格出现 alice,点"下载 .ovpn"
下载的文件约 4-5 KB,里面已经内嵌了:
- CA 证书(
<ca>...</ca>) - 客户端证书(
<cert>...</cert>) - 客户端私钥(
<key>...</key>) - TLS-Auth 密钥(
<tls-auth>...</tls-auth>,key-direction 1)
直接发给用户即可,无需额外的 ca.crt 等文件。
IP 转发与内网访问(客户端访问服务端 LAN)
场景: 客户端连上 VPN 后,想访问服务端所在的局域网设备(如 172.16.2.0/24 网段的服务器、打印机、NAS 等)。
前提: 客户端能 ping 通 VPN 网关(10.8.0.1),但访问不了 LAN(如 ping 172.16.2.30 失败)。
OpenVPN 默认只让客户端访问 VPN 子网本身,不能直接访问服务端 LAN。要打通,服务端需要做两件事:
- 开启 IP 转发(
net.ipv4.ip_forward=1),让内核把 VPN 客户端的包从 tun 设备转到 LAN 网卡。 - 配置 NAT(MASQUERADE),让 VPN 客户端的源 IP 在离开服务端时被替换成 LAN 网卡 IP,这样 LAN 设备知道怎么回包。
一键启用(推荐)
如果服务端只有一个网卡接 LAN(典型场景),执行:
# 1. 立即启用 IP 转发
sudo sysctl -w net.ipv4.ip_forward=1
# 2. 持久化(重启不丢)
echo "net.ipv4.ip_forward=1" | sudo tee /etc/sysctl.d/99-openvpn-manager.conf
# 3. 添加 NAT 规则
# eth0 是服务端接 LAN 的网卡名(可能是 ens192/eno1/enp0s3 等,用 ip a 查看)
LAN_IF="eth0" # ← 改成实际网卡名
sudo iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o "$LAN_IF" -j MASQUERADE
sudo iptables -A FORWARD -i "$LAN_IF" -o tun+ -m state --state RELATED,ESTABLISHED -j ACCEPT
sudo iptables -A FORWARD -i tun+ -o "$LAN_IF" -j ACCEPT
# 4. 持久化 iptables(重启不丢)
sudo apt-get install -y iptables-persistent # Debian/Ubuntu
sudo netfilter-persistent save # 保存当前规则
# 或: sudo service iptables save # CentOS/RHEL
为什么是
tun+?: OpenVPN 用tun0/tun1...设备,+通配匹配所有 tun 设备。多个实例并存也能用。
验证
# 服务端视角: 应该看到一条 MASQUERADE 规则
sudo iptables -t nat -L POSTROUTING -n -v | grep MASQUERADE
# 客户端视角: VPN 连接后
ping 10.8.0.1 # 必通(网关)
ping <服务端 LAN 上的某个 IP> # 通了说明成功
curl ifconfig.me # 显示服务端公网 IP,说明 NAT 生效
找对 LAN 网卡
ip -br a # 简略列出所有网卡和 IP
ip route | grep default # 看默认路由走哪块网卡 — 通常就是 LAN 网卡
例:
default via 172.16.2.254 dev ens192 → LAN 网卡是 ens192
push 路由(让客户端知道去 LAN 怎么走)
光在服务端做 NAT 不够,还得让客户端知道 "要去 172.16.2.0/24 就走 VPN"。两种做法:
方法 A:在实例里 push 路由(推荐)
编辑实例的 server.conf,添加:
push "route 172.16.2.0 255.255.255.0"
然后在 Web 面板重启实例。
方法 B:在 Web 面板"实例管理"里配置
把 LAN 网段加到实例的 AccessMode 白名单或 push 路由列表。白名单模式下 allow_networks 会自动生成对应的 push 路由。
多网卡/复杂路由场景
如果服务端有多块 LAN 网卡(如同时接公司网和家庭网),MASQUERADE 规则要分别添加:
for IF in ens192 ens224; do
sudo iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o "$IF" -j MASQUERADE
done
或者直接用 -o eth+ 一次性匹配所有以太网卡(不太安全,慎用):
sudo iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -j MASQUERADE
排障
| 症状 | 排查 |
|---|---|
| 客户端能 ping 通 10.8.0.1,ping 不通 LAN | 没开 IP 转发,或没加 MASQUERADE 规则 |
| 能 ping 通 LAN IP,但 SSH/服务不通 | LAN 设备防火墙拒绝 VPN 子网;在 LAN 设备上 iptables -I INPUT -s 10.8.0.0/24 -j ACCEPT |
| 重启后规则丢失 | 没装 iptables-persistent 或没保存;参考上文持久化步骤 |
| ping 得通但访问慢/丢包 | MTU 问题;在 server.conf 加 tun-mtu 1400 mssfix 1360 |
| ip_forward 已开但客户端仍上不了网 | push 路由缺失;客户端路由表里没有目标 LAN 网段 |
检查清单
# 1. 转发开关
cat /proc/sys/net/ipv4/ip_forward # 必须输出 1
# 2. NAT 规则
sudo iptables -t nat -L POSTROUTING -n # 应该有 10.8.0.0/24 的 MASQUERADE
# 3. FORWARD 策略(默认 ACCEPT 还是 DROP?)
sudo iptables -L FORWARD # 看到 policy DROP 就需要显式 ACCEPT tun+ ↔ LAN 的包
# 4. 客户端路由
# VPN 连接后,在客户端执行:
ip route # 应该看到 "10.8.0.0/24 dev tun0" 和 "172.16.2.0/24 via 10.8.0.1 dev tun0"
防火墙与公网暴露
-
Web 管理端口(默认 8089)不建议直接暴露公网,建议:
- 用防火墙只允许公司/家庭 IP 访问
- 或反代 + HTTPS + Basic Auth
- 或 SSH 端口转发
ssh -L 8089:127.0.0.1:8089 user@server
-
OpenVPN 实例端口必须开放给需要连入的客户端。UDP 优先(性能好):
# ufw
ufw allow 1194/udp
ufw allow 1194/tcp # 如果实例用 TCP
# firewalld
firewall-cmd --permanent --add-port=1194/udp
firewall-cmd --reload
- 若服务器在 NAT 后(如家用宽带),需要在路由器做端口映射 UDP 1194 → 服务器内网 IP。
三、访问控制(白名单模式)
核心需求:VPN 用户登录后,只能访问你明确允许的内网网段,默认与所有内网隔离。
工作原理
每个实例有一个 access_mode 字段:
open(默认):不限制,客户端可访问所有可达网段whitelist:仅允许访问白名单中列出的内网网段
当 access_mode=whitelist 时:
- 实例配置
allow_networks:该实例下所有用户共享的允许网段 - 用户配置
allow_networks:单个用户的额外允许网段(在实例基础上叠加) - 实际生效 = 实例白名单 ∪ 用户白名单(去重)
- 服务端强制:
- OpenVPN 自动生成
client-connect.sh/client-disconnect.sh脚本 - 每个用户连接时,服务端的 FORWARD 链插入 iptables ACCEPT 规则,只放行到白名单网段的流量
- 其他内网流量在服务端被 REJECT(客户端看到的是"无法连接",而非"超时")
- OpenVPN 自动生成
- 客户端推送:
- 自动
push "redirect-gateway def1 bypass-dhcp"让客户端把所有流量都走 VPN(否则白名单没意义) - 自动按白名单
push route ...让客户端知道这些网段在 VPN 后
- 自动
双重防护:即使客户端操作系统被绕过,iptables 仍会拒绝非法流量。
使用方法
Web 端
-
实例管理 → 新建/编辑实例
- "访问控制"下拉选 "白名单(whitelist)"
- "允许网段"标签输入:每行一个 CIDR,如
192.168.1.0/24、10.0.0.0/8 - 保存
-
用户管理 → 编辑用户(可选)
- "额外允许网段":这个用户特有的、实例未列出的网段
- 保存
-
启动实例 —— 实例启动时,会执行一次
iptables -A FORWARD -i tunX -j REJECT(仅白名单模式)作为兜底
命令行(API)
# 创建白名单实例
curl -X POST http://localhost:8089/api/instances \
-H "Authorization: Bearer $TOKEN" \
-H 'content-type: application/json' \
-d '{
"name": "internal",
"port": 1194,
"subnet": "10.8.0.0/24",
"access_mode": "whitelist",
"allow_networks": ["192.168.1.0/24", "10.0.0.0/8"]
}'
# 创建用户,额外允许一个网段
curl -X POST http://localhost:8089/api/instances/$IID/users \
-H "Authorization: Bearer $TOKEN" \
-d '{"username":"alice","allow_networks":["172.16.0.0/16"]}'
验证白名单生效
实例启动后,登录客户端:
# 1. 客户端连上 VPN,确认 VPN IP 拿到
ip addr show | grep 10.8.0
# 2. 测允许的网段 - 应通
ping 192.168.1.1
# 3. 测未允许的网段 - 应 REJECT(ICMP net unreachable)
ping 192.168.50.1 # 不在白名单,应该不通
服务端验证 iptables 规则:
iptables -L FORWARD -n --line-numbers
# 应看到 -i tun0 -j REJECT 在底部,前面若干 -s <vpn-ip> -d <allowed-net> -j ACCEPT
限制与注意
| 项 | 说明 |
|---|---|
| 仅 IPv4 | IPv6 白名单需要扩展 client-connect.sh 使用 route-ipv6,目前未实现 |
| 需要 root + iptables | 若运行在容器内/无 root,白名单模式会以"open"模式退化运行 |
| 端口转发必须由客户端发起 | 客户端连入后,服务端只允许它主动访问白名单中的目标;不能用 VPN 当跳板从外部进入内网 |
| 修改 allow_networks 后 | 已有用户需重新连接一次才能拿到新的 push route;新用户即时生效 |
| 删除/吊销用户 | 会清掉 iptables 中对应的 ACCEPT 规则,不影响其他用户 |
常见误用
| 错误 | 后果 |
|---|---|
| 客户端关掉 VPN 网关 | 服务端 iptables 仍会拒绝非白名单流量,客户端访问不到 |
| 客户端把 allowed 网段路由改成另一条 | 服务端 iptables 在 FORWARD 链过滤,客户端改路由无效 |
| 忘记添加 DNS 服务器 | 客户端没法解析域名 —— 在"允许网段"加上 DNS 服务器的 IP(如 8.8.8.8/32) |
三、客户端使用
配置文件说明
下载的 alice.ovpn 是单一文件,内容大致为:
client
dev tun
proto udp
remote vpn.example.com 1194
resolv-retry infinite
nobind
persist-key
persist-tun
cipher AES-256-GCM
auth SHA256
remote-cert-tls server
verb 3
<ca>
-----BEGIN CERTIFICATE-----
... CA 证书内容 ...
-----END CERTIFICATE-----
</ca>
<cert>
-----BEGIN CERTIFICATE-----
... 客户端证书 ...
-----END CERTIFICATE-----
</cert>
<key>
-----BEGIN PRIVATE KEY-----
... 客户端私钥(请勿泄露)...
-----END PRIVATE KEY-----
</key>
<tls-auth>
-----BEGIN OpenVPN Static key V1-----
... TLS-Auth 共享密钥 ...
-----END OpenVPN Static key V1-----
</tls-auth>
key-direction 1
注意 remote 行是客户端实际连接的服务器地址,默认是 vpn.example.com,需要改成你自己的服务器公网域名/IP。
修改方法:
- 在 Web 界面"用户管理"页面,顶部"客户端连接的远端域名/IP"输入框填入你的服务器地址(如
vpn.your-domain.com或1.2.3.4),再点"下载 .ovpn" - 或下载后用文本编辑器手动改
remote行
Windows
推荐:OpenVPN 官方 GUI 客户端
- 下载:https://openvpn.net/community-downloads/ → 选择 "Windows 64-bit MSI installer"
- 安装(一路下一步,会安装一个虚拟网卡驱动,需要管理员权限)
- 把
alice.ovpn放到C:\Users\<你>\OpenVPN\config\ - 启动 "OpenVPN GUI"(开始菜单里),右下角会出现托盘图标
- 右键托盘图标 → Connect
- 第一次会弹窗请求管理员权限(用于配置路由)
- 连接成功后托盘变绿,会分配一个
10.8.0.x的 VPN IP
验证:
ipconfig /all
# 看到 "10.8.0.x" 的 Tap adapter IPv4 地址即成功
ping 10.8.0.1
# 应该通(10.8.0.1 是 OpenVPN 服务端在子网里的网关)
macOS
选项 1:Tunnelblick(免费开源,推荐)
- 下载:https://tunnelblick.net/
- 安装,会自动安装
tun驱动 - 双击
alice.ovpn,Tunnelblick 会问你"是否为所有用户安装",选"仅我"即可 - 菜单栏点 Tunnelblick 图标 → Connect alice
- 状态变绿即成功
选项 2:OpenVPN Connect(官方)
从 Mac App Store 搜索 "OpenVPN" 安装。
Linux
命令行 (systemd 服务)
# Debian/Ubuntu
sudo apt-get install -y openvpn
# RHEL/Fedora
sudo dnf install -y openvpn
# 连接
sudo openvpn --config alice.ovpn --daemon
# 或前台运行(能看到日志)
sudo openvpn --config alice.ovpn
NetworkManager 图形客户端
sudo apt-get install -y network-manager-openvpn network-manager-openvpn-gnome # Debian/Ubuntu
sudo dnf install -y NetworkManager-openvpn NetworkManager-openvpn-gnome # Fedora
设置 → 网络 → + VPN → "从文件导入 VPN" → 选 alice.ovpn → 保存 → 连接。
Android
- 安装"OpenVPN Connect"(Google Play / F-Droid 都有)
- 把
alice.ovpn通过 USB / 邮件 / 网盘传到手机 - 用文件管理器打开
.ovpn,系统会询问"用 OpenVPN 打开" - 点右上角"连接"
- 首次会提示接受 VPN 配置,点确定
- 通知栏出现钥匙图标即连接成功
iOS
- App Store 搜索"OpenVPN"安装
- 用"文件"App 把
alice.ovpn传到手机(隔空投送也行) - 在"文件"App 里点击
.ovpn,选择"用 OpenVPN 打开" - 点"ADD"导入 → 点右上角开关连接
- 系统会弹窗请求添加 VPN 配置,允许
- 设置 → 通用 → VPN 里可以看到状态
验证连接
无论哪个平台,连接成功后都可以这样验证:
- VPN IP:看分配的 IP 是否在配置的子网里(如
10.8.0.x) - Ping 服务端:从客户端
ping 10.8.0.1应该通 - 公网出口:从客户端
curl ifconfig.me应显示服务器公网 IP - DNS 解析:如果推送了 DNS,客户端
/etc/resolv.conf(Linux)应看到推送的 DNS
在 Web 控制台:
- "仪表盘"会实时显示当前在线客户端数(10 秒刷新)
- "实例管理" → 点实例 → 不会直接显示在线,但可以看 status.log
- "连接日志"显示历史连接记录
排障
| 症状 | 可能原因 |
|---|---|
| 连接后立刻断开 | 客户端证书与 CA 不匹配;服务端证书过期 |
| 拿到 IP 但 ping 不通服务端 | 防火墙没允许 UDP 1194;服务端没启用 IP 转发(见 IP 转发与内网访问) |
| 拿到 IP 但访问不了互联网 | 没推送 DNS,或客户端没把 VPN 设为默认网关 |
| 拿到 IP 能 ping 通网关,访问不了 LAN | 没做服务端 NAT — 见 IP 转发与内网访问 |
| Android 连不上 | 服务器在 NAT 后,检查运营商是否屏蔽 UDP |
| iOS 连不上 | 看 OpenVPN 日志(应用内 OpenVPN → Settings → Log) |
五、API 参考
所有 /api 路径(除 /login、/health)都需要 Authorization: Bearer <token>。
完整列表见 docs/API.md 或启动后查看 internal/api/router.go。
简单列几个常用的:
| 方法 | 路径 | 用途 |
|---|---|---|
| POST | /api/login |
登录,返回 {token, username} |
| GET | /api/dashboard |
仪表盘汇总 |
| GET/POST | /api/instances |
列出/创建实例 |
| GET/PUT/DELETE | /api/instances/:id |
单实例 CRUD |
| POST | /api/instances/:id/start / /stop |
启停 |
| GET/POST | /api/instances/:id/users |
用户列表/新建 |
| POST | /api/instances/:id/users/:uid/revoke |
吊销 |
| DELETE | /api/instances/:id/users/:uid |
删除 |
| GET | /api/instances/:id/users/:uid/ovpn?host=X |
下载 .ovpn |
| GET | /api/certs |
证书到期清单 |
| GET | /api/connlogs?instance=X |
连接日志 |
| GET/POST/DELETE | /api/backups / /api/backups/:id |
备份管理 |
| POST | /api/backups/:id/restore |
恢复 |
| GET | /api/audits |
审计日志 |
示例:
# 登录
TOKEN=$(curl -s -X POST http://localhost:8089/api/login \
-H 'content-type: application/json' \
-d '{"username":"admin","password":"admin123"}' | jq -r .token)
# 创建实例
curl -X POST http://localhost:8089/api/instances \
-H "Authorization: Bearer $TOKEN" \
-H 'content-type: application/json' \
-d '{"name":"prod","port":1194,"subnet":"10.8.0.0/24"}'
六、常见问题
Q: 服务启动后访问 8089 提示"无法连接"?
排查顺序:
systemctl status openvpn-manager # 进程在不在?
journalctl -u openvpn-manager -n 50 # 启动报错?
ss -tlnp | grep 8089 # 端口在听吗?
curl http://127.0.0.1:8089/api/health # 本机能访问吗?
常见原因:go/nodejs/openvpn 没装或版本太低。
Q: 在实例列表点"启动"提示成功,但状态一直 stopped?
OpenVPN 需要 root 启动。如果你的 systemd 单元不是 root 运行,启动会失败。检查:
ps aux | grep openvpn-manager # 确认主进程是 root
cat /etc/systemd/system/openvpn-manager.service | grep User
# 应该 User=root
Q: 下载的 .ovpn 客户端连不上?
- 确认服务器防火墙/路由器开放了对应端口(UDP 1194 等)
- 客户端能 ping 通服务器公网 IP 吗?
- 服务端 status.log 有没有客户端连接尝试?
Q: 怎么从备份恢复?
Web → "备份与恢复" → 选中备份 → "恢复"。会覆盖现有 pki/、instances/、clients/,操作前先做一次新的备份以防万一。
Q: 能用 Nginx 反代 + HTTPS 吗?
可以,示例 Nginx 配置:
server {
listen 443 ssl http2;
server_name vpn-admin.example.com;
ssl_certificate /etc/letsencrypt/live/vpn-admin.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/vpn-admin.example.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:8089;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
注意 OpenVPN 自身的 UDP/TCP 端口(1194 等)不能走 Nginx,要单独放行。
Q: 支持 IPv6 吗?
服务端支持(把 proto 改成 udp6 或 tcp6,子网用 IPv6 CIDR),但本项目目前 UI 主要按 IPv4 写,IPv6 子网需要手动编辑 server.conf(用"自定义"字段)。
七、开发
# 后端
cd backend
go run ./cmd/server --data ../data --dist ../dist
# 前端(开发热重载,会自动代理 /api 到 :8089)
cd frontend
npm install
npm run dev
# 浏览器打开 http://localhost:3000
调试模式日志:
OVPNMGR_LOG_LEVEL=debug ./bin/openvpn-manager --data ./data --dist ./dist
跑测试:
cd backend && go test ./...
cd frontend && npm run build # 顺便当 type/lint 校验
构建发布版:
# 前端
cd frontend && npm run build && cd ..
# 后端(静态链接、剥离调试符号)
cd backend
CGO_ENABLED=0 go build -ldflags "-s -w" -trimpath -o ../bin/openvpn-manager ./cmd/server
许可证
致谢
本项目使用了以下开源软件: