如何在VPS上自建WireGuard VPN
从密钥生成和wg0.conf开始,配置IP转发、NAT、AllowedIPs与DNS,并排查握手失败、权限错误和容器虚拟化不支持等问题。
构建内容
在您拥有的服务器上搭建 WireGuard VPN,大约只需要40行配置:一对密钥、一个接口文件、一个 sysctl 设置、一条 NAT 规则和一个防火墙放行规则。安装过程很简单,因此本指南的大部分内容都介绍常见故障、密钥权限、AllowedIPs、转发和 DNS。
WireGuard 是内核中的第3层隧道,自 Linux 5.6 起已进入主线内核,因此 Ubuntu 24.04 和 Debian 13 无需外部模块即可提供 WireGuard。它不进行密码套件协商,不需要证书颁发机构,也没有用户名/密码验证步骤:对等端由一个公钥以及该密钥允许使用的 IP 地址组成。MAC 校验失败的数据包会直接丢弃且不回复,因此该端口不会响应扫描。另一方面,WireGuard 不存在认证服务器,因此要撤销访问权限,必须在服务器上删除对应的对等端。
先检查虚拟化类型
WireGuard 需要能够加载模块的内核,在 KVM VPS 上通常无需额外配置即可运行。对于共享主机内核的容器虚拟化环境(如 OpenVZ、LXC),第一个命令会失败并显示 RTNETLINK answers: Operation not supported,此时应改用 wireguard-go 用户态实现。先使用 sudo modprobe wireguard && echo ok 检查虚拟化类型。
生成密钥,避免泄露
任何用户都可读取的 /etc/wireguard/server.key 等同于完全没有 VPN。常见的 umask 077 && wg genkey | sudo tee ... 命令不可靠,因为 sudo 会对 tee 创建的文件应用自己的 umask。请显式设置文件模式。
sudo apt update && sudo apt install -y wireguard nftables
sudo install -d -m 700 /etc/wireguard
sudo sh -c 'umask 077; wg genkey > /etc/wireguard/server.key'
sudo sh -c 'wg pubkey < /etc/wireguard/server.key > /etc/wireguard/server.pub'
sudo chmod 600 /etc/wireguard/server.key以相同方式生成客户端密钥对。wg genpsk 会额外生成一个可选的预共享密钥,并
服务器接口:/etc/wireguard/wg0.conf
[Interface]
Address = 10.8.0.1/24
ListenPort = 51820
PrivateKey = <contents of /etc/wireguard/server.key>
[Peer]
PublicKey = <laptop public key>
PresharedKey = <psk, optional>
AllowedIPs = 10.8.0.2/32chmod 600;如果启动时警告文件对所有用户可读,说明你遗漏了这一步。Address是服务器在隧道内的地址,并包含整个 VPN 子网的掩码。请选择一个实际网络中不会遇到的地址范围。192.168.1.0/24会与客户端所在网络中的大量家用路由器冲突,随后隧道会静默地被本地路由取代。
在服务器端,对等方的 AllowedIPs 是一个 /32,也就是该客户端拥有的唯一隧道地址。为两个对等方指定相同的允许 IP 后,该地址会指向最后配置的对等方;第一个对等方将停止接收流量,且任何位置都不会打印错误。保持 SaveConfig 未设置,否则 wg-quick down 会根据实时状态重写此文件。
将此主机配置为路由器
Linux 服务器会丢弃发往其他主机的数据包。默认未启用转发和源 NAT。
printf 'net.ipv4.ip_forward = 1\nnet.ipv6.conf.all.forwarding = 1\n' \
| sudo tee /etc/sysctl.d/99-wireguard.conf
sudo sysctl --system
sysctl net.ipv4.ip_forward仅使用 sysctl -w 的配置会在下次重启前生效,之后便会悄然停止工作。NAT 需要使用出口接口,即连接到互联网的网卡,而不是 wg0。不要假定接口名为 eth0;应从 ip route show default 获取实际名称,因为当前镜像使用类似 enp1s0 或 ens3 的名称。
防火墙:端口和转发路径
一个 nftables 文件同时配置过滤和 NAT。写入 /etc/nftables.conf 会清空现有规则集,因此已由 ufw 或 Docker 管理的服务器不要执行此操作。
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
chain input {
type filter hook input priority filter; policy drop;
ct state established,related accept
iif lo accept
tcp dport 22 accept
udp dport 51820 accept
}
chain forward {
type filter hook forward priority filter; policy drop;
ct state established,related accept
iifname "wg0" oifname "enp1s0" accept
}
}
table ip nat {
chain postrouting {
type nat hook postrouting priority srcnat; policy accept;
ip saddr 10.8.0.0/24 oifname "enp1s0" masquerade
}
}使用 sudo systemctl enable --now nftables 应用配置,并保持第二个 SSH 会话处于打开状态:policy drop,再加上 SSH 规则中的一个拼写错误,就会让您无法登录自己的服务器。注意转发链不会允许什么,即 wg0 到 wg0。对等端可以访问互联网,但不能互相访问;如需点对点 VPN,请添加 iifname "wg0" oifname "wg0" accept。同一条链还控制对等端可以访问服务器本身的哪些内容。当这台服务器同时作为在 tmux 中运行 Claude Code 的远程开发服务器时,这一点尤其重要,因为您通常不希望将这部分服务公开到公网。
在使用 ufw 的服务器上:ufw allow 51820/udp,在 /etc/default/ufw 中设置 DEFAULT_FORWARD_POLICY="ACCEPT",并在 /etc/ufw/before.rules 顶部添加一条 *nat POSTROUTING MASQUERADE 规则。
通过 systemd 启动服务
sudo systemctl enable --now wg-quick@wg0
sudo wg showwg-quick 创建网络接口、添加地址,并安装根据 AllowedIPs 派生的路由。enable --now 是关键部分:手动运行的 wg-quick up wg0 在下次重启后就会失效,而内核升级也需要重启。如果单元在其中一次重启后未能恢复运行,它会一直保持静默,直到有人尝试连接。因此,在 wg-quick@wg0 上添加一个指向您自己的 ntfy 服务器的 OnFailure= drop-in,是通过手机获知故障的最低成本方式,而不必等被拒绝访问的用户报告问题。
客户端配置,以及人人都会配置错的设置
[Interface]
PrivateKey = <laptop private key>
Address = 10.8.0.2/32
DNS = 10.8.0.1
[Peer]
PublicKey = <server public key>
Endpoint = vpn.example.com:51820
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25AllowedIPs 同时承担两项不同的功能,将这两者混为一谈,是大多数 WireGuard 配置问题的根源。
出站方向,它是路由表。 目标地址匹配某个对等点的 AllowedIPs 时,数据包会加密并发送到该对等点。0.0.0.0/0, ::/0 会将所有流量通过隧道发送,即全隧道,并将服务器设为默认路由。分流隧道使用更窄的列表:AllowedIPs = 10.8.0.0/24, 10.20.0.0/16 承载 VPN 流量以及服务器后方的一个私有网络,其他流量继续使用本地路由。正是这个较窄的列表,让您可以完全不将服务暴露到公网。例如,绑定到隧道地址的 VPS 上的私有 Nextcloud 实例,或运行在同一台主机上的 嵌套虚拟化实验室 VM,都可以继续被对等点访问,同时对其他人不可见。
入站方向,它是访问控制列表。 如果来自某个对等点的解密数据包,其源地址不在该对等点的 AllowedIPs 中,数据包就会被丢弃。这就是服务器端为笔记本列出 10.8.0.2/32 的原因:如果在那里填写 0.0.0.0/0,该客户端就可以伪造隧道中的任意地址。
PersistentKeepalive 用于位于 NAT 后的客户端。此时,路由器只会在有数据包传输期间保持 UDP 映射处于打开状态。映射过期后,服务器将无法再访问客户端。PersistentKeepalive = 25 会保持映射打开;应在客户端上设置它,而不是在具有公网 IP 的服务器上设置。
DNS,以及没人注意到的泄露
使用 AllowedIPs = 0.0.0.0/0 且没有 DNS = 行时,客户端会继续使用从本地网络获取的解析器,即咖啡馆路由器上的 192.168.1.1。这条路由比默认路由更具体,因此 DNS 查询会通过本地链路以明文发送,而其他流量则通过隧道传输。流量内容是私密的,但查询的域名列表不是。
有两种可行方案。将 DNS 指向公共解析器(DNS = 9.9.9.9),查询就会通过隧道传输,并从您的服务器发出,但该解析器仍然可以看到这些查询。或者运行绑定到 10.8.0.1 的 unbound 或 dnsmasq,设置 DNS = 10.8.0.1,并将 udp dport 53 iifname "wg0" accept 添加到 input 链;设置该行后无需再处理解析器,但所有域名都无法解析。
在 Linux 客户端上,wg-quick 会通过 resolvconf 应用 DNS;如果缺少该配置,结果就是 resolvconf: command not found。请安装 openresolv,或者在使用 systemd-resolved 的客户端上设置 PostUp = resolvectl dns %i 10.8.0.1。
在不中断隧道的情况下添加和删除对等端
重启接口来添加用户会断开所有现有连接。将 [Peer] 块追加到 wg0.conf,然后原地重新加载对等端集合。
sudo bash -c 'wg syncconf wg0 <(wg-quick strip wg0)'wg-quick strip 会输出不包含 wg-quick 专用键(Address、DNS、PostUp)的配置,syncconf 会在保持现有会话的情况下应用差异。它只更新对等端;如果 Address 发生变化,仍需完整执行 down/up。使用 sudo wg set wg0 peer <public key> remove 撤销,然后从文件中删除该块,否则下次重新加载时它会再次生效。
故障模式及对应的提示信息
握手始终无法完成。 wg show 列出对端,但没有 latest handshake,客户端日志显示:
Handshake for peer 1 (10.0.0.10:51820) did not complete after 5 seconds, retrying (try 2)没有数据包到达,或没有数据包被接受。按以下顺序检查:VPS 防火墙和服务商网络防火墙是否都开放了 UDP 51820;大多数控制面板会单独管理服务商网络防火墙。检查 Endpoint 地址和端口是否正确。检查密钥是否配置反了。客户端 [Peer] 块中的密钥必须是服务器的公钥,反之亦然。粘贴私钥或客户端自己的公钥,都会出现完全相同的症状。服务器上的 sudo tcpdump -ni any udp port 51820 可显示数据包是否实际到达。内核模块默认不会记录日志;启用动态调试(echo module wireguard +p | sudo tee /sys/kernel/debug/dynamic_debug/control)后,WireGuard 消息才会出现在 dmesg 中。启用后,密钥不匹配会显示为无效 MAC 丢包。
握手成功,但无法访问互联网。 ping 10.8.0.1 成功,但 ping 1.1.1.1 超时:缺少转发或 NAT。检查 sysctl net.ipv4.ip_forward 是否为 1,然后让客户端执行 ping,同时使用 sudo nft list ruleset 或 sudo iptables -t nat -L POSTROUTING -n -v 查看计数器。伪装规则的计数器为 0,表示出口接口名称错误;计数器持续增加但没有回复,说明转发链策略存在问题。
可以访问互联网,但无法解析域名。 ping 1.1.1.1 成功,curl https://example.com 返回 Could not resolve host。缺少 DNS 行,或其中指定的解析器无法从隧道内部访问。
部分 HTTPS 网站加载停滞。 SSH 和 ping 正常,但较大的页面会卡住。这是路径 MTU 问题:隧道会增加额外开销,中间某条链路可能丢弃超大数据包,且没有 ICMP 消息返回。降低客户端 [Interface] 中的 MTU,尝试 1420,然后尝试 1380,最后尝试 1280。如果降低 MTU 可以解决卡顿,但吞吐量仍不理想,不要继续猜测整十的数值。请按照 通过二分查找并限制 TCP MSS 来确定实际路径 MTU 操作,这也可以排除并非由隧道导致的问题。
接口拒绝启动。 Address already in use 表示其他进程占用了 UDP 51820。失败的 up 之后出现 Cannot find device wg0,通常表示配置被拒绝;请阅读 journalctl -u wg-quick@wg0 -n 50。
从 Streisand 或 OpenVPN 迁移
Streisand 已停止维护,其代码仓库也已归档。在已废弃的自动化方案上运行 VPN,会逐渐形成安全隐患。两者无法原地升级,OpenVPN 的 PKI 也无法转换:WireGuard 没有证书、CA 或有效期,因此每个客户端都必须生成一对新的密钥。
可以并行迁移:同一台服务器上,使用 UDP 51820 的 WireGuard 可与使用 1194 端口的 OpenVPN 共存。部署 wg0,逐个迁移客户端,然后停止旧服务。OpenVPN 的用户名/密码和吊销模型无法迁移;如果需要账户管理或审计跟踪,应在 WireGuard 之上添加相应的管理层。
备份、升级,以及规模扩大后会增加压力的部分
/etc/wireguard 就是服务器本身。备份它(sudo tar czf wg-backup.tgz -C /etc wireguard,权限为 600,并存放在服务器之外),即可在几分钟内于全新的 VPS 上完成重建。服务器私钥一旦丢失,就必须重新签发所有客户端配置,因为客户端会固定使用服务器公钥。升级就是常规的 apt upgrade;内核更新还需要重启,而如果启用了 wg-quick@wg0,它会自行恢复。
每个对等端占用的状态很少,密码运算在内核中执行,因此上限取决于 VPS 的 CPU 和带宽配额,而不是此配置中的某项限制。应通过隧道使用 iperf3 测量实际性能,不要直接相信公开数据。规模扩大后,真正增加压力的是运维。每个对等端都需要唯一的隧道 IP。手动编辑六十个 [Peer] 块,很容易引入重复的 AllowedIPs;应使用脚本生成配置。单台服务器只有一个 UDP 端点,也是单点故障;WireGuard 不支持集群。实现冗余意味着部署第二台服务器,并为其配置独立密钥。密钥轮换仍需手动完成,因此应记录每个密钥由谁持有,以及如何撤销密钥。当这些记录超出文本文件的管理能力后,通常的做法是在相同的内核数据平面之上增加控制平面。自托管的 NetBird 服务器可以处理地址分配、对等端分发和设置密钥;否则这些工作都需要手动完成。如果自行运行控制平面又需要额外部署一台服务器,Tailscale 可以代为托管;其免费计划支持六名用户和不限数量的设备,这已经足够让大多数个人设备组无需付费。超过这一规模后,费用取决于人员而不是机器,因此家庭或小型团队实际需要支付的费用取决于拥有登录权限的用户数量,而不是需要手动写入 wg0.conf 的对等端数量。在这种架构下,分流 AllowedIPs 的管理方式会变成由子网路由器发布私有网段:只需从一台 VPS 发布一次,再由中央统一批准,而不必粘贴到每个客户端文件中。这种取舍是否值得,取决于托管控制平面实际能够访问什么;并且它不会持有用于加密网络流量的密钥,但会决定哪些对等端能够获知彼此。
所有这些都需要一台由您控制的 Linux 服务器、一个公网 IP、一个可以加载模块的内核,以及一套由您全程管理的防火墙。
FAQ
WireGuard 握手为什么始终无法完成?
wg show 列出的对端没有 latest handshake,表示数据包未到达,或未被接受。在 VPS 防火墙和服务商单独提供的网络防火墙上检查 UDP 51820,确认 Endpoint 主机和端口,然后检查密钥是否配置交叉:客户端的 [Peer] 块必须包含服务器的公钥。服务器上的 sudo tcpdump -ni any udp port 51820 可显示数据包是否到达;dmesg 只有在启用动态调试(echo module wireguard +p | sudo tee /sys/kernel/debug/dynamic_debug/control)后,才会报告 WireGuard 的握手失败。此时,密钥不匹配会显示为无效 MAC 丢包。
隧道已连接,但无法访问互联网。缺少什么?
ping 10.8.0.1 正常工作而 ping 1.1.1.1 超时,通常表示转发或 NAT 配置有问题。确认 sysctl net.ipv4.ip_forward 的值为 1,并确认已将其写入 /etc/sysctl.d/,而不是只通过重启后会失效的 sysctl -w 设置。然后检查伪装规则使用的是真实出口接口名称,该名称可从 ip route show default、enp1s0 或 ens3 获取,少数情况下也可从 eth0 获取。
客户端配置中是否需要 DNS = 行?
使用全隧道且没有 DNS = 行时,客户端会继续使用从本地网络获取的解析器。这些 DNS 查询会通过本地链路以明文传输,其他流量则通过隧道传输。将 DNS 指向公共解析器,或运行绑定到 10.8.0.1 的 unbound/dnsmasq,并在输入链中开放 udp dport 53 iifname "wg0"。
AllowedIPs 实际控制什么?
它有两个作用。对于出站流量,它是路由表:匹配对端 AllowedIPs 的流量会被加密并发送到该对端。对于入站流量,它是访问控制列表:解密后源地址不属于该对端 AllowedIPs 的数据包会被丢弃。因此,服务器端会为每个客户端列出一个 /32,而客户端可以列出 0.0.0.0/0。
WireGuard 可以在任何 VPS 上运行吗?
在 KVM VPS 上,WireGuard 可使用内核模块运行,无需额外设置。在共享主机内核的容器虚拟化环境中,例如 OpenVZ 或 LXC,modprobe wireguard 会因 Operation not supported 失败,此时应使用 wireguard-go 用户空间实现。先运行 sudo modprobe wireguard && echo ok。