为什么选择 WireGuard 而不是 OpenVPN 或 IPSec
在 VPS 之间搭建虚拟专用网络是运维工作中最常见的需求之一——无论是让分布在不同机房的服务器通过内网互通,还是把家庭网络和云上的机器连成一张网,VPN 都是基础设施中的基础设施。传统方案如 OpenVPN 配置繁琐、代码量庞大,IPSec 更是协议复杂到让人望而却步。WireGuard 以不到 4000 行核心代码的极简设计,提供了同样甚至更好的安全性,同时把配置和维护的复杂度降到了最低。
WireGuard 的核心优势可以总结为三点:极简——一个配置文件加一对密钥就能跑起来;高性能——内核模块实现,加密握手和数据传输的速度远超用户态方案;现代加密——默认使用 ChaCha20-Poly1305 和 Curve25519,不提供过时算法的可选项,从根本上消除了降级攻击的可能。
本文将从零开始,手把手教你在多台 VPS 上搭建 WireGuard 组网,涵盖密钥生成、节点配置、路由策略、NAT 穿透、流量转发等核心场景,最终实现一个安全可控的多节点内网。
环境准备与密钥生成
安装 WireGuard
主流 Linux 发行版的安装方式如下:
1
2
3
4
5
6
7
8
9 # Debian / Ubuntu
sudo apt update && sudo apt install -y wireguard
# CentOS 8 / Rocky Linux
sudo dnf install -y elrepo-release epel-release
sudo dnf install -y kmod-wireguard wireguard-tools
# Arch Linux
sudo pacman -S wireguard-tools
安装完成后,内核模块会自动加载。你可以用
1 | modinfo wireguard |
验证模块是否存在。如果使用的是 OpenVZ 或 LXC 容器,需要确认宿主机已加载 WireGuard 模块,否则无法创建接口。
生成密钥对
WireGuard 使用非对称加密进行节点认证。每个节点需要一对密钥:私钥(Private Key)保存在本机,公钥(Public Key)分发给对端。生成方式非常简单:
1
2
3
4
5
6
7
8
9 # 生成私钥
wg genkey > /etc/wireguard/server_private.key
chmod 600 /etc/wireguard/server_private.key
# 从私钥推导公钥
wg pubkey < /etc/wireguard/server_private.key > /etc/wireguard/server_public.key
# 查看公钥内容(稍后配置对端时需要)
cat /etc/wireguard/server_public.key
每台参与组网的节点都需要执行上述操作生成各自的密钥对。切记:私钥绝不能通过网络明文传输,建议通过 SSH 登录到每台机器直接生成,而非在本地生成后 scp 过去。
如果你需要为节点预共享密钥以增加一层对称加密保护,可以额外生成:
1
2 wg genpsk > /etc/wireguard/preshared.key
chmod 600 /etc/wireguard/preshared.key
预共享密钥(PSK)是可选的,但在高安全要求的场景下建议启用——即使未来量子计算攻破了 Curve25519,PSK 仍然能提供一层保护。
中心化星型拓扑:Hub-Spoke 模式搭建
最常见的组网拓扑是星型结构:一台 VPS 作为中心节点(Hub),其他所有节点通过 WireGuard 隧道连接到中心节点。所有跨节点的流量都经由 Hub 中转。这种模式配置简单,适合节点数量不多(10台以内)且节点间不需要直接通信的场景。
Hub 节点配置
假设 Hub 节点的公网 IP 是
1 | 203.0.113.1 |
,监听端口
1 | 51820 |
,内网地址段使用
1 | 10.10.0.0/24 |
,Hub 自身地址为
1 | 10.10.0.1 |
。配置文件位于
1 | /etc/wireguard/wg0.conf |
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 [Interface]
PrivateKey = <Hub的私钥>
Address = 10.10.0.1/24
ListenPort = 51820
# 允许 Hub 转发流量
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
# Spoke 节点 A
[Peer]
PublicKey = <Spoke-A的公钥>
AllowedIPs = 10.10.0.2/32
# Spoke 节点 B
[Peer]
PublicKey = <Spoke-B的公钥>
AllowedIPs = 10.10.0.3/32
1 | AllowedIPs |
决定了哪些目标地址的流量会被路由到这个 Peer。在 Hub 上,每个 Spoke 的 AllowedIPs 通常只分配一个 /32 地址,精确匹配该节点的隧道 IP。
Spoke 节点配置
以 Spoke-A 为例,其隧道地址为
1 | 10.10.0.2 |
:
1
2
3
4
5
6
7
8
9 [Interface]
PrivateKey = <Spoke-A的私钥>
Address = 10.10.0.2/24
[Peer]
PublicKey = <Hub的公钥>
Endpoint = 203.0.113.1:51820
AllowedIPs = 10.10.0.0/24
PersistentKeepalive = 25
几个关键参数说明:
- Endpoint:Hub 的公网地址和端口。Spoke 主动连接 Hub,Hub 不需要配置 Spoke 的 Endpoint。
- AllowedIPs = 10.10.0.0/24:所有发往 10.10.0.0/24 网段的流量都走 WireGuard 隧道。这意味着 Spoke-A 可以访问整个 VPN 内网。
- PersistentKeepalive = 25:每 25 秒发送一次心跳包。这是 NAT 穿透的关键——如果 Spoke 在 NAT 后面,没有这个参数,NAT 映射超时后 Hub 将无法主动连接 Spoke。
启动与验证
1
2
3
4
5
6
7
8
9 # 启动接口
sudo wg-quick up wg0
# 查看连接状态
sudo wg show wg0
# 测试连通性
ping 10.10.0.1 # 从 Spoke-A ping Hub
ping 10.10.0.2 # 从 Hub ping Spoke-A
如果
1 | wg show |
显示 latest handshake 在几秒之内,说明隧道已经成功建立。如果握手时间一直不更新,首先检查防火墙是否放行了 UDP 51820 端口,然后确认 Endpoint 地址是否正确。
全网状拓扑:点对点互联
星型拓扑的缺点是 Hub 成为单点故障和性能瓶颈——所有跨节点流量都经过 Hub 转发,延迟翻倍。对于对延迟敏感的应用(如分布式数据库集群),全网状(Full Mesh)拓扑是更好的选择:每个节点都和其他所有节点直连,流量不经过中间节点。
手动配置全网状
以三节点为例,每台机器都需要配置另外两台为 Peer:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 # 节点 A(10.10.0.1,公网 203.0.113.1)
[Interface]
PrivateKey = <A私钥>
Address = 10.10.0.1/24
ListenPort = 51820
[Peer]
PublicKey = <B公钥>
Endpoint = 198.51.100.2:51820
AllowedIPs = 10.10.0.2/32
[Peer]
PublicKey = <C公钥>
Endpoint = 192.0.2.3:51820
AllowedIPs = 10.10.0.3/32
节点 B 和 C 的配置类似,只需要把 Peer 的 PublicKey、Endpoint 和 AllowedIPs 换成对应的值。注意全网状中 AllowedIPs 只写 /32,因为每条隧道只承载到特定节点的流量。
当节点数量超过 5 台时,手动维护全网状配置会变得非常痛苦——N 个节点需要 N×(N-1)/2 条隧道。这时候应该使用自动化工具。
用 wg-quick 配置模板批量生成
一个简单的 Bash 脚本可以根据节点列表自动生成所有配置文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42 #!/bin/bash
# gen-mesh.sh - 生成全网状 WireGuard 配置
declare -A PUBKEYS
declare -A ENDPOINTS
NODES=("a:203.0.113.1" "b:198.51.100.2" "c:192.0.2.3")
SUBNET="10.10.0"
for i in "${!NODES[@]}"; do
IFS=':' read -r name endpoint <<< "${NODES[$i]}"
PUBKEYS[$name]=$(ssh root@$endpoint "cat /etc/wireguard/server_public.key")
ENDPOINTS[$name]=$endpoint
done
for i in "${!NODES[@]}"; do
IFS=':' read -r name endpoint <<< "${NODES[$i]}"
ip="${SUBNET}.$(($i + 1))"
privkey=$(ssh root@$endpoint "cat /etc/wireguard/server_private.key")
cat > /tmp/wg0-${name}.conf << EOF
[Interface]
PrivateKey = ${privkey}
Address = ${ip}/24
ListenPort = 51820
EOF
for j in "${!NODES[@]}"; do
if [ $i -ne $j ]; then
IFS=':' read -r pname pendpoint <<< "${NODES[$j]}"
pip="${SUBNET}.$(($j + 1))"
cat >> /tmp/wg0-${name}.conf << EOF
[Peer]
PublicKey = ${PUBKEYS[$pname]}
Endpoint = ${pendpoint}:51820
AllowedIPs = ${pip}/32
PersistentKeepalive = 25
EOF
fi
done
scp /tmp/wg0-${name}.conf root@$endpoint:/etc/wireguard/wg0.conf
done
高级路由策略与流量转发
让 VPN 节点作为网关转发公网流量
除了内网互通,WireGuard 的另一个常见用途是让所有公网流量都通过某台 VPS 出去——本质上是自建代理。这需要在 Hub 节点上启用 IP 转发,并在 Spoke 端把默认路由指向 WireGuard 接口。
Hub 端(
1 | /etc/wireguard/wg0.conf |
的 PostUp):
1
2 PostUp = sysctl -w net.ipv4.ip_forward=1; iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
Spoke 端把 AllowedIPs 设为
1 | 0.0.0.0/0 |
,这样所有流量(包括公网流量)都会走 WireGuard 隧道:
1
2
3
4
5 [Peer]
PublicKey = <Hub公钥>
Endpoint = 203.0.113.1:51820
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25
但这样会导致 SSH 连接也走隧道,一旦隧道断开你就无法重新连接到 Spoke。解决方案是添加一条高优先级的静态路由,确保到 Hub 公网 IP 的流量仍然走原始网卡:
1
2 # 在 wg-quick 启动之前手动添加
ip route add 203.0.113.1/32 via <Spoke的默认网关> dev eth0
或者在 Interface 段使用 PostUp:
1 PostUp = ip route add 203.0.113.1/32 via $(ip route | grep default | awk '{print $3}') dev eth0
选择性路由:仅特定流量走 VPN
更精细的控制是只让某些目标网段的流量走 WireGuard。例如,只让访问 10.0.0.0/8 内网的流量走 VPN,其他流量正常出去:
1
2
3
4
5 [Peer]
PublicKey = <Hub公钥>
Endpoint = 203.0.113.1:51820
AllowedIPs = 10.0.0.0/8, 10.10.0.0/24
PersistentKeepalive = 25
WireGuard 会根据 AllowedIPs 自动添加路由表项。多条路由用逗号分隔,支持混合不同网段。
使用 fwmark 和策略路由避免路由冲突
当 VPN 网关配置复杂(比如多 WAN 环境)时,WireGuard 自身的握手包可能和路由规则产生循环依赖——握手包被路由规则捕获走隧道,但隧道还没建立。解决方案是使用 fwmark:
1
2
3
4
5
6
7
8 [Interface]
PrivateKey = <私钥>
Address = 10.10.0.2/24
FwMark = 0xca6c
Table = 51820
PostUp = ip rule add not fwmark 0xca6c table 51820; ip rule add table main suppress_prefixlength 0
PostDown = ip rule del not fwmark 0xca6c table 51820; ip rule del table main suppress_prefixlength 0
1 | FwMark |
给 WireGuard 自身发出的 UDP 包打上标记,
1 | ip rule |
确保带标记的包走 main 路由表(即正常出去建立隧道),其他包走 51820 号路由表(即隧道内部路由)。这种模式是官方推荐的最佳实践,特别是在
1 | AllowedIPs = 0.0.0.0/0 |
的全流量转发场景下必须使用。
NAT 穿透与动态 IP 处理
在真实环境中,不是所有节点都有固定的公网 IP。家庭宽带、4G 热点、NAT 后面的 VPS 都可能无法被对端主动连接。WireGuard 的设计天然支持 NAT 穿透——只要一方能主动连接到另一方,双向通信就能建立。
PersistentKeepalive 是关键
位于 NAT 后面的节点必须设置
1 | PersistentKeepalive |
。原因是 NAT 设备会维护一个映射表,记录内部 IP:端口到外部 IP:端口的对应关系。这个映射有超时时间(通常 30-120 秒),超时后映射被删除,外部就无法再通过之前的映射找到内部节点。PersistentKeepalive 持续发送心跳包,保持 NAT 映射不过期。
1
2
3
4
5 [Peer]
PublicKey = <Hub公钥>
Endpoint = 203.0.113.1:51820
AllowedIPs = 10.10.0.0/24
PersistentKeepalive = 25 # 必须小于 NAT 超时时间
推荐值为 25 秒——大多数 NAT 设备的超时时间至少 30 秒,25 秒的间隔提供了足够的余量。
动态 Endpoint 更新
当对端的 IP 变化时(比如家庭宽带重拨后 IP 改变),WireGuard 有一个很棒的特性:它会在收到合法的握手包时自动更新对端的 Endpoint 地址。这意味着只要 NAT 后面的节点持续发送心跳,Hub 上的 Endpoint 就会自动跟随变化,无需手动更新配置。
DNS 集成与名称解析
当节点数量增多后,记忆 IP 地址变得不现实。WireGuard 支持在接口配置中指定 DNS 服务器:
1
2
3
4 [Interface]
PrivateKey = <私钥>
Address = 10.10.0.1/24
DNS = 10.10.0.1
这会让 wg-quick 在启动时将系统 DNS 指向 10.10.0.1。你可以在 Hub 上运行一个轻量级 DNS 服务(如 CoreDNS 或 dnsmasq),为每个节点分配域名:
1
2
3
4 # /etc/dnsmasq.d/wireguard
address=/hub.vpn/10.10.0.1
address=/node-a.vpn/10.10.0.2
address=/node-b.vpn/10.10.0.3
这样你就可以用
1 | ssh user@node-a.vpn |
代替
1 | ssh user@10.10.0.2 |
,极大提升了管理效率。
安全加固与生产环境建议
最小化暴露面
WireGuard 的设计哲学是”不响应未经认证的包”——如果你没有配置某个 Peer 的公钥,发到 WireGuard 端口的任何数据都会被静默丢弃。这意味着从外部扫描看,WireGuard 端口和关闭的端口几乎没有区别。但这不代表可以忽视其他安全措施:
- 更改默认端口:虽然 WireGuard 不响应未认证包,但自动化扫描器仍然可以通过特征识别 UDP 51820 端口。改用高位端口(如 41920)可以减少被发现的概率。
- 限制监听地址:如果 Hub 只需要被特定网段的节点访问,可以用 iptables 限制来源 IP。
- 启用预共享密钥:在高安全场景下,为每个 Peer 配置独立的 PSK,即使私钥泄露,攻击者没有 PSK 也无法建立连接。
密钥轮换
WireGuard 没有内置的密钥轮换机制,你需要手动定期更换。推荐的流程是:
1
2
3
4
5
6
7
8
9
10
11
12 # 1. 生成新密钥对
wg genkey | tee /etc/wireguard/new_private.key | wg pubkey > /etc/wireguard/new_public.key
# 2. 在所有对端的配置中更新公钥
# 编辑每个 Peer 的配置文件,替换 PublicKey
# 3. 重载配置
sudo wg syncconf wg0 <(wg-quick strip wg0)
# 4. 替换本机私钥并重启
sudo sed -i "s/PrivateKey = .*/PrivateKey = $(cat /etc/wireguard/new_private.key)/" /etc/wireguard/wg0.conf
sudo wg-quick down wg0 && sudo wg-quick up wg0
监控与告警
生产环境中需要监控隧道状态。一个简单的 Cron 脚本可以检测握手超时:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 #!/bin/bash
# /usr/local/bin/wg-monitor.sh
INTERFACE="wg0"
THRESHOLD=120 # 秒,超过此时间未握手则告警
while read -r line; do
peer=$(echo "$line" | awk '{print $2}')
latest=$(echo "$line" | awk '{print $5}')
if [ -n "$latest" ]; then
ago=$(( $(date +%s) - latest ))
if [ $ago -gt $THRESHOLD ]; then
echo "ALERT: Peer $peer last handshake ${ago}s ago" | logger -t wg-monitor
fi
fi
done < <(wg show $INTERFACE | grep -A3 "peer:" | grep "latest handshake" | awk -F'[,: ]' '{print $0}' | while IFS= read -r l; do
peer=$(echo "$l" | head -1 | awk '{print $2}')
seconds=$(echo "$l" | grep -oP '\d+' | head -1)
echo "peer $peer handshake $seconds"
done)
设置开机自启与日常运维
使用 systemd 管理 WireGuard 接口的启停和开机自启:
1
2
3
4
5
6
7
8
9
10
11
12
13
14 # 启用开机自启
sudo systemctl enable wg-quick@wg0
# 启动
sudo systemctl start wg-quick@wg0
# 停止
sudo systemctl stop wg-quick@wg0
# 查看状态
sudo systemctl status wg-quick@wg0
# 修改配置后重载(不中断连接)
sudo wg syncconf wg0 <(wg-quick strip wg0)
注意
1 | wg syncconf |
只更新 Peer 配置(公钥、AllowedIPs、Endpoint 等),不会重置接口 IP 或路由。如果你修改了 Address 或 PostUp/PostDown 命令,需要
1 | wg-quick down wg0 && wg-quick up wg0 |
完整重启。
性能优化
WireGuard 本身的性能已经非常出色,但在高吞吐场景下仍然有优化空间:
- 调整 MTU:WireGuard 会额外添加 60 字节的头部(IPv4 场景)。如果物理接口 MTU 是 1500,WireGuard 接口的 MTU 应设为 1420 或更低。在某些云服务商中,底层网络可能已经使用了更小的 MTU(如 AWS 的 9001 jumbo frame 或某些 VPC 的 1460),需要相应调整。
- 启用 GRO/GSO:现代内核默认启用 Generic Receive/Send Offload,可以显著降低 CPU 占用。确认方法:
1ethtool -k eth0 | grep generic-receive-offload
。
- 选择合适的加密方案:WireGuard 默认使用 ChaCha20-Poly1305,这在没有 AES 硬件加速的 CPU(如大多数 ARM 芯片)上性能优于 AES。如果你的服务器支持 AES-NI,性能差异可以忽略不计。
1
2
3
4
5
6 # 测试 WireGuard 隧道吞吐量
# 在一端启动 iperf3 服务端
iperf3 -s -B 10.10.0.1
# 在另一端测试
iperf3 -c 10.10.0.1 -t 30 -P 4
常见问题排查
最后总结几个最容易踩坑的地方:
| 现象 | 原因 | 解决方法 | ||
|---|---|---|---|---|
| 握手失败,wg show 无 latest handshake | 防火墙未放行 UDP 端口 |
|
||
| 能握手但 ping 不通 | AllowedIPs 配置错误,路由未生效 | 检查
|
||
| Hub 能 ping Spoke,但 Spoke 之间不通 | Hub 未开启转发 |
,添加 iptables FORWARD 规则 |
||
| 隧道建立后 SSH 断开 | AllowedIPs=0.0.0.0/0 导致 SSH 也走隧道 | 添加 Hub 公网 IP 的静态路由走原始网卡 | ||
| NAT 后节点偶尔失联 | 未设置 PersistentKeepalive | 添加
|
||
| MTU 问题导致大包丢包 | 隧道 MTU 过大 | 设置
或更低 |
WireGuard 的精简设计意味着排错也相对直接——检查握手状态、路由表、防火墙规则三板斧就能解决 90% 的问题。掌握了本文的配置方法和路由策略,你就能在 VPS 上构建出安全、高效、可扩展的虚拟内网。
汤不热吧