两年前我在博客上写过一篇《3分钟搞定 OpenVPN 搭建》,当时贪图省事,直接推荐了一个国外挺火的开源一键安装脚本(也就是大家常搜到的 angristanopenvpn-install)。

那篇文章后来成了我博客长期的搜索流量大户,很多朋友在后台留言说“一条命令跑完就能用,太爽了”。

但随着很多读者在生产环境或者自己的云服务器上跑业务越来越深,脚本的硬伤就全暴露出来了:一键脚本本质上是一个不透明的黑盒。平时跑着挺顺,可一旦系统大版本升级、云厂商默认网卡名称变动(比如从 eth0 变成了 enp0s3)、或者证书一年到期之后,后台只要报错,90% 的人根本不知道该去哪一行代码里捞人。甚至在一些正规企业或开发团队里,安全规范明令禁止直接 curl | bash 跑来源不明的第三方脚本。

OpenVPN 发展到今天,依然是跨国企业、外企内网和远程办公中最稳健的基础设施之一。

这次我们彻底抛弃外部一键脚本,使用 Ubuntu 官方仓库里的纯净包,从零一步步理清:Easy-RSA 3 的证书信任链是怎么签出来的?OpenVPN 的服务端配置每一行都在干嘛?iptables 的出口 NAT 规则怎么做成 systemd 开机自愈?

搞懂了这套底层逻辑,以后不管是在甲骨文云、AWS 还是你自己家里的微服务器上搭建,你心里都有数,出了故障 3 分钟就能定位解决。


1. 核心架构与证书信任链透视

很多人之所以觉得 OpenVPN 难搭,主要是被那一堆 .crt.key.pem 绕晕了。其实只要把它想象成“公安局办护照”的流程,逻辑就非常清晰:

flowchart TD
    CA["根证书机构 (CA 证书与私钥)"] -->|"签发并背书"| ServerCert["服务端证书 (server.crt)"]
    CA -->|"签发并背书"| ClientCert["客户端证书 (client1.crt)"]

    subgraph Handshake ["安全握手与数据隧道"]
        Client["客户端 (OpenVPN Client)"]
        Server["服务端 (OpenVPN Server)"]
        DH["Diffie-Hellman 密钥交换 (dh.pem)"]
        TLSAuth["防重放攻击签名 (ta.key)"]

        Client -->|"1. 校验服务端合法性 (ca.crt)"| Server
        Server -->|"2. 双向校验客户端合法性 (ca.crt)"| Client
        DH -.->|"密钥协商"| Server
        TLSAuth -.->|"HMAC 签名校验"| Server
    end

    Server -->|"iptables NAT 转发"| Internet["公网互联网 / 局域网内网"]
  • CA (Certificate Authority):相当于发证机构。服务端和客户端的身份,都必须由同一个 CA 签名盖章;
  • Server 证书/私钥:服务端的专属身份证,证明“我是正规服务器,不是钓鱼节点”;
  • Client 证书/私钥:客户端的专属通行证,证明“我有权接入内网”;
  • Diffie-Hellman (DH 参数):用于在不安全的公网信道上,安全协商出对称加密会话密钥;
  • TLS-Auth (ta.key):在建立正式连接前增加一层 HMAC 签名校验,直接丢弃未授权端口探测,能够免疫绝大多数针对 OpenVPN 端口的暴力破解与 DDoS。

2. 基础工具安装与 PKI 目录准备

环境以 Ubuntu 22.04 / 24.04 LTS(或 Debian 11/12)为例,用户使用拥有 sudo 权限的普通账号或 root。

先更新软件源,并安装 OpenVPN 核心包和 Easy-RSA 证书管理工具:

sudo apt update
sudo apt install -y openvpn easy-rsa

避坑提醒
绝对不要直接在系统自带的 /usr/share/easy-rsa/ 目录里签发证书!系统包升级时可能会冲掉你的数据。我们新建一个独立的 PKI 工作目录:

make-cadir ~/openvpn-ca
cd ~/openvpn-ca

此时你的 ~/openvpn-ca 目录中已经准备好了标准的 Easy-RSA 配置文件和脚本。


3. 纯手工构建 PKI 证书体系

3.1 初始化并创建 CA 根证书

~/openvpn-ca 目录下初始化 PKI 环境并生成 CA:

cd ~/openvpn-ca
# 1. 清空并初始化 PKI
./easyrsa init-pki

# 2. 生成 CA 根证书 (使用 nopass 参数免去每次启动服务端输入密码)
./easyrsa build-ca nopass

执行期间终端会提示你输入 Common Name(通用名称),直接按回车使用默认的 Easy-RSA CA 即可。

3.2 生成并签署服务端证书

接下来为 OpenVPN 服务端生成证书请求并签发:

# 1. 生成服务端的证书请求与私钥 (命名为 server,nopass 免密)
./easyrsa gen-req server nopass

# 2. 使用刚才的 CA 为服务端签名 (输入 yes 确认)
./easyrsa sign-req server server

3.3 生成 Diffie-Hellman 交换参数与 TLS-Auth 密钥

# 1. 生成 2048 位的 Diffie-Hellman 参数 (可能需要十几秒)
./easyrsa gen-dh

# 2. 生成 TLS-Auth 预共享密钥 (用于防端口扫描与 DoS 攻击)
openvpn --genkey secret ta.key

3.4 将证书归档到 OpenVPN 服务端目录

现在我们把签发出来的凭据统一拷贝到 /etc/openvpn/server/ 正式目录下:

sudo cp pki/ca.crt /etc/openvpn/server/
sudo cp pki/issued/server.crt /etc/openvpn/server/
sudo cp pki/private/server.key /etc/openvpn/server/
sudo cp pki/dh.pem /etc/openvpn/server/
sudo cp ta.key /etc/openvpn/server/

确保私钥文件的权限收紧:

sudo chmod 600 /etc/openvpn/server/server.key
sudo chmod 600 /etc/openvpn/server/ta.key

4. 编写生产级 OpenVPN 服务端配置

创建配置文件 /etc/openvpn/server/server.conf

sudo nano /etc/openvpn/server/server.conf

将以下经过生产检验的配置粘贴进去(去除了冗余注释,关键行都有说明):

# 监听端口与协议 (首选 UDP,延迟低且抗抖动好)
port 1194
proto udp
dev tun

# 证书与密钥绝对路径
ca /etc/openvpn/server/ca.crt
cert /etc/openvpn/server/server.crt
key /etc/openvpn/server/server.key
dh /etc/openvpn/server/dh.pem
tls-auth /etc/openvpn/server/ta.key 0

# 分配给 VPN 客户端的虚拟内网网段
server 10.8.0.0 255.255.255.0
ifconfig-pool-persist /etc/openvpn/server/ipp.txt

# 核心路由推送:让客户端所有网络流量都通过 VPN 出口
push "redirect-gateway def1 bypass-dhcp"

# 推送公共 DNS (使用 Cloudflare 与 Google DNS)
push "dhcp-option DNS 1.1.1.1"
push "dhcp-option DNS 8.8.8.8"

# 心跳保活 (每 10 秒探测一次,120 秒无响应判定断开)
keepalive 10 120

# 现代加密算法 (推荐 AES-256-GCM,性能与安全性兼备)
cipher AES-256-GCM
auth SHA256

# 最小特权原则:启动后降权为 nobody 运行,增强系统安全性
user nobody
group nogroup
persist-key
persist-tun

# 运行状态与日志
status /var/log/openvpn/openvpn-status.log
log-append /var/log/openvpn/openvpn.log
verb 3
explicit-exit-notify 1

创建日志目录并调整权限:

sudo mkdir -p /var/log/openvpn
sudo chown nobody:nogroup /var/log/openvpn

5. 开启内核转发与 iptables NAT 出口持久化 (核心踩坑点)

很多新手搭建 OpenVPN 时,客户端明明显示“已连接成功”,但打开浏览器却网页打不开、断网。
99% 的原因都是出在“内核没有开启转发”或者“缺少 iptables 伪装规则”!

5.1 启用 Linux 内核 IPv4 转发

编辑 /etc/sysctl.conf

sudo nano /etc/sysctl.conf

取消注释或追加这一行:

net.ipv4.ip_forward = 1

让配置立即生效:

sudo sysctl -p

5.2 编写独立的 iptables 自启服务 (告别重启失效)

我们需要将虚拟内网网段(10.8.0.0/24)的流量,伪装成云服务器物理外网网卡的 IP 转发出去。

首先查看你的公网网卡名称:

ip route get 8.8.8.8 | awk '{for(i=1;i<=NF;i++)if($i=="dev")print $(i+1)}'

记下输出的名字(例如是 eth0 或是 enp0s3)。

很多一键脚本依赖杂乱的辅助包,我们直接用原生 Systemd 单元 做一个极简的规则自愈守护。
创建 /etc/systemd/system/openvpn-iptables.service

sudo nano /etc/systemd/system/openvpn-iptables.service

写入以下内容(注意将代码中的 eth0 替换为你上面查到的实际物理网卡名):

[Unit]
Description=OpenVPN iptables rules persistence
After=network.target

[Service]
Type=oneshot
RemainAfterExit=yes
# 启动时注入 NAT 出口规则
ExecStart=/sbin/iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE
# 停止时清理规则
ExecStop=/sbin/iptables -t nat -D POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE

[Install]
WantedBy=multi-user.target

激活并启动防火墙规则服务:

sudo systemctl daemon-reload
sudo systemctl enable --now openvpn-iptables.service

6. 启动 OpenVPN 服务并验证监听

Systemd 使用模版服务单元来管理 OpenVPN:openvpn-server@<配置文件名>。因为我们的配置文件是 server.conf,所以对应的服务名就是 openvpn-server@server

# 启动并设置开机自启
sudo systemctl enable --now openvpn-server@server

# 检查运行状态
sudo systemctl status openvpn-server@server

如果看到绿色的 active (running),说明服务端已经就绪。再检查一下 UDP 端口监听:

sudo ss -tulpn | grep 1194

云厂商防火墙警告
如果你使用的是甲骨文云(OCI)、阿里云或腾讯云,切记去云平台控制台的 “安全组 / Security List” 中,添加入站规则:协议选择 UDP,端口填 1194


7. 客户端证书签发与一键生成单文件 .ovpn

很多教程的做法是:让用户在本地下载好几个文件(ca.crtclient.crtclient.keyta.key),然后再手动配路径,特别容易出错。

这里我写了一个单文件内嵌转换方案:把所有证书内容全部内嵌到一个独立的 .ovpn 文件中,手机或电脑双击直接导入,免去一切碎文件的困扰。

7.1 为客户端(例如 client1)签发证书

回到 ~/openvpn-ca 目录:

cd ~/openvpn-ca
# 1. 生成名为 client1 的证书请求
./easyrsa gen-req client1 nopass

# 2. 签发客户端证书 (输入 yes 确认)
./easyrsa sign-req client client1

7.2 编写一键合成脚本 (make_config.sh)

在当前用户主目录创建客户端配置构建工作区:

mkdir -p ~/client-configs/files
chmod 700 ~/client-configs

创建基础模板文件 ~/client-configs/base.conf

nano ~/client-configs/base.conf

粘贴以下客户端通用配置(将其中的 YOUR_SERVER_IP 改为你服务器的实际公网 IP):

client
dev tun
proto udp
remote YOUR_SERVER_IP 1194
resolv-retry infinite
nobind
persist-key
persist-tun
remote-cert-tls server
cipher AES-256-GCM
auth SHA256
key-direction 1
verb 3

接着创建自动打包脚本 ~/client-configs/make_config.sh

nano ~/client-configs/make_config.sh

写入打包逻辑:

#!/bin/bash
# 自动合成单文件 .ovpn 脚本
if [ -z "$1" ]; then
    echo "使用方法: ./make_config.sh <客户端名称>"
    exit 1
fi

CLIENT="$1"
KEY_DIR=~/openvpn-ca/pki
OUTPUT_DIR=~/client-configs/files
BASE_CONFIG=~/client-configs/base.conf

if [ ! -f "${KEY_DIR}/issued/${CLIENT}.crt" ]; then
    echo "错误: 找不到客户端证书 ${KEY_DIR}/issued/${CLIENT}.crt"
    exit 1
fi

cat ${BASE_CONFIG} \
    <(echo -e '<ca>') \
    ${KEY_DIR}/ca.crt \
    <(echo -e '</ca>\n<cert>') \
    <(sed -ne '/BEGIN CERTIFICATE/,$ p' ${KEY_DIR}/issued/${CLIENT}.crt) \
    <(echo -e '</cert>\n<key>') \
    ${KEY_DIR}/private/${CLIENT}.key \
    <(echo -e '</key>\n<tls-auth>') \
    /etc/openvpn/server/ta.key \
    <(echo -e '</tls-auth>') \
    > ${OUTPUT_DIR}/${CLIENT}.ovpn

echo "✅ 生成完毕: ${OUTPUT_DIR}/${CLIENT}.ovpn"

赋予执行权限并生成客户端配置:

chmod +x ~/client-configs/make_config.sh
~/client-configs/make_config.sh client1

此时,在 ~/client-configs/files/client1.ovpn 就生成了一个完全独立的配置文件!

你只需要通过 SFTP 或 scp 命令将其下载到本地:

scp -i ~/.ssh/your_key ubuntu@YOUR_SERVER_IP:~/client-configs/files/client1.ovpn ./

在 Windows / macOS 下载官方 OpenVPN Connect 客户端,或者在 iOS / Android 上,直接将该文件拖入客户端,点击即可秒级连接!


8. 生产日常运维与常见故障排查手册

8.1 客户端连上后无网络访问(无法 Ping 通外网)

  • 检查 1:服务器是否开启内核转发:sysctl net.ipv4.ip_forward,必须返回 1
  • 检查 2iptables -t nat -L -n -v 查看 POSTROUTING 链里是否有 MASQUERADE 规则,确认规则绑定的物理网卡名是否正确;
  • 检查 3:云控制台路由表(VCN/VPC)是否允许实例进行源/目标地址检查(部分云平台需要关闭“Source/Destination Check”)。

8.2 握手失败提示:TLS Error: TLS key negotiation failed

  • 检查 1:云服务器安全组 UDP 1194 端口是否放行;
  • 检查 2:客户端与服务端的系统时钟偏差。如果服务端或客户端的时间慢了几分钟,会导致 TLS 证书被判定为尚未生效或已过期。在服务器运行 sudo timedatectl set-ntp on 同步标准时间。

8.3 如何注销(吊销)泄露的客户端凭据?

如果某个客户端设备丢失或离职,必须立即吊销其证书访问权:

cd ~/openvpn-ca
# 1. 吊销 client1
./easyrsa revoke client1

# 2. 生成证书吊销黑名单 (CRL)
./easyrsa gen-crl

# 3. 部署 CRL 规则到服务端
sudo cp pki/crl.pem /etc/openvpn/server/
sudo chmod 644 /etc/openvpn/server/crl.pem

然后在 /etc/openvpn/server/server.conf 中追加一行 crl-verify /etc/openvpn/server/crl.pem 并重启服务,该客户端就会被永久拒之门外。


结语

从头到尾走完这一遍,你会发现 OpenVPN 并没有想象中那么神秘。没有了各种一键脚本在底层悄悄安装的杂乱依赖和定时任务,整个服务器干净清爽。每一张证书从何而来、每一条 iptables 规则为何而生,都在你自己的完全掌控之中。

这篇教程里的单文件配置生成脚本,可以长久保存下来作为你自己的运维百宝箱工具。以后只要需要固定出口 IP 或者搭建远程办公隧道,拉出这套流程,十分钟内就能交付一个生产级的高可用网关。

文章作者: tutu
版权声明: 本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 TutuのBlog
喜欢就支持一下吧