WireGuard(Docker 部署)+ ZeroTier/VPC 内网穿透完整教程
**场景:**手机通过 WireGuard 连接服务器后,中转访问服务器上的 ZeroTier 网络和腾讯云 VPC 内网。
0. 环境说明
| 项目 | 值(示例,以你实际为准) |
|---|---|
| 服务器公网 IP | 114.132.1.3 |
| 腾讯云 VPC 网卡 | ens5,网段 10.1.0.0/22 |
| ZeroTier 网卡 | ztrfvxjtwd(每台机器名字不同,不是固定的 zt0) |
| ZeroTier 网段 | 10.82.13.0/24 |
| WireGuard 网卡 | wg0,网段 10.2.6.0/24 |
先执行 ip addr 确认自己的网卡名称,不要照抄网上教程里的 zt0/eth0,不同机器命名规则不同(比如腾讯云是 ens5,ZeroTier 是随机字符串)。
1. 用 Docker 部署 WGDashboard(WireGuard 管理面板)
1.1 为什么必须用 host 网络模式
WireGuard 需要直接操作宿主机的网卡和路由表来做流量转发。如果用 Docker 默认的 bridge 桥接网络,容器会被隔离在独立的网络命名空间里,拿不到宿主机的 ens5、ZeroTier 网卡,也就没法做转发。所以必须用 network_mode: "host"。
1.2 docker-compose.yml
1 | mkdir -p /opt/wgdashboard |
1 | version: '3.8' |
1.3 启动
1 | docker compose up -d |
浏览器访问 http://<服务器公网IP>:10086,默认账号 admin,密码 admin。首次登录后立刻改密码。
⚠️ 踩坑提示:容器是 host 网络模式,
wg、wg-quick这些命令工具其实是装在容器里的,宿主机上直接敲wg show会提示command not found。因为 wg0 接口本身建在宿主机网络命名空间里,建议直接在宿主机也装一份工具,操作更方便:
1 | sudo apt update |
装完后 wg show、wg-quick up/down wg0 在宿主机上就能正常用了。
2. 配置 wg0.conf 实现转发
2.1 开启内核 IP 转发
这是让服务器具备“路由器”能力的前提,不开这个,什么规则都白搭:
1 | echo 'net.ipv4.ip_forward = 1' | sudo tee -a /etc/sysctl.conf |
验证:
1 | cat /proc/sys/net/ipv4/ip_forward # 应该输出 1 |
2.2 wg0.conf(精简版 —— 转发规则不写在这里)
📌 **v2 优化:**初版教程把
iptables转发规则和 NAT 规则都塞进了PostUp/PostDown,后来在第 3 节又往/etc/ufw/before.rules里加了一份 NAT 规则,变成两处规则并存。这样做的问题是:每次wg-quick up,PostUp都会重新执行一遍iptables -A(追加),如果 ufw 那边也在管理同一批规则,容易出现规则重复叠加、排查时分不清到底是哪一层生效的。更干净的做法:
wg0.conf只负责“这是一张网卡、这是它的地址和密钥”,完全不掺和防火墙的事,转发和 NAT 统一交给 ufw 管理(见第 3 节)。这样 WGDashboard 保存配置时也不会不小心把你手写的PostUp/PostDown冲掉。
1 | [Interface] |
对比初版,这里有两处改动,分别对应下面两个常见体验问题:
- **
MTU = 1360:**你的流量路径是 手机 → WireGuard 隧道 → 服务器 → 再转发进 ZeroTier 虚拟网卡,是“隧道套隧道”。WireGuard 默认 MTU 是 1420,再叠加一层 ZeroTier 自己的封包头部开销后,数据包很容易超过物理网卡(通常 1500)的承载上限,导致分片甚至丢包——具体表现为访问网页、SSH 敲字时明显卡顿,大文件传输忽快忽慢。把 MTU 显式调低到 1360 能给两层封包留出足够的头部空间,服务器和所有客户端(手机、Shadowrocket)都要设成一致的值。 PersistentKeepalive = 25:(写在[Peer]段,客户端和服务器两边都建议加)手机在 4G/5G 和 Wi-Fi 之间切换,或者长时间锁屏休眠时,运营商基站侧的 NAT 映射经常会变化或超时清除,导致隧道看起来还在但实际已经“假死”。设置这一项后,客户端会每 25 秒主动发一次心跳包,让运营商 NAT 保持这条映射不过期,能显著减少切换网络/锁屏后隧道断连的情况。
AllowedIPs 依然只写手机的隧道地址(10.2.6.2/32),不是它能访问的网段——那是下面手机端的配置。
2.3 手机端 AllowedIPs 与 PersistentKeepalive
手机(或 Shadowrocket)的 WireGuard Peer 配置里,AllowedIPs 必须包含要访问的网段,否则流量根本不会走隧道;同时也建议加上 PersistentKeepalive = 25:
1 | AllowedIPs = 10.82.13.0/24, 10.1.0.0/22, 10.2.6.0/24 |
2.4 权限与生效
1 | chmod 600 /etc/wireguard/wg0.conf |
⚠️ 踩坑提示:如果用 WGDashboard 面板管理(
SaveConfig = true),面板会接管配置文件的读写,自己维护一份 Peer 数据库。如果你在面板打开之前用命令行手动写好[Peer]段,面板首次加载/保存时有可能用它自己数据库里(空的)Peer 列表覆盖掉你手动写的内容。建议 Peer 统一在面板里添加,不要手动改文件后又去面板里操作,两边容易打架、互相覆盖。
3. 防火墙(ufw)是最容易踩的坑
这次实际排查中,90% 的“连不上”问题都出在这里,单独拎出来讲。
3.1 现象
- WireGuard 握手成功(
wg show能看到latest handshake) - 手机能 ping 通 WireGuard 隧道自己的地址(如
10.2.6.1) - 但是 ping 不通 ZeroTier/VPC 网段里的其他机器
iptables -L FORWARD -n -v里,自己加的转发规则命中数一直是0
3.2 原因
ufw 默认把 FORWARD 链策略设为 DROP,并且会在 FORWARD 链最前面插入自己的一堆检查链(ufw-before-forward、ufw-after-forward 等)。你在 wg0.conf 的 PostUp 里用 iptables -A FORWARD 追加的规则是排在最后面的,数据包在流转到你的规则之前,已经先被 ufw 的默认策略拦截丢弃了。
3.3 解决(v2:规则全部收拢到 ufw,不再动 wg0.conf)
📌 v2 优化:初版教程为了让转发跑通,把
DEFAULT_FORWARD_POLICY整体改成了ACCEPT——这等于把服务器所有网卡之间的转发全部放开,不只是 WireGuard 这一条路径,范围偏宽,不够收敛。更合适的做法是保持默认策略DROP不动,只针对 WireGuard 需要用到的这几条路径,用ufw route allow精确放行(Ubuntu 24.04 自带的 ufw 版本支持这个语法)。这样转发规则、放行范围一目了然,也不用再单独去改/etc/default/ufw。
第一步,只放行 WireGuard 需要用到的转发路径,别的路径依然是默认拒绝:
1 | sudo ufw route allow in on wg0 out on ztrfvxjtwd |
第二步,NAT/MASQUERADE 规则写进 ufw 自己的配置文件(这是转发之外必须单独处理的部分,ufw 没有对应的命令行子命令,只能手动编辑):
1 | sudo nano /etc/ufw/before.rules |
在文件最前面(*filter 之前)加入:
1 | *nat |
第三步,重启生效:
1 | sudo ufw disable |
至此,wg0.conf 里不再需要任何 PostUp/PostDown,所有转发和 NAT 规则都固化在 ufw 一处地方,查规则、改规则、排错都只用看 ufw status 和 /etc/ufw/before.rules 这一份来源,不会再有“两处规则谁生效了”的疑惑。
⚠️ **重大踩坑提示:**执行
ufw disable再ufw enable之后,ufw 会用配置文件里保存的规则重新生效——但如果你的 WireGuard 端口(如51826/udp)和面板端口(如10086/tcp)之前不在放行列表里(比如是后来才开的、或者从没加过),重启后这两个端口会直接被墙挡住,导致面板和 WireGuard 同时连不上。这次排查中就实际发生了这个问题——表现为“改完防火墙规则后连管理面板都打不开了”。
每次修改 ufw 相关配置后,务必用 sudo ufw status numbered 检查一遍必要端口是否都在放行列表里,该补的补上:
1 | sudo ufw allow 51826/udp # WireGuard 监听端口,可以对全网开放 |
3.4 本机自测,快速定位是不是防火墙问题
不依赖公网,本机直接测试服务是否活着:
1 | curl -I http://127.0.0.1:10086 # 测面板 |
如果本机能通、外部访问不了,基本可以确定是 ufw 或云安全组(腾讯云控制台的安全组规则,同样要放行 UDP 51826 和面板端口)拦截的问题。
3.5 面板端口收敛:不要对全网开放 10086
初版教程用的是 sudo ufw allow 10086/tcp,相当于把管理面板暴露给全世界任何一个 IP。WGDashboard 容器本身带有 NET_ADMIN、SYS_MODULE 权限,对宿主机网络的操作能力很高,一旦面板密码被弱口令爆破,风险不是“进了一个网站后台”这么简单,而是拿到了服务器网络层面的控制能力。
更安全的做法是只放行你自己会用到的来源 IP:
1 | sudo ufw delete allow 10086/tcp # 先删掉之前全网放行的规则(如果加过) |
如果你的公网 IP 不固定(比如家庭宽带经常换 IP),可以退而求其次,把面板端口也一起丢进 WireGuard 隧道里管理——即只允许通过 wg0 接口访问面板,断开 VPN 就完全摸不到面板入口:
1 | sudo ufw delete allow 10086/tcp |
这样面板只能在连上 WireGuard 之后,用隧道内网地址(如 http://10.2.6.1:10086)访问,公网直接访问会被拒绝。WireGuard 本身的 51826/udp 端口不受影响,继续对全网开放即可(不然手机没法握手连接)。
4. 排查思路总结(遇到“连不上”时按这个顺序查)
- 隧道本身通不通:
wg show看latest handshake有没有更新 - **点对点通不通:**手机 ping 隧道对端地址(如
10.2.6.1),这一步不经过 FORWARD 链,只测 WireGuard 本身 - 转发链是否放行:
iptables -L FORWARD -n -v,同时手机发起 ping,看自定义规则的pkts计数是否增长- 计数一直是 0 → 包根本没到这条链,查前面的 ufw/DOCKER-USER 等链是不是拦截了,或者客户端
AllowedIPs没配对 - 计数涨了但还是不通 → 查 NAT/MASQUERADE 规则或对端网络自己的防火墙
- 计数一直是 0 → 包根本没到这条链,查前面的 ufw/DOCKER-USER 等链是不是拦截了,或者客户端
- NAT 是否生效:
iptables -t nat -L POSTROUTING -n -v看 MASQUERADE 命中数 - 内核转发开关:
cat /proc/sys/net/ipv4/ip_forward确认是1
实时监控包命中数变化,便于与手机操作同步观察:
1 | watch -n1 'iptables -L FORWARD -n -v | head -15' |
5. 其他转发延伸玩法
5.1 多网段同时转发(不止 ZeroTier 一个)
思路和这次一样,每多一个要访问的内网,就用 sudo ufw route allow in on wg0 out on <对应网卡>(以及反向一条)加一组转发规则,再在 /etc/ufw/before.rules 的 *nat 段补一条对应的 MASQUERADE,手机端 AllowedIPs 里加上对应网段即可,前面 VPC(ens5)那部分已经是现成例子。
5.2 全局代理 vs 分流(按需路由)
- 全局代理:
AllowedIPs = 0.0.0.0/0,手机所有流量都走服务器转发出去,适合当“梯子”用,但会让所有流量(包括访问公网)都经过服务器,速度和流量成本都要考虑。 - **分流(推荐用于内网穿透场景):**只把
AllowedIPs设置成你真正需要访问的内网网段(如本教程的做法),访问其他外网地址走手机自己的网络,不占用服务器带宽,也更安全。
在 Shadowrocket 里还可以进一步按域名/IP 做规则分流,把 WireGuard 节点只用于访问特定内网 IP 段,其余流量走直连或其他代理,这样一台手机可以同时“翻墙 + 访问内网”互不干扰。
5.3 多个手机/多设备接入
每台设备生成一对独立密钥,在 wg0.conf 里各加一个 [Peer] 段,AllowedIPs 用不同的 /32 地址区分(如 10.2.6.2/32、10.2.6.3/32 ……),都指向同一个服务器 Endpoint 即可,互不影响。用 WGDashboard 面板操作最方便,点几下就能生成一个新 Peer 和对应二维码。
5.4 客户端到客户端互通(WireGuard 内网互访)
如果想让多个手机/客户端之间也能互相访问(不只是访问服务器转发的内网),用 ufw 加一条 wg0 到自身的转发放行即可:
1 | sudo ufw route allow in on wg0 out on wg0 |
默认情况下客户端之间是互相隔离、无法直接访问的(出于安全考虑),按需开启。
5.5 反向访问(让 ZeroTier/VPC 里的机器主动访问 WireGuard 客户端)
如果需要反过来,让 ZeroTier 网络里的其他机器主动发起连接访问 WireGuard 客户端(比如手机),需要在 ZeroTier 那些机器上添加一条静态路由,把 10.2.6.0/24 指向这台服务器的 ZeroTier 地址(如 10.82.13.169)作为网关。多数路由器/服务器都可以加静态路由,ZeroTier 自己的管理后台里也可以设置“托管路由”(Managed Routes)全局下发,不用每台机器手动加。
5.6 用 Shadowrocket 代替官方 WireGuard App
Shadowrocket 支持直接导入 WireGuard 配置(扫二维码或手动填写),优势是可以和其他代理规则混用、按需分流。手动填写时对照关系:
| Shadowrocket 字段 | 对应内容 |
|---|---|
| Private Key | 客户端私钥 |
| Address | 如 10.2.6.3/32 |
| Peer Public Key | 服务器公钥 |
| Endpoint | 114.132.1.3:51826 |
| Allowed IPs | 需要访问的内网网段 |
| DNS | 10.2.6.1(走服务器内网 DNS,见第 6 节)或 9.9.9.9 |
| MTU | 1360(和服务器保持一致,原因见第 2.2 节) |
⚠️ 提示:手机 WireguArd App/Shadowrocket 里的私钥只存在设备本地,永远不会显示在服务器或面板里。如果需要给新客户端(比如切换到 Shadowrocket)生成配置,最简单的方式是当作一台“新设备”处理——生成一对新密钥、在服务器加一个新 Peer,而不是尝试还原旧密钥。
6. 内网主机名解析(用域名代替 IP 访问内网机器)
一个常见误区需要先澄清:国内自建 DNS 服务器,和“网站备案”完全是两回事。 ICP 备案针对的是“域名解析到境内服务器 + 对公众提供 Web 服务”这种场景;而这里要做的,是给自己的私有 WireGuard 网络内部搭一个不对外公开、不绑定任何公网域名的解析器,纯粹用来把 db.internal 这样的名字翻译成 10.82.13.20 这样的内网 IP,跟备案监管的对象没有交集,合规上没有障碍,国内很多公司内网都是这么做的。
6.1 用 dnsmasq 搭一个内网 DNS
在 WireGuard 服务器上安装:
1 | sudo apt install -y dnsmasq |
编辑 /etc/dnsmasq.conf,只监听 WireGuard 网卡,避免暴露到公网:
1 | # 只在 wg0 接口和本机监听,不对公网提供服务 |
重启生效:
1 | sudo systemctl restart dnsmasq |
6.2 客户端指向这个内网 DNS
把 WireGuard [Interface] 里的 DNS 设置成服务器自己的隧道地址即可(第 2.2 节的示例已经是这样写的):
1 | DNS = 10.2.6.1 |
手机连上 WireGuard 后,访问 http://nas.internal 之类的地址就能自动解析到对应内网 IP,不用再记 IP 段;访问其他正常网站时,dnsmasq 会把请求转发给 9.9.9.9,不影响日常上网解析。
6.3 如果 ZeroTier 网络本身有更多机器需要解析
- 直接编辑
/etc/hosts,dnsmasq 默认会读取并叠加解析(在dnsmasq.conf里确认没有关掉no-hosts选项即可) - 机器数量比较大且需要动态更新,可以换成 CoreDNS 或者直接用 ZeroTier 自带的 Central 管理里配置 DNS(需要付费版 ZeroTier Central 支持),按需选择,量小用 dnsmasq 完全够用。
7. 关于实际使用体验的说明
延迟和速度这块,我没有办法替你实测——这套服务是部署在你自己的服务器和网络环境里,具体走的国际/国内链路、运营商、服务器负载等因素都会直接影响体验,只有你自己连上之后测才准。建议这样自查:
1 | # 手机连上 WireGuard 后,在电脑或服务器上互相 ping 一下,看延迟和丢包 |
如果发现延迟明显偏高或者不稳定,排查方向通常是:第 5.2 节提到的 MTU 是否统一设置(不一致容易导致分片重传拖慢速度)、服务器到 ZeroTier/VPC 那一段网络本身的质量,以及手机所在网络(4G/5G 信号弱、公共 WiFi)本身的抖动。
8. 常用命令速查
1 | # 查看 WireGuard 状态(握手时间、流量) |