WireGuard极简搭建:从入门到生产可用
发布日期: 2026/08/13 阅读总量: 2

真实场景:我被OpenVPN折磨了三天

上个月,公司要给三个机房搭内网互通。我按老习惯用OpenVPN,结果被证书体系折腾够呛。CA签发、服务端证书、客户端证书、CRL吊销列表,每个环节都可能出错。最崩溃的是,一个客户端配置写错,排查到凌晨两点,最后发现只是少了个remote-cert-tls参数。

后来CTO说:试试WireGuard。我花了一个下午读完官方文档和几个issue,晚上就把三机房连通了。延迟比OpenVPN低40%,吞吐量翻倍,配置量只有OpenVPN的五分之一。

这篇文章不是WireGuard官方文档的翻译。是我从零搭建、踩坑、调优的完整记录。你照着做,不用像我一样浪费那三天。

为什么选WireGuard:三种方案横向对比

先说结论:不是所有场景都适合WireGuard。做方案对比,是为了让你别选错。

维度WireGuard 1.0.20210914OpenVPN 2.6.6IPsec StrongSwan 5.9.13
代码量约4000行约10万行约15万行
首次握手耗时0.1s~0.3s1s~3s(需TLS握手+证书校验)2s~5s(IKE_SA+CHILD_SA协商)
加密套件仅ChaCha20-Poly1305,无协商TLS 1.2/1.3,多种可选ESP/AH,算法可配
配置复杂度1个配置文件/端CA+证书+server.conf+client.ovpnipsec.conf+ipsec.secrets+证书
Linux内核集成5.6+内置模块用户态TUN设备内核态xfrm
移动端支持iOS/Android官方App需要第三方客户端需要额外配置
NAT穿透客户端 roaming 自动重连需keepalive+端口固定需NAT-T+端口固定
UDP 4500bps小包吞吐812 Mbps156 Mbps238 Mbps
CPU占用(iperf3 1Gbps)单核28%单核67%单核81%

测试环境:两台 Ubuntu 22.04.3 LTS 云服务器,同一可用区,内核 6.2.0-26-generic,CPU 2核 Intel Xeon Platinum 8374C,万兆内网。WireGuard 走 UDP 51820 端口,MTU 1420。

我的选择逻辑:内网互通、客户端漫游、弱网环境,这三个需求OpenVPN和IPsec也能做,但WireGuard用最少配置达到最好效果,尤其是在高丢包链路(10%丢包)下,WireGuard的TCP吞吐比OpenVPN高3.2倍。原因是WireGuard在用户态处理包太少,内核态直接转发。

完整搭建过程:三机房组网

网络拓扑

北京机房 (wg0: 10.0.0.1/24)
上海机房 (wg0: 10.0.0.2/24)
广州机房 (wg0: 10.0.0.3/24)

每个机房内网分别是 192.168.1.0/24、192.168.2.0/24、192.168.3.0/24
目标:三机房内网互通,并能通过VPN访问各机房的内部服务

三台服务器都有一个公网IP,不需要NAT穿透,但我会在客户端配置里加上PersistentKeepalive,确保后续加新机房时不需要改配置。

1. 安装 WireGuard

Ubuntu 22.04 内核 5.15+ 自带 wireguard 内核模块,只需要装工具链。这里有个坑:不要用 apt install wireguard 默认源,Ubuntu 22.04 官方源里的 wireguard-tools 版本是 1.0.20210914,但内核模块可能不是最新的。建议先确认内核模块。

# 在三台服务器上分别执行
sudo apt update
sudo apt install -y wireguard wireguard-tools

# 确认内核模块已加载
sudo modprobe wireguard
lsmod | grep wireguard
# wireguard 28672  0

# 确认版本
wg --version
# wireguard-tools v1.0.20210914 - https://git.zx2c4.com/wireguard-tools/

# 确认内核支持(5.6+内置)
uname -r
# 6.2.0-26-generic

如果你用的是 CentOS 9 Stream 或 Rocky Linux 9,需要额外启用 EPEL 源:dnf install epel-release,然后 dnf install wireguard-tools。内核模块在 el9 的内核中默认编译为模块,不需要额外安装。

2. 生成密钥对

WireGuard 使用 Curve25519 密钥对,一个私钥一个公钥。每台机器都需要独立的密钥对。

# 在三台服务器上分别执行(生成各自独立的密钥)
cd /etc/wireguard
umask 077

# 生成私钥
wg genkey | tee privatekey

# 从私钥推导公钥
wg pubkey < privatekey | tee publickey

# 输出结果(示例,实际每次生成都不同)
echo "--- 北京机房 ---"
cat privatekey
# 8CzF0yHzk3Kk8dR+oQvJqJQ1zXV6ZzM0xKUbPbqB1UY=
cat publickey
# +T4YxOQYzCkQeCcWjQ6BCFjO/YT7CkzN8yPzQn2J0Ew=

echo "--- 上海机房 ---"    # 在上海机器上执行
echo "--- 广州机房 ---"    # 在广州机器上执行

三台机器我都生成了,把公钥记录下来:

节点私钥(示例)公钥(示例)
北京2Nk...(保密)t5Zx...(公开)
上海8Yb...(保密)wQpR...(公开)
广州HfK...(保密)v3mN...(公开)

注意权限:umask 077 确保私钥文件只有root可读。WireGuard 对私钥文件权限要求严格,如果权限太宽松,wg-quick up 会直接报错。

3. 服务端配置(北京机房)

北京作为中心节点,但WireGuard是P2P的,没有真正的客户端/服务端之分。我习惯把第一个配置的当成主节点。

sudo vim /etc/wireguard/wg0.conf
# /etc/wireguard/wg0.conf - 北京机房
[Interface]
# 北京机房的私钥
PrivateKey = 2Nk...(北京私钥)
# WireGuard 隧道 IP
Address = 10.0.0.1/24
# 监听端口(UDP)
ListenPort = 51820
# 默认允许所有流量走隧道。如果要分流,改成 0.0.0.0/0 但配合路由表。
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

# 上海机房 peer
[Peer]
# 上海机房的公钥
PublicKey = wQpR...(上海公钥)
# 上海机房能访问的隧道IP段(上海分配了 10.0.0.2,同时允许访问北京内网)
AllowedIPs = 10.0.0.2/32, 192.168.1.0/24

# 广州机房 peer
[Peer]
PublicKey = v3mN...(广州公钥)
AllowedIPs = 10.0.0.3/32, 192.168.1.0/24

PostUp 里的 eth0 要改成你的实际网卡名。用 ip route 查看默认路由对应的网卡。北京这台机器网卡是 eth0,如果你的是 ens3 或 eno1,记得替换。

AllowedIPs 是 WireGuard 的访问控制列表。我允许上海和广州访问北京内网 192.168.1.0/24。这是通过路由实现的:WireGuard 会自动添加路由到路由表。

这里有个关键点:AllowedIPs 不止控制访问,还告诉本机怎么路由流量。如果写 0.0.0.0/0,就是全量VPN,所有流量走隧道。我们这里是点对点组网,所以写具体网段。

4. 客户端配置(上海机房)

sudo vim /etc/wireguard/wg0.conf
# /etc/wireguard/wg0.conf - 上海机房
[Interface]
PrivateKey = 8Yb...(上海私钥)
Address = 10.0.0.2/24
ListenPort = 51820
# 上海有公网IP,同样开启转发
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

[Peer]
PublicKey = t5Zx...(北京公钥)
# 上海需要访问北京内网 + 广州内网
# 路由:去 10.0.0.0/24 的走隧道,去 192.168.0.0/16 的走隧道
AllowedIPs = 10.0.0.0/24, 192.168.1.0/24, 192.168.3.0/24
# 指定北京的公网IP和端口
Endpoint = 北京公网IP:51820
# 每25秒发一次心跳,保持NAT映射
PersistentKeepalive = 25

注意:上海访问广州内网,数据包会先发给北京,再由北京转发给广州。这就是中心节点组网。WireGuard 支持 mesh,但需要每个节点都配置与其他节点的 peer。

这里 AllowedIPs 里的 192.168.3.0/24 不是直接路由到北京,而是告诉本机去往这些目标地址的包走 wg0 隧道。至于隧道内的下一跳怎么走,由 WireGuard 内部的路由决定。

5. 广州机房配置

# /etc/wireguard/wg0.conf - 广州机房
[Interface]
PrivateKey = HfK...(广州私钥)
Address = 10.0.0.3/24
ListenPort = 51820
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

[Peer]
PublicKey = t5Zx...(北京公钥)
AllowedIPs = 10.0.0.0/24, 192.168.1.0/24, 192.168.2.0/24
Endpoint = 北京公网IP:51820
PersistentKeepalive = 25

6. 启动并验证

# 三台机器分别执行
sudo systemctl enable wg-quick@wg0
sudo systemctl start wg-quick@wg0

# 查看状态(在任意一台执行)
sudo wg show

# 输出类似:
# interface: wg0
#   public key: t5Zx...(北京公钥)
#   private key: (hidden)
#   listening port: 51820
#   peer: wQpR...(上海公钥)
#     endpoint: 上海公网IP:51820
#     allowed ips: 10.0.0.2/32, 192.168.1.0/24
#     latest handshake: 1 minute ago
#     transfer: 1.2 MiB received, 3.4 MiB sent
#   peer: v3mN...(广州公钥)
#     endpoint: 广州公网IP:51820
#     allowed ips: 10.0.0.3/32, 192.168.1.0/24
#     latest handshake: 1 minute ago
#     transfer: 0.8 MiB received, 2.1 MiB sent

latest handshake 表示最近一次握手时间。如果显示 - 或超过2分钟,说明握手失败,检查防火墙和 Endpoint 配置。

然后从北京 ping 上海的隧道 IP:

# 北京执行
ping -c 3 10.0.0.2
# 64 bytes from 10.0.0.2: icmp_seq=1 ttl=64 time=8.42 ms
# 64 bytes from 10.0.0.2: icmp_seq=2 ttl=64 time=8.31 ms
# 64 bytes from 10.0.0.2: icmp_seq=3 ttyl=64 time=8.28 ms

# 验证跨机房内网访问(北京 ping 广州的内网机器)
ping -c 3 192.168.3.15
# 64 bytes from 192.168.3.15: icmp_seq=1 ttl=62 time=18.4 ms

ttl=62 说明经过了两次路由跳转(北京-广州隧道+广州内网路由),符合预期。

7. 路由转发与防火墙配置

如果内网其他机器也想通过 VPN 访问远端内网,需要做 NAT 或添加静态路由。我的方案是在服务器上做 MASQUERADE,这样内网机器不需要额外配置。

# 持久化 iptables 规则(Ubuntu 22.04)
sudo apt install -y iptables-persistent
sudo netfilter-persistent save

# 或者用 ufw 放行 WireGuard 端口
sudo ufw allow 51820/udp
sudo ufw allow from 10.0.0.0/24

# 确认 IP 转发已开启
sudo sysctl net.ipv4.ip_forward
# net.ipv4.ip_forward = 1
# 如果不是 1,执行:
echo 'net.ipv4.ip_forward=1' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p

wg-quick 脚本会自动设置 net.ipv4.ip_forward,但只对当前会话有效。持久化配置要写进 sysctl.conf。

还有一个细节:如果服务器用 UFW 防火墙,需要放行 FORWARD 链。wg-quick 的 PostUp 已经加了 ACCEPT 规则,但 UFW 默认策略可能比较激进。我建议用 iptables-persistent 管理,避免 UFW 和 iptables 规则冲突。

Production级配置:性能调优

基础配置能通,但距离生产可用还差几个关键优化。下面是压测数据和对应调优参数。

性能对比:WireGuard vs OpenVPN 实测

测试工具:iperf3 3.12,TCP 窗口默认,测试时长60秒。分别在直连、WireGuard、OpenVPN三种模式下的表现:

测试项直连(基准)WireGuardOpenVPN
TCP 吞吐(iperf3 单线程)9.41 Gbps2.12 Gbps1.06 Gbps
TCP 吞吐(iperf3 8线程)9.87 Gbps2.38 Gbps1.12 Gbps
UDP 小包(64字节)N/A812 Mbps156 Mbps
ICMP 延迟(吞吐同时测)0.3 ms0.5 ms1.8 ms
首次握手延迟(客户端回连)N/A0.12 s2.3 s
CPU 占用(服务端单核)0%28%67%

WireGuard 吞吐是 OpenVPN 的2倍,CPU 占用只有 OpenVPN 的41%,延迟低 1.3ms。在 10% 丢包链路上,WireGuard 的 TCP 吞吐 412 Mbps,OpenVPN 只有 128 Mbps。

原因是 WireGuard 完全在内核态运行,数据包不经过用户态拷贝。ChaCha20-Poly1305 用 AVX2 指令集加速,比 OpenSSL 的 AES-NI 在短包场景效率更高。

MTU 调优

WireGuard 默认 MTU 1420,是为互联网标准 1500 减去 80 字节 WireGuard 头部预留的。但如果你在云机房内网(内网 MTU 通常是 1500 或 9000),可以调大。

我踩过坑:把 MTU 调到 1500 后,ping 10.0.0.2 -s 1400 能通,但 scp 大文件间歇性卡死。查了 Wireshark 才发现是分片重组问题。

正确做法:ping -M do -s 1372 从 1372 往上试,找到最大不分片大小。我这边电信机房到北京阿里云,实测 MTU 1360 才稳定。

# /etc/wireguard/wg0.conf 修改 MTU
[Interface]
PrivateKey = ...
Address = 10.0.0.1/24
ListenPort = 51820
MTU = 1360

[Peer]
...

修改 MTU 后重启 systemctl restart wg-quick@wg0,再用 ping -M do -s 1372 验证。

多队列优化

服务器 CPU 多核时,默认 WireGuard 只用单队列,跑不满多核。需要开启多队列支持。

# 查看当前队列数
ethtool -l eth0
# Channel parameters for eth0:
# Pre-set maximums:
# RX: 0
# TX: 0
# Other: 0
# Combined: 8
# Current hardware settings:
# RX: 0
# TX: 0
# Other: 0
# Combined: 4

# 开启网卡多队列
sudo ethtool -L eth0 combined 8

# 确认 WireGuard 使用的 RX 队列被分散到不同 CPU
sudo cat /proc/interrupts | grep -E "eth0|wg0"

同时配置 RPS(Receive Packet Steering),让软中断分散到多个 CPU:

echo "f" | sudo tee /sys/class/net/eth0/queues/rx-0/rps_cpus
echo "f" | sudo tee /sys/class/net/eth0/queues/rx-1/rps_cpus
echo "f" | sudo tee /sys/class/net/eth0/queues/rx-2/rps_cpus
echo "f" | sudo tee /sys/class/net/eth0/queues/rx-3/rps_cpus

# 查看是否生效
cat /sys/class/net/eth0/queues/rx-0/rps_cpus
# f

注意 f 是 CPU 掩码,表示 0-3 号 CPU。如果服务器是 8 核,改成 ff。这个优化在 4 核以上机器效果明显。我优化后,WireGuard 单隧道吞吐从 2.1 Gbps 提升到 2.38 Gbps,但 CPU 占用从 28% 降到 21%。

Mesh组网:三机房全互联

如果 A 与 C 之间流量大,经过 B 转发会增加延迟。WireGuard 支持全互联 mesh,每个节点直接与其他节点建立隧道。

改法:把每个 peer 的 Endpoint 都配置上,同时 AllowedIPs 把其他两个机房的内网段都写上。

# 北京(wg0.conf 增加上海和广州的直连配置)
[Peer]
PublicKey = wQpR...(上海公钥)
Endpoint = 上海公网IP:51820
AllowedIPs = 10.0.0.2/32, 192.168.2.0/24
PersistentKeepalive = 25

[Peer]
PublicKey = v3mN...(广州公钥)
Endpoint = 广州公网IP:51820
AllowedIPs = 10.0.0.3/32, 192.168.3.0/24
PersistentKeepalive = 25
# 上海(wg0.conf 增加广州直连)
[Peer]
PublicKey = v3mN...(广州公钥)
Endpoint = 广州公网IP:51820
AllowedIPs = 10.0.0.3/32, 192.168.3.0/24
PersistentKeepalive = 25

改动后,上海到广州直接走隧道,不再经过北京转发。实测延迟从 18ms 降到 9ms(上海到广州物理延迟本来就在 8ms 左右)。

mesh 模式的坑在于:如果某个节点内网互通(比如广州和上海之间有专线),WireGuard 路由表会冲突。需要精确控制 AllowedIPs,避免路由环路。我的做法:每个节点只写自己需要访问的目标网段,不写超大网段如 0.0.0.0/0。

避坑指南(我实际踩过的)

这部分是重点,每个坑都是我花了时间定位的。

坑1:systemd 管理下 wg-quick 启动失败

症状:systemctl start wg-quick@wg0 报错 Operation not permitted

原因:Ubuntu 22.04 的 systemd 250+ 默认限制了网络命名空间权限,WireGuard 需要 CAP_NET_ADMIN

解决:检查服务状态 journalctl -u wg-quick@wg0 -n 50,看完整报错。如果是权限问题,用 sudo systemctl restart systemd-networkd 后再启动。这不是 WireGuard 的问题,是 systemd 的防护机制。

坑2:公网 IP 变化导致隧道中断

症状:客户端 IP 变了,wg show 显示 last handshake 一直不更新。

原因:对端 Endpoint 还指向旧 IP。

解决:客户端开启 PersistentKeepalive = 25。服务端不用做任何配置,WireGuard 会自动学习客户端的最新 Endpoint。但如果服务端 IP 变了,客户端必须在配置里更新 Endpoint。没有动态 DNS 的话,写个 cron 脚本检查 IP 变化并重启 wg-quick。

坑3:AllowedIPs 写错导致路由异常

症状:能 ping 通隧道 IP,但访问对端内网超时。

原因:AllowedIPs 控制的是「本机认为哪些 IP 应该走这个隧道」,如果写漏了,内核会尝试通过默认路由访问远端内网,自然不通。

解决:把需要访问的远端内网段全部写进 AllowedIPs。注意是「远端」的网段,不是本端的。如果本端是上海,要访问北京内网,AllowedIPs 里必须写 192.168.1.0/24。

坑4:MTU 问题导致大包丢包

症状:小包正常,大文件传输卡死,ping 大包丢包。

原因:WireGuard 默认 MTU 1420,但如果物理链路有额外的 PPPoE(宽带拨号)或隧道叠加,实际可用 MTU 更小。

解决:从 1400 往下试,ping -M do -s 1372 10.0.0.2。如果 1372 通了,再试 1400,找到临界值。我最终设在 1360。一定要用 -M do 禁止分片,否则测不出来。

坑5:多网卡服务器上 iptables MASQUERADE 不生效

症状:VPN 能连通,但内网机器访问不了对端。

原因:PostUp 里写死了 -o eth0,但服务器的出口网卡可能是 eth1 或 bond0。

解决:用 ip route 查默认路由,确认出口网卡。或者写成自动探测:

# 自动探测默认网卡
DEFAULT_IF=$(ip route | grep default | awk '{print $5}' | head -n1)
echo "默认网卡: $DEFAULT_IF"
# 然后在 PostUp 里用变量
PostUp = iptables -t nat -A POSTROUTING -o $DEFAULT_IF -j MASQUERADE

注意:wg-quick 的 PostUp 是单行命令,不支持变量。我建议写成独立脚本挂在 PostUp 里:

# /etc/wireguard/postup.sh
#!/bin/bash
DEFAULT_IF=$(ip route | grep default | awk '{print $5}' | head -n1)
iptables -A FORWARD -i wg0 -j ACCEPT
iptables -t nat -A POSTROUTING -o "$DEFAULT_IF" -j MASQUERADE
# wg0.conf 里这样引用
PostUp = /etc/wireguard/postup.sh

坑6:重启服务器后 WireGuard 不自动启动

症状:重启后隧道没起来。

原因:systemd service 没 enable。

解决:sudo systemctl enable wg-quick@wg0。但注意:如果配置文件不是 wg0.conf,而是别的名字如 vpn.conf,要改成 wg-quick@vpn

坑7:云机房安全组忘记放行 UDP

症状:本机启动正常,但对端一直握手失败。

原因:云服务商安全组(如阿里云安全组、腾讯云防火墙)默认不放行 UDP 51820。服务器内部防火墙放了也没用,数据包在云平台层就被拦了。

解决:登录云控制台,在安全组入口方向放行 UDP 51820。记得同时放行 TCP 如果要用 Web 管理界面。

结语

WireGuard 不是万能的。它没有 OpenVPN 的插件生态,加密套件固定不能更换,某些合规场景可能过不了审。但 90% 的站点到站点、客户端到站点场景,它是最优解:配置最少、性能最好、容易排查。

我用这套方案在 35 分钟内搞定了原本要三天的组网任务。WireGuard 没有 keepalive 的维护负担,没有证书轮换的定时炸弹。遇到问题,wg show + tcpdump port 51820 基本能定位 90% 的故障。

如果你还在犹豫要不要迁移,我的建议是:新项目直接用 WireGuard,老项目用双跑测试替换。风险很低,收益明显。

有问题可以直接在评论区贴 wg show 的输出,我尽量回答。

FAQ 快速排查

Q: 握手超时,wg show 显示 latest handshake 是 "-"

依次检查:1)防火墙/安全组是否放行 UDP 51820;2)Endpoint 地址和端口是否正确;3)用 tcpdump -i eth0 udp port 51820 抓包看是否收到对端的包;4)AllowedIPs 是否匹配。

Q: 能 ping 通隧道 IP,但访问内网失败

查路由:ip route show table all | grep wg0。如果路由缺失,检查 AllowedIPs 是否写了远端网段。注意:AllowedIPs 只是路由的「生效条件」,WireGuard 会自动添加路由,但需要触发。用 ping 对端隧道IP 触发一次握手,然后 ip route 确认。

Q: 客户端 Mac/Windows 连不上服务端

官方客户端要求服务端必须支持应答,默认就行。但 Mac 客户端有个坑:如果上一次连接没有正常断开,下次连接会直接失败。解决:在 Mac 的网络设置里删掉 VPN 配置重新添加。

附录:完整配置文件模板

# 一键生成密钥(在 /etc/wireguard 下执行)
wg genkey | tee privatekey | wg pubkey > publickey
cat privatekey
# 中心节点服务器模板(对应机房 A)
[Interface]
PrivateKey = [A的私钥]
Address = 10.0.0.1/24
ListenPort = 51820
PostUp = /etc/wireguard/postup.sh
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE

[Peer]
PublicKey = [B的公钥]
AllowedIPs = 10.0.0.2/32, 192.168.2.0/24
Endpoint = [B的公网IP]:51820
PersistentKeepalive = 25

[Peer]
PublicKey = [C的公钥]
AllowedIPs = 10.0.0.3/32, 192.168.3.0/24
Endpoint = [C的公网IP]:51820
PersistentKeepalive = 25
# 分支节点模板(对应机房 B)
[Interface]
PrivateKey = [B的私钥]
Address = 10.0.0.2/24
ListenPort = 51820
PostUp = /etc/wireguard/postup.sh

[Peer]
PublicKey = [A的公钥]
AllowedIPs = 10.0.0.0/24, 192.168.1.0/24, 192.168.3.0/24
Endpoint = [A的公网IP]:51820
PersistentKeepalive = 25
# 日常维护命令速查
# 查看状态
sudo wg show
# 重启隧道
sudo systemctl restart wg-quick@wg0
# 完整日志
sudo journalctl -u wg-quick@wg0 -f
# 抓包排查(UDP 51820)
sudo tcpdump -i eth0 udp port 51820 -nn
# 测试吞吐
iperf3 -c 10.0.0.1 -t 30 -P 4