Files
netbird-selfhosted/SUMMARY.md
timmy c5a8757da4 docs: add client-side troubleshooting (Force Relay, bufferbloat)
Document the 'Force relay connection' switch that silently disables
ICE gathering on iOS NetBird (and similar on macOS), which was the
real cause of all peers showing Connection type: Relayed despite
STUN being correctly configured.

Also document bufferbloat diagnosis via macOS networkQuality, which
was a red herring during the P2P investigation (large RTT spikes
were the WiFi/ISP, not NetBird).
2026-04-18 10:40:04 +08:00

6.8 KiB
Raw Blame History

NetBird 部署摘要

部署結果

NetBird Self-Hosted 已部署於 192.168.42.127

  • 部署日期2026-04-17
  • 存取方式:http://192.168.42.127
  • 部署模式HTTP-only無 TLS、IP-only無域名

部署時的關鍵決策

1. 為何不用官方 getting-started.sh 腳本?

官方腳本是互動式的(讀取 /dev/tty),且預設選項需要公網域名與 Let's Encrypt。本次為內網 IP 部署,手動產生設定檔更直接。

腳本原始碼中支援 NETBIRD_DOMAIN=use-ip 模式line 308會自動偵測 IP 並切換為 HTTP、port 80、rel:// relay 協定。本次部署採用了相同的邏輯但以手動設定檔實現。

2. 為何選 Caddy 而非 Traefik 作為反向代理?

  • Traefik 內建 Let's Encrypt ACME但無域名時會反覆嘗試失敗
  • Caddy 設定檔更精簡(約 10 行),純 HTTP 路由對本場景足夠
  • 兩者都能正確處理 gRPC (h2c) 後端

3. 為何需要 crun 取代 runc

遇到的錯誤

OCI runtime create failed: runc create failed: unable to start container process:
error during container init: open sysctl net.ipv4.ip_unprivileged_port_start file:
reopen fd 8: permission denied

根因分析

  • Proxmox 非特權 LXC 將 /proc/sys 掛載為唯讀(ro
  • runc 1.2+ 為防止 TOCTOU 攻擊會用「reopen fd」機制存取 sysctl 檔案
  • 此機制在 LXC 的 /proc mount namespace 限制下失敗

嘗試但無效的方法

  • --privileged flag
  • --security-opt apparmor=unconfined --security-opt seccomp=unconfined
  • mount -o remount,rw /proc/sys
  • apt 安裝的 crun 1.14.1OCI spec 太舊Docker 不接受)

最終解法 下載 GitHub 最新 crun binary (1.20),並設為 Docker 預設 runtime。

安裝步驟摘要

1. apt 安裝前置套件 (curl, jq, openssl)           ← 約 1 分鐘
2. 安裝 Docker Engine                              ← 約 2 分鐘(網速慢)
3. 下載 crun binary設定為 Docker 預設 runtime   ← 約 30 秒
4. 建立 /opt/netbird/ 下四個設定檔                 ← 約 30 秒
5. docker compose pull + up -d                    ← 約 1 分鐘
6. 等待 netbird-server 下載 GeoLite2 資料庫       ← 約 2-5 分鐘

總耗時約 10 分鐘(含 GeoIP 下載)。

服務驗證

所有服務已驗證正常運作:

端點 狀態 說明
http://192.168.42.127/ 200 Dashboard Web UI
http://192.168.42.127/oauth2/.well-known/openid-configuration 200 OIDC Discovery
STUN :3478/udp NAT 穿透
內部 gRPC (Signal + Management) h2c via Caddy

疑難排解記錄

問題 原因 解法
初次 apt install 卡住 背景執行的 apt 行程未結束就被觸發第二個,導致 lock 競爭 清除 /var/lib/dpkg/lock*/var/lib/apt/lists/lock,重啟 apt
curl: command not found apt 仍在安裝中 等待安裝完成
所有 Docker 容器啟動失敗 LXC /proc/sys 唯讀 + runc 1.3 的 reopen fd 機制 改用 crun 1.20
Docker Compose crun runtime 未知 /etc/docker/daemon.json 設定未生效 systemctl restart docker
apt 的 crun 1.14.1 報 unknown version specified crun 的 OCI spec 1.0.0 與 Docker 29.4 不相容 從 GitHub 下載 crun 1.20
502 Bad Gateway所有 API 端點) netbird-server 下載 GeoLite2 中,尚未開始監聽 port 80 等待 2-5 分鐘
手機 Appserver closed the stream without sending trailers 透過 NPM 對外公開時NPM 用 proxy_pass 而非 grpc_pass 轉 gRPC加上 Caddy :80 預設不支援 h2c NPM 加 advanced_configgRPC 路由)+ Caddy 全域啟用 protocols h1 h2 h2c;見 npm-grpc-fix.md
PUT /api/setup-keys/{id} 回 200 但 name 沒改 NetBird API 只更新 auto_groupsrevokedname 是 read-only store.dbsetup_keys.name → 重啟 netbird-server;見 setup-keys-and-pat.md
peer 之間一直走 Relay[stun:...:3478] is Checking... STUN port 3478 被 Headscale (.126) 搶先佔用 DNAT NetBird 改用 3479/udpserver config + OpenWrt DNATstun-port-conflict.md
STUN 都通了,仍然 Connection type: RelayedICE candidate: -/- 客戶端 Force Relay Connection 開關被打開,跳過 ICE gathering App Settings → Advanced → 關掉 Force Relayclient-troubleshooting.md
ping RTT 尖峰到 2-4 秒 當下 WiFi/ISP 有 bufferbloat networkQuality -v 確認(看 RPM與 NetBird 無關

檔案位置

本機(/Users/timmy/dev/42_127/

├── README.md                         ← 專案總覽
├── QUICKSTART.md                     ← 快速部署步驟
├── SUMMARY.md                        ← 本檔(部署摘要)
├── netbird-selfhosted-setup.md       ← 完整安裝指南
├── npm-grpc-fix.md                   ← 經 NPM 對外公開時的 gRPC 修復
├── setup-keys-and-pat.md             ← Setup Keys / PAT 操作與 API 限制繞道
├── stun-port-conflict.md             ← STUN 改用 3479 (3478 被 Headscale 佔用)
└── client-troubleshooting.md         ← 客戶端 Force Relay / bufferbloat 排錯

遠端伺服器192.168.42.127:/opt/netbird/

├── docker-compose.yml    ← 服務編排
├── Caddyfile             ← 反向代理路由
├── config.yaml           ← NetBird Server 設定
└── dashboard.env         ← Dashboard 環境變數

Docker Volume

  • netbird_netbird_dataNetBird SQLite 資料庫與 GeoIP 資料

後續維運

日常指令

ssh root@192.168.42.127
cd /opt/netbird

# 查看日誌
docker compose logs -f

# 重啟
docker compose restart

# 更新映像
docker compose pull && docker compose up -d

# 備份資料庫
docker compose exec netbird-server sqlite3 /var/lib/netbird/store.db ".backup /tmp/backup.db"
docker cp netbird-server:/tmp/backup.db ./backup-$(date +%F).db

可能的擴展

  • 加上 TLS:若之後取得域名,可切換為 Traefik + Let's Encrypt或本機自簽憑證
  • 外部 IdP:需支援 OIDC 的 IdP如 Authentik、Keycloak、Google可取代內建 Dex
  • 高可用性SQLite 不支援叢集,需改用 PostgreSQLRelay 可獨立部署
  • 監控:已暴露 metricsPort: 9090 的 Prometheus 指標

參考資料