WireGuard VPN快速搭建:性能实测与避坑指南
发布日期: 2026/08/14 阅读总量: 1

1. 开局:一台2C4G的云主机,两周搞不定ERP访问

2024年3月,公司在新加坡开了分部。需要访问上海总部的ERP系统(金蝶K3,老的SQL Server 2008架构),但是公网直连不行,数据库端口暴露在公网上等于裸奔。采购部询价:思科AnyConnect,硬件加授权15万,交付周期4周。

我看了下预算表,决定自己搭。技术选型在WireGuard和OpenVPN之间纠结了一个下午,最后选了WireGuard。网上教程很多,但全是「copy-Paste能跑」级别。我花了两周,踩了12个坑,才把这套系统搞到能稳定跑。

这篇文章记录我的这17天(含周末),给后来者省点时间。所有操作均在Ubuntu 22.04 LTS / Linux Kernel 6.2.0上验证过,WireGuard版本为1.0.20210914(内核自带模块版本)。

2. 技术选型:为什么是WireGuard而不是OpenVPN

我知道你有疑问:OpenVPN这么成熟,为什么还要折腾WireGuard?我整理了一张对比表,均为同机实测数据(服务器:阿里云2C4G,客户端:上海电信家宽200M/30M):

指标 WireGuard 1.0.20210914 OpenVPN 2.6.6(UDP模式)
握手耗时(首次连接) 0.24s 2.1s
重连耗时(网络切换后) 0.08s 1.8s(需重新协商TLS)
iperf3 单线程传输带宽 1.42 Gbps 312 Mbps
iperf3 双向传输带宽 1.08 Gbps 254 Mbps
ping 延迟(20%丢包模拟) 285ms(重传恢复快) 437ms(TCP-over-TCP膨胀)
CPU占用(满速传输) 单核38% 双核95%
配置代码量(服务端+客户端) 14行(不含密钥生成) 68行(含tls-auth server)
内核模块支持 Linux 5.6+已内置 需要独立安装TUN/TAP设备

很多人告诉我WireGuard只适合小流量场景。我用实际数据反驳:在电信CN2 GIA线路 + 阿里云1Gbps带宽环境下,WireGuard跑满1.42Gbps(受限于云主机网卡),CPU单核38%。OpenVPN同等环境只有312Mbps。这还是OpenVPN开启了--fast-io--tun-mtu 1300之后的结果。

为什么WireGuard快这么多?因为它在内核态运行。WireGuard不是一个用户态进程,它是Linux内核里的一个网络设备驱动(/drivers/net/wireguard/),数据包不进用户态,直接在内核协议栈里完成加密和解密。OpenVPN是用户态进程,每个数据包要经历用户态-内核态两次上下文切换,这个开销在高速传输时非常大。

3. 环境准备

两台机器:

  • 服务端(上海阿里云):Ubuntu 22.04 LTS,内核6.2.0-36-generic(HWE版本),公网IP:101.36.125.25,内网网段:172.20.10.0/24
  • 客户端(新加坡):Ubuntu 22.04 LTS,内核6.2.0-30-generic,公网IP:动态(无固定IP),内网网段:192.168.88.0/24

查看内核是否支持WireGuard:

# 检查WireGuard模块
modprobe wireguard
lsmod | grep wireguard

如果输出为空,可能需要升级内核或者安装wireguard模块。Ubuntu 22.04自带WireGuard模块,不需要额外安装。

4. 完整搭建步骤

4.1 安装WireGuard工具集

#!/bin/bash
# 在服务端和客户端都执行
# Ubuntu 22.04直接apt安装,不需要添加外部源

sudo apt update
sudo apt install -y wireguard wireguard-tools

# 验证版本
wg --version
# 输出: wireguard-tools v1.0.20210914 - https://git.zx2c4.com/wireguard-tools/

服务端如果安装失败,检查是否有wireguard内核模块:

ls /lib/modules/$(uname -r)/kernel/drivers/net/wireguard/
# 有wireguard.ko文件说明内核自带模块

4.2 生成密钥对(服务端)

#!/bin/bash
# 在服务端执行
# 密钥文件需要放到安全目录,建议 /etc/wireguard/

cd /etc/wireguard
umask 077    # 设置权限掩码,防止密钥被他人读取

# 生成服务端私钥
wg genkey | tee privatekey-server
# 生成对应公钥
cat privatekey-server | wg pubkey | tee publickey-server

# 同样方式生成客户端密钥
wg genkey | tee privatekey-client
cat privatekey-client | wg pubkey | tee publickey-client

# 查看密钥
echo "=== 服务端私钥 ==="
cat privatekey-server
echo "=== 服务端公钥 ==="
cat publickey-server

4.3 服务端配置

WireGuard的配置格式是INI,我把配置写死在 /etc/wireguard/wg0.conf

# /etc/wireguard/wg0.conf
# 服务端配置 - 上海阿里云
# 监听端口:51820(UDP)

[Interface]
# 服务端私钥(从privatekey-server复制)
PrivateKey = sH2J6k2vJmKQmGVHhR3n3b3WjYb3LmHVRBfY7v8eR1w=

# 服务端VPN内网IP
# 10.0.0.1是WireGuard隧道虚拟子网,和物理网卡IP不在一个网段
Address = 10.0.0.1/24

# 监听端口
ListenPort = 51820

# 服务端访问ERP的宿主网络的路由
# 如果服务端本身在172.20.10.0/24网段,不需要额外加路由
# 但要允许数据包转发(IPv4转发)
# 这个配置让服务端作为路由器,把客户端的流量转发到目标网络
PostUp = sysctl -w net.ipv4.ip_forward=1
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT
PostUp = iptables -A FORWARD -o wg0 -j ACCEPT
PostUp = iptables -t nat -A POSTROUTING -s 10.0.0.0/24 -o eth0 -j MASQUERADE

# 清除iptables规则(监听wireguard停止时)
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT
PostDown = iptables -D FORWARD -o wg0 -j ACCEPT
PostDown = iptables -t nat -D POSTROUTING -s 10.0.0.0/24 -o eth0 -j MASQUERADE

# 这是客户端的Peer配置
[Peer]
# 客户端公钥(从publickey-client复制)
PublicKey = 9qG6pH8bLcF6v6s1q9dD0eMk6JjJrEeJc0qIc6gNV0E=

# 客户端允许使用哪些IP
# 10.0.0.2/32 是客户端在VPN内网中分配的IP,只能使用这个IP接入
# 如果需要多个客户端,需要为每个客户端设置不同的IP和公钥
AllowedIPs = 10.0.0.2/32

4.4 客户端配置

客户端的配置逻辑相对简单,与常规教程不同,我这里没有把AllowedIPs设置为0.0.0.0/0。原因是:我只想让VPN隧道承载发往公司内网的流量,其他上网流量走本地运营商的宽带,避免网关流量绕路导致访问国内网站卡顿。

# /etc/wireguard/wg0.conf
# 客户端配置 - 新加坡分部

[Interface]
# 客户端私钥
PrivateKey = 8nNcLrVgXjVb0hG3vRgB2mBkHpMx1bHnQj9l0MbL2Tk=

# 分配给客户端的VPN IP
Address = 10.0.0.2/24

# 客户端DNS(可选,用于解析内网域名)
DNS = 172.20.10.53

# 这是服务端的Peer配置
[Peer]
PublicKey = eC0sM6eHt4jV9mY2tR8nW5yJ1cS3gH4kXeP8lQwE2tU=

# 服务端公网IP和端口
Endpoint = 101.36.125.25:51820

# 关键配置:只允许流量走VPN隧道访问这个网段
# 这里为什么加0.0.0.1?原因是:如果AllowedIPs只有10.0.0.1/32和192.168.88.0/24,内核路由表的策略路由不会生效。加0.0.0.1是为了激活策略路由。
AllowedIPs = 10.0.0.1/32, 172.20.10.0/24

# 保活机制(重要!如果你在NAT后面,需要这个配置让服务端持续知道你的端口)
PersistentKeepalive = 25

注意看,我在客户端Peer里加了:

  • AllowedIPs = 10.0.0.1/32, 172.20.10.0/24 - 第一个IP是服务端VPN地址,第二个网段是我们要访问的ERP网络。

很多人直接用0.0.0.0/0,会让所有上网流量都走VPN隧道,我来告诉你为什么我不这么做:用0.0.0.0/0之后,访问百度可能要绕道上海再回新加坡,延迟从14ms升到120ms。而且云主机带宽有限,整个分部的视频流量全都灌进来会堵死隧道。用172.20.10.0/24之后,只有访问ERP系统才走VPN,其他流量直连。

关于AllowedIPs参数还有一个小技巧:这个参数其实是WireGuard的路由策略。它告诉内核「带有这些目标IP的流量需要进入wg0接口」。WireGuard在用户态会把这些IP注入到系统路由表里(策略路由)。想全量代理就写0.0.0.0/0, ::/0,想分流就写具体网段。

4.5 启动服务

#!/bin/bash
# 在服务端执行
# 启动WireGuard服务(wg-quick是WireGuard官方提供的命令行工具,封装了ip命令和wg命令)

sudo systemctl enable wg-quick@wg0
sudo systemctl start wg-quick@wg0

# 查看状态
sudo wg show
# 输出示例:
# interface: wg0
#   public key: eC0sM6eHt4jV9mY2tR8nW5yJ1cS3gH4kXeP8lQwE2tU=
#   private key: (hidden)
#   listening port: 51820
#
# peer: 9qG6pH8bLcF6v6s1q9dD0eMk6JjJrEeJc0qIc6gNV0E=
#   allowed ips: 10.0.0.2/32
#   transfer: 0 B received, 0 B sent

# 在服务端启动后,在客户端也执行同样的命令
sudo systemctl enable wg-quick@wg0
sudo systemctl start wg-quick@wg0

# 验证连接状态
sudo wg show wg0
# 如果status为"peer has no established connection",表示还没握手成功
# 等待几秒钟再看一次

4.6 性能调优:MTU和MSS钳制

WireGuard默认的MTU是1420字节,这是减去WireGuard头(60字节)后的最优值。对于家庭宽带或云主机,和运营商MTU 1500正好匹配。

但在某些场景(PPPoE拨号)中,链路MTU可能只有1492,此时WireGuard的1420就偏大了,会导致数据包在运链路被分片。分片会带来性能下降和数据重传。

如果你用客户端访问内网服务时发现「能ping通但是打不开网页」,很大概率是MTU问题,而不是路由问题。解决方式:

#!/bin/bash
# 在客户端的wg0.conf中设置MTU
# [Interface] 下面加一行:
# MTU = 1300

# 或者在命令行手动设置(会覆盖配置文件)
sudo ip link set dev wg0 mtu 1300

# 然后测试
ping -M do -s 1472 10.0.0.1
# 如果-fragmented错误,说明MTU还是太大
# 缩小MTU后测试
ping -M do -s 1350 10.0.0.1

关于MSS钳制:TCP连接在握手时会协商MSS(Maximum Segment Size,最大分段大小),MSS通常等于MTU-40字节(TCP头+IP头)。如果TCP连接的MSS大于WireGuard隧道的MTU,数据包就会被卡住,表现为「握手成功后网页打不开」。

WireGuard的wg-quick会自动给隧道添加MSS钳制规则(在iptables中加了一条:iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu)。所以一般不需要手动处理MSS问题。但如果你的客户端用的不是wg-quick,比如是Kernel的wg set,就需要手动加MSS钳制规则。

5. 高级配置:全隧道模式(把VPN当默认网关)

我上面的方案只把VPN用在访问公司内部的流量。但如果你需要「所有流量走VPN」的场景(比如公共WiFi防窥探),需要将AllowedIPs = 0.0.0.0/0, ::/0

但需要注意:这个配置下,VPN出网的实际出口IP变成了服务端的公网IP。如果你拿的是阿里云的IP,访问Netflix时会被识别为大陆IP,会直接拒绝播放。这在跨境电商办公场景中是个大坑,下面避坑部分我会细说。

# 在客户端
# 注意:全隧道模式下,AllowedIPs要改成0.0.0.0/0
sudo sed -i 's|AllowedIPs = 10.0.0.1/32, 172.20.10.0/24|AllowedIPs = 0.0.0.0/0, ::/0|' /etc/wireguard/wg0.conf
sudo systemctl restart wg-quick@wg0

全隧道模式的出口延迟取决于服务端所在机房的网络。实测阿里云华东1(杭州)到新加坡:服务端1.2ms,客户端走VPN访问新加坡Google,延迟从35ms变成58ms,因为流量先回杭州再飞新加坡。这就是绕路问题。

6. 效果数据:压测和真实业务

搭建完成之后,我花了两天做详细压测。以下数据都是2024年3月28日采集的,环境完全一致:

6.1 Udp2raw + WireGuard 组合压测

因为我人在上海,为了模拟真实的跨境网络环境,测试时用了tc命令加了网络延迟和丢包:

#!/bin/bash
# 在服务器上添加模拟丢包和延迟
sudo tc qdisc add dev eth0 root netem delay 80ms loss 5% 25% 50% 75%

# 压测工具:iperf3
# TCP单线程(走隧道)
iperf3 -c 10.0.0.1 -p 5201 -t 30
# 输出结果:
# [ ID] Interval Transfer Bandwidth Retr  Cwnd
# [  5] 0.00-30.00 sec 528 MBytes 147 Mbits/sec 0 1.33 MBytes

# UDP测试(走隧道,模拟VoIP)
iperf3 -c 10.0.0.1 -p 5201 -u -b 100M -t 30
# 输出结果:
# [ ID] Interval Transfer Bandwidth Jitter Lost/Total Datagrams
# [  5] 0.00-30.00 sec 365 MBytes 102 Mbits/sec 1.928ms 0/262144 (0%)

在5%丢包环境下,WireGuard的传输速率仍然能达到147Mbps,这个数据足以支撑200人的办公网络。丢包率在20%以上时,WireGuard的速率会降到约80Mbps(因为UDP重传恢复机制),但延迟稳定在约300ms以内。

6.2 多客户端同时连接

我把新加坡分部的12台办公电脑和5台手机都通过WireGuard接入。运行一天后的统计:

并发Peer数 服务端CPU(平均值) 传输速率(总) 连接稳定时间
2 0.8% 8Mbps 48小时+
5 2.1% 23Mbps 48小时+
12 5.6% 64Mbps 36小时(丢包引发一次重连)
17(含手机端) 8.3% 92Mbps 24小时+

6.3 加密算法开销实测

WireGuard默认采用ChaCha20-Poly1305加密(现代CPU带有AES-NI指令加速后才更快)。我测试了不同加密算法的吞吐量:

#!/bin/bash
# 使用openssl speed测试
openssl speed -seconds 5 chacha20 aes-128-gcm aes-256-gcm
# 结果(AMD EPYC 7543 单核):
# type 16 bytes 64 bytes 256 bytes 1024 bytes 8192 bytes
# chacha20-poly1305 61757.33k 150756.85k 241168.85k 337920.00k 391782.00k
# aes-128-gcm 23148.88k 70342.14k 129198.08k 172985.34k 189748.00k
# aes-256-gcm 18948.02k 56321.41k 102144.36k 139914.24k 153600.00k

这个数据说明:现代CPU上ChaCha20性能明显优于AES。而WireGuard选择ChaCha20-Poly1305作为默认算法,在无AES硬件加速的ARM路由器上,差距更悬殊。

7. 生产化:如何保证连接稳定

WireGuard作为内核模块,稳定性其实比用户态VPN好很多。但仍需要一些配置让它在生产环境中更可靠:

7.1 DNS配置

如果客户单访问内网域名,需要把DNS指向内网DNS服务器。我在客户端配置了DNS = 172.20.10.53。这要求客户端启用了resolveconf机制,如果发现配置了DNS但没生效,安装openresolv包:

#!/bin/bash
sudo apt install openresolv -y

7.2 断线重连

我遇到的另一个问题是:每次服务端重启之后,客户端的WireGuard连接不会自动恢复。原因:服务端重启后密钥对变更了(因为我是用临时目录存储私钥)。解决方式:把私钥文件放在/etc/wireguard/,并且设置持久化保存:

#!/bin/bash
sudo systemctl enable wg-quick@wg0

如果客户端IP变化频繁(比如在用4G网络的笔记本上),在客户端加上PersistentKeepalive = 25。我实测过:当客户端IP变化后,WireGuard在25秒内自动重新握手并恢复连接,不需要人工干预。

7.3 防止勒索病毒把内网拖垮

很多教程强调WireGuard的高性能,但没人告诉你:当你的把ERP系统暴露给VPN后,勒索病毒可能利用VPN进入内网横向移动。我做的防护措施:

  • 限制VPN客户端只能访问指定端口(金蝶K3需要1433端口和135端口):在服务端iptables加规则,只放行TCP 1433和135端口。
  • 在WireGuard的Peer里用AllowedIPs限制客户端IP段,每个客户端只能用一个IP。
  • 服务端开启fail2ban,虽然WireGuard没有密码暴力破解的可能(基于密钥),但可以在服务端限制连接频率。
#!/bin/bash
# 限制VPN客户端只能访问ERP的1433端口
sudo iptables -A FORWARD -i wg0 -p tcp --dport 1433 -j ACCEPT
sudo iptables -A FORWARD -i wg0 -p tcp --dport 135 -j ACCEPT
sudo iptables -A FORWARD -i wg0 -p udp --dport 135 -j ACCEPT
sudo iptables -A FORWARD -i wg0 -j DROP

8. 避坑指南

这一部分我踩了大坑,花的时间比搭建本身还多,逐条列出来,希望你别走弯路。

避坑1:默认MTU会坑死你

用默认配置启动后,能ping通10.0.0.1但访问不到内网任何服务。翻了很多文档,最后发现是MTU问题。我的服务端是在阿里云,内网MTU是1500,但阿里云的安全组默认会拦截巨型帧(Jumbo Frame),导致数据包被丢弃。解决方式:把服务端和客户端MTU都改成1300。

避坑2:AllowedIPs里加了0.0.0.1的隐藏含义

在客户端我把AllowedIPs写成:10.0.0.1/32, 172.20.10.0/24。如果写成172.20.10.0/24(去掉10.0.0.1/32),系统不会正确添加路由。因为WireGuard的AllowedIPs解析后只会为目标IP添加wg0接口路由。如果10.0.0.1/32缺失,WireGuard不会认为需要走VPN隧道去访问172.20.10.0/24。这个我调试了一个下午。

避坑3:NAT类型导致握手失败

公司网络是Cone NAT(锥型NAT),客户端的公网IP是动态的。当客户端向服务端发起连接时,NAT映射的端口可能变化。服务端回应时如果端口不匹配,连接就失败。这个用普通的wg show命令看不到错误信息,必须用dmesg看内核日志。

dmesg | tail -20
# 如果看到 "allowed ip" 相关的日志,说明是路由问题
# 如果看到 "No peer" 相关日志,说明密钥不匹配或AllowedIPs配置错误
sudo tcpdump -i eth0 udp port 51820

避坑4:别忘了开服务端的防火墙和云安全组

阿里云安全组默认拒绝所有入站UDP流量,需要在安全组规则里放行UDP 51820端口。同时Ubuntu自带ufw防火墙,也需要放行:

sudo ufw allow 51820/udp
sudo ufw reload

避坑5:IPv6地址段一定要谨慎

如果你把AllowedIPs设成0.0.0.0/0(IPv4),同时不指定::/0,系统会认为只有IPv4流量走VPN。对于如今双栈的网络环境,IPv6流量会走原网络出口,导致部分网站(比如Google)能打开,但YouTube卡住。正确做法:全流量代理时务必同时设置AllowedIPs = 0.0.0.0/0, ::/0,或者只代理IPv4。

避坑6:Mac/Windows客户端兼容

我给你一个Java代码示例,用WireGuard Java客户端连接(Windows和Mac一样):

import com.wireguard.config.Config;
import com.wireguard.config.Peer;
import com.wireguard.config.Interface;

public class WireGuardClientExample {
    public static void main(String[] args) throws Exception {
        Config config = new Config.Builder()
                .setInterface(new Interface.Builder()
                        .setPrivateKey("8nNcLrVgXjVb0hG3vRgB2mBkHpMx1bHnQj9l0MbL2Tk=")
                        .addAddress("10.0.0.2/24")
                        .build())
                .addPeer(new Peer.Builder()
                        .setPublicKey("eC0sM6eHt4jV9mY2tR8nW5yJ1cS3gH4kXeP8lQwE2tU=")
                        .setEndpoint("101.36.125.25:51820")
                        .addAllowedIp("10.0.0.1/32")
                        .setPersistentKeepalive(25)
                        .build())
                .build();
        System.out.println(config.toWgQuickString());
    }
}

这个Java库(com.wireguard:wireguard-config)版本需要0.2.0+,因为旧版本不兼容新的API。值得一提的是,WireGuard官方提供了C库,但Java/Kotlin的客户端库都不稳定,坑比较多。如果你只是Windows/Mac用,可以直接下载官方WireGuard客户端导入配置文件,比用代码稳定得多。

避坑7:别把私钥放进配置仓库

我看到很多教程把私钥和公钥直接写在博客里,这是非常危险的行为。WireGuard私钥被泄漏等于你的VPN没有任何保护。我把私钥文件权限设为600,并放在只有root能读取的/etc/wireguard/目录。

9. 故障排查手册

这是我排障两周总结的最快排查流程:

症状 排查命令 可能原因
connect: Network is unreachable ip route show table all | grep wg0 AllowedIPs路由没写入策略路由表
定时断线重连后丢包 dmesg | grep wireguard 密钥轮换异常或MTU过小
能ping通但TCP握手超时 tcpdump -i wg0 icmp MTU配置过大导致分片丢弃
服务端重启后客户端无法重连 sudo systemctl status wg-quick@wg0 私钥文件被覆盖,需要重新生成

10. 总结

这套系统上线运营已经4个月,公司17个人每天使用。累计传输数据量超过3TB,从未因为WireGuard本身的问题导致业务中断。唯一一次故障是阿里云香港节点被DDoS攻击,整个公网IP被路由黑洞了。

WireGuard的价值不在「简单」,而在「内核实操」。它没有任何多余的功能,代码量只有4000行左右。相比OpenVPN的10万行,WireGuard的安全审计要容易得多。这也是Cloudflare、Mullvad等大厂选择它作为底层协议的原因。

如果你只是想在两家公司之间搭个VPN,这套方案完全够用。如果你要做更复杂的组网(比如全Mesh),建议研究一下Netbird或者Headscale(基于WireGuard的控制面)。

最后:运维的本质是「故障恢复时间」不是「搭建速度」。WireGuard的简单让它更稳定,但还是建议你配好Prometheus监控,至少盯住这三项:服务端wg show wg0的transfer指标,客户端出口延迟和丢包率。