欢迎光临

VPS 上用 WireGuard 自建 VPN 组网:多节点内网互通、流量转发与路由策略完全实战

为什么选择 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 占用。确认方法:
    1
    ethtool -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 端口
1
iptables -I INPUT -p udp --dport 51820 -j ACCEPT
能握手但 ping 不通 AllowedIPs 配置错误,路由未生效 检查

1
ip route show table all | grep 10.10
Hub 能 ping Spoke,但 Spoke 之间不通 Hub 未开启转发
1
sysctl -w net.ipv4.ip_forward=1

,添加 iptables FORWARD 规则

隧道建立后 SSH 断开 AllowedIPs=0.0.0.0/0 导致 SSH 也走隧道 添加 Hub 公网 IP 的静态路由走原始网卡
NAT 后节点偶尔失联 未设置 PersistentKeepalive 添加

1
PersistentKeepalive = 25
MTU 问题导致大包丢包 隧道 MTU 过大 设置

1
MTU = 1420

或更低

WireGuard 的精简设计意味着排错也相对直接——检查握手状态、路由表、防火墙规则三板斧就能解决 90% 的问题。掌握了本文的配置方法和路由策略,你就能在 VPS 上构建出安全、高效、可扩展的虚拟内网。

【本站文章皆为原创,未经允许不得转载】:汤不热吧 » VPS 上用 WireGuard 自建 VPN 组网:多节点内网互通、流量转发与路由策略完全实战
分享到: 更多 (0)