概述 链接到标题
用 NetBird 自建控制平面,替换现有的 WireGuard VPN 手动组网。控制平面部署在云服务器上,所有节点通过 P2P WireGuard 隧道互联。
步骤 链接到标题
1. Traefik v2 → v3 升级 链接到标题
现有 Traefik 版本是 v2.10.7,先升级到 v3.7.5 为后续路由做准备。
兼容性分析:
- 文件配置文件(File provider)的 YAML 语法完全兼容,无需改动
--serversTransport.insecureSkipVerifyCLI 参数名不变ipwhitelist中间件重命名为ipAllowList,在 v3 中需要手动迁移- 升级时需要加
--core.defaultrulesyntax=v2保持路由语法兼容,确认稳定后移除
升级步骤:只需改 Docker Compose 的 image tag,重启即可。
2. NetBird 控制平面部署 链接到标题
控制平面用 Docker Compose 部署,使用 NetBird v0.65+ 的合并容器架构,不再需要分开的 management/signal/relay/coturn 四个容器。
架构:
netbirdio/netbird-server— 合并容器,包含 management + signal + relay + STUNnetbirdio/dashboard— Web 管理界面- SQLite 存储(无需 PostgreSQL)
Traefik 路由:
nb.example.com→ dashboard(Web UI)api.nb.example.com→ combined server(gRPC + REST API + WebSocket)
3. 内网 DNS 服务器 链接到标题
在办公室的节点上用 Docker 部署 dnsmasq 2.93,提供 *.lan 域名解析。
选型:dnsmasq 仍然是最轻量的内网 DNS 方案。2026年5月 dnsmasq 被通报了 6 个 CVE(CVE-2026-2291 等),修复版本 2.93,务必使用最新版。
镜像:使用 dockurr/dnsmasq(GitHub 开源,500K+ pulls),支持 arm64。由于 Docker Hub 被墙,通过 DaoCloud 镜像拉取。
4. NetBird Agent 安装 链接到标题
两个节点安装 agent:
- 云服务器(控制平面所在节点)— 也需要装 agent,控制平面和 agent 是两回事,可以共存
- 办公室节点(同时跑 dnsmasq)
踩坑记录 链接到标题
Signal 连接不上 链接到标题
现象:agent 的 Management 显示 Connected,但 Signal 始终 Disconnected。日志报错 missing port in address。
原因:NetBird 合并容器的 exposedAddress 配置中必须包含端口号。例如 https://API_DOMAIN:443,否则客户端解析 Signal URI 时获取不到端口,gRPC 拨号失败。
解决:在 config.yaml 的 server.exposedAddress 中显式加上 :443。
DNS 端口冲突 链接到标题
现象:dnsmasq 无法启动,报错 Address in use。ss 发现 NetBird agent 内部 DNS 代理占用了 53 端口。
原因:NetBird agent 收到 Nameserver Group 配置后,会在本地启动 DNS 代理监听 53 端口。与已有 dnsmasq 冲突。
解决:在 DNS 服务器所在的节点上禁用 NetBird 的 DNS 代理:
{"disable_dns": true}
写入 /etc/netbird/config.json 后重启 agent。
FQDN 解析失败 链接到标题
现象:短名 server-a 能解析,但 server-a.lan(FQDN)返回 NXDOMAIN。
原因:dnsmasq 的 expand-hosts 只对 /etc/hosts 中的短名生效,自动添加 domain 后缀。但通过 NetBird Nameserver Group 发来的查询已经是 FQDN 格式(server-a.lan),expand-hosts 不会再次匹配。
解决:使用 dnsmasq 的 address=/ 指令显式声明 FQDN 映射:
address=/server-a.lan/NETBIRD_IP_A
address=/server-b.lan/NETBIRD_IP_B
而非依赖 /etc/hosts + expand-hosts。
STUN 端口未开放 链接到标题
现象:两个 agent 注册后无法建立 P2P 连接,Nameserver 显示 required key not available。
原因:STUN UDP 3478 端口未在云服务器安全组中开放,NAT 穿透失败。
解决:在安全组中添加 UDP 3478 入站规则。
Relay WebSocket 连接不上 链接到标题
现象:agent 状态里 Relays: 1/2 Available。展开详情后,STUN 正常:
[stun:api.example.com:3478] is Available
但内置 relay 不可用:
[rels://api.example.com:443] is Unavailable, reason: relay client not connected
P2P 连接正常时不影响通信,所以一开始容易忽略。但遇到严格 NAT、酒店网络、公司网络时,relay 是兜底通道,最好修掉。
原因:Traefik 把 api.example.com 的所有请求都转给了 h2c://netbird-server:80。这能满足 gRPC,但 relay 走的是 WebSocket,需要 HTTP/1.1 upgrade,不能全部走 h2c 后端。
解决:按路径拆成两条 Traefik 路由:
- gRPC 路径走
h2c://netbird-server:80 - relay / API / OAuth2 路径走
http://NETBIRD_SERVER:80
关键路径:
http:
routers:
netbird-grpc:
rule: "Host(`api.example.com`) && (PathPrefix(`/signalexchange.SignalExchange/`) || PathPrefix(`/management.ManagementService/`) || PathPrefix(`/management.ProxyService/`))"
service: netbird-h2c
netbird-backend:
rule: "Host(`api.example.com`) && (PathPrefix(`/relay`) || PathPrefix(`/ws-proxy/`) || PathPrefix(`/api`) || PathPrefix(`/oauth2`))"
service: netbird-http
services:
netbird-h2c:
loadBalancer:
servers:
- url: "h2c://netbird-server:80"
netbird-http:
loadBalancer:
servers:
- url: "http://NETBIRD_SERVER:80"
注意 PathPrefix(`/relay`) 不能写成 PathPrefix(`/relay/`),否则 /relay 本身匹配不到。修复后 agent 显示:
Relays: 2/2 Available
最终拓扑 链接到标题
云服务器 (控制平面 + agent)
├── Traefik v3 (nb.example.com / api.nb.example.com)
├── NetBird combined server (Docker)
└── NetBird agent (WireGuard peer)
│ P2P 隧道
办公室节点 (agent + dnsmasq)
├── NetBird agent
└── dnsmasq 2.93 (Docker, host network)
│
└── 解析 *.lan 域名
所有节点 → Nameserver Group → 办公室节点:53 → 解析 *.lan
总结 链接到标题
NetBird 的自建方案已经成熟,合并容器架构让部署简化很多。关键点是配置文件中的细节(端口号、DNS 代理冲突、FQDN 格式、Traefik 对 gRPC 和 WebSocket 的分流),踩过一遍就清楚了。