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
mkdir -p /opt/wgdashboard
cd /opt/wgdashboard
nano docker-compose.ymlversion: '3.8'
services:
wgdashboard:
image: ghcr.io/wgdashboard/wgdashboard:latest
container_name: wgdashboard
restart: unless-stopped
network_mode: "host"
cap_add:
- NET_ADMIN
- SYS_MODULE
volumes:
- /etc/wireguard:/etc/wireguard
- ./data:/data
environment:
- TZ=Asia/Shanghai1.3 启动
docker compose up -d浏览器访问 http://<服务器公网IP>:10086,默认账号 admin,密码 admin。首次登录后立刻改密码。
⚠️ 踩坑提示:容器是 host 网络模式,
wg、wg-quick这些命令工具其实是装在容器里的,宿主机上直接敲wg show会提示command not found。因为 wg0 接口本身建在宿主机网络命名空间里,建议直接在宿主机也装一份工具,操作更方便:
sudo apt update
sudo apt install -y wireguard-tools装完后 wg show、wg-quick up/down wg0 在宿主机上就能正常用了。
2. 配置 wg0.conf 实现转发
2.1 开启内核 IP 转发
这是让服务器具备“路由器”能力的前提,不开这个,什么规则都白搭:
echo 'net.ipv4.ip_forward = 1' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p验证:
cat /proc/sys/net/ipv4/ip_forward # 应该输出 12.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冲掉。
[Interface]
Address = 10.2.6.1/24
ListenPort = 51826
PrivateKey = <服务器私钥>
DNS = 10.2.6.1
MTU = 1360
SaveConfig = true
[Peer]
PublicKey = <手机公钥>
AllowedIPs = 10.2.6.2/32
PersistentKeepalive = 25对比初版,这里有两处改动,分别对应下面两个常见体验问题:
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:
AllowedIPs = 10.82.13.0/24, 10.1.0.0/22, 10.2.6.0/24
PersistentKeepalive = 252.4 权限与生效
chmod 600 /etc/wireguard/wg0.conf
wg-quick down wg0 2>/dev/null || true
wg-quick up wg0⚠️ 踩坑提示:如果用 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 需要用到的转发路径,别的路径依然是默认拒绝:
sudo ufw route allow in on wg0 out on ztrfvxjtwd
sudo ufw route allow in on ztrfvxjtwd out on wg0
sudo ufw route allow in on wg0 out on ens5
sudo ufw route allow in on ens5 out on wg0第二步,NAT/MASQUERADE 规则写进 ufw 自己的配置文件(这是转发之外必须单独处理的部分,ufw 没有对应的命令行子命令,只能手动编辑):
sudo nano /etc/ufw/before.rules在文件最前面(*filter 之前)加入:
*nat
:POSTROUTING ACCEPT [0:0]
-A POSTROUTING -s 10.2.6.0/24 -o ztrfvxjtwd -j MASQUERADE
-A POSTROUTING -s 10.2.6.0/24 -o ens5 -j MASQUERADE
COMMIT第三步,重启生效:
sudo ufw disable
sudo ufw enable
sudo wg-quick down wg0
sudo wg-quick up wg0至此,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 检查一遍必要端口是否都在放行列表里,该补的补上:
sudo ufw allow 51826/udp # WireGuard 监听端口,可以对全网开放3.4 本机自测,快速定位是不是防火墙问题
不依赖公网,本机直接测试服务是否活着:
curl -I http://127.0.0.1:10086 # 测面板
docker ps -a # 看容器状态是否 healthy
docker logs wgdashboard --tail 50 # 看容器日志如果本机能通、外部访问不了,基本可以确定是 ufw 或云安全组(腾讯云控制台的安全组规则,同样要放行 UDP 51826 和面板端口)拦截的问题。
3.5 面板端口收敛:不要对全网开放 10086
初版教程用的是 sudo ufw allow 10086/tcp,相当于把管理面板暴露给全世界任何一个 IP。WGDashboard 容器本身带有 NET_ADMIN、SYS_MODULE 权限,对宿主机网络的操作能力很高,一旦面板密码被弱口令爆破,风险不是“进了一个网站后台”这么简单,而是拿到了服务器网络层面的控制能力。
更安全的做法是只放行你自己会用到的来源 IP:
sudo ufw delete allow 10086/tcp # 先删掉之前全网放行的规则(如果加过)
sudo ufw allow from <你的家庭/公司公网IP> to any port 10086 proto tcp如果你的公网 IP 不固定(比如家庭宽带经常换 IP),可以退而求其次,把面板端口也一起丢进 WireGuard 隧道里管理——即只允许通过 wg0 接口访问面板,断开 VPN 就完全摸不到面板入口:
sudo ufw delete allow 10086/tcp
sudo ufw allow in on wg0 to any port 10086 proto 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
实时监控包命中数变化,便于与手机操作同步观察:
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 到自身的转发放行即可:
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 服务器上安装:
sudo apt install -y dnsmasq编辑 /etc/dnsmasq.conf,只监听 WireGuard 网卡,避免暴露到公网:
# 只在 wg0 接口和本机监听,不对公网提供服务
interface=wg0
bind-interfaces
listen-address=10.2.6.1,127.0.0.1
# 内网主机名手动映射(按需增删)
address=/nas.internal/10.82.13.20
address=/db.internal/10.1.0.15
address=/app.internal/10.82.13.30
# 解析不到的域名(比如正常上网用的域名),转发给公共 DNS
server=9.9.9.9重启生效:
sudo systemctl restart dnsmasq
sudo systemctl enable dnsmasq6.2 客户端指向这个内网 DNS
把 WireGuard [Interface] 里的 DNS 设置成服务器自己的隧道地址即可(第 2.2 节的示例已经是这样写的):
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. 关于实际使用体验的说明
延迟和速度这块,我没有办法替你实测——这套服务是部署在你自己的服务器和网络环境里,具体走的国际/国内链路、运营商、服务器负载等因素都会直接影响体验,只有你自己连上之后测才准。建议这样自查:
# 手机连上 WireGuard 后,在电脑或服务器上互相 ping 一下,看延迟和丢包
ping -c 20 10.82.13.169
# 如果装了 mtr(比 ping 更能看出问题出在哪一跳)
mtr 10.82.13.169如果发现延迟明显偏高或者不稳定,排查方向通常是:第 5.2 节提到的 MTU 是否统一设置(不一致容易导致分片重传拖慢速度)、服务器到 ZeroTier/VPC 那一段网络本身的质量,以及手机所在网络(4G/5G 信号弱、公共 WiFi)本身的抖动。
8. 常用命令速查
# 查看 WireGuard 状态(握手时间、流量)
wg show
# 重启 wg0 使配置生效
wg-quick down wg0 && wg-quick up wg0
# 查看转发链规则及命中数
iptables -L FORWARD -n -v
# 查看 NAT/MASQUERADE 规则及命中数
iptables -t nat -L POSTROUTING -n -v
# 查看 ufw 放行规则(带编号,方便删除)
sudo ufw status numbered
# 查看容器状态和日志
docker ps -a
docker logs wgdashboard --tail 50
# 本机自测面板是否存活
curl -I http://127.0.0.1:10086
# 测试内网 DNS 是否生效(在服务器上自测)
dig @10.2.6.1 nas.internal
# 查看 dnsmasq 状态
sudo systemctl status dnsmasq 











