1. 部署前先理解架构

这套方案里一共有 3 个关键部分:

  • Caddy:运行在宿主机,占用 80/tcp443/tcp
  • dashboard:NetBird 控制台前端,运行在 Docker 中
  • netbird-server:NetBird 的 management / signal / API / embedded IdP,运行在 Docker 中

另外还有一个必须直接暴露到公网的端口:

  • 3478/udp:STUN 端口,不能走 HTTP 反向代理

推荐的流量路径如下:

  • 浏览器访问 https://netbird.example.com
  • Caddy 根据请求类型,把流量转发给:
    • 127.0.0.1:8080 上的 dashboard
    • 127.0.0.1:8081 上的 netbird-server

2. 域名是不是必须

建议当作必须。

原因:

  • Caddy 申请公开 TLS 证书需要域名
  • NetBird 客户端连接控制端也更适合固定域名
  • embedded IdP 的回调地址本身就是 https://你的域名/...

所以本文默认你已经准备好一个域名,例如:

netbird.example.com

并且 DNS 已解析到你的服务器公网 IP。

3. 前提条件

至少满足下面这些条件:

  • 一台公网 Linux 服务器
  • 已安装 Docker
  • 已安装 docker compose
  • 已安装 Caddy
  • 已安装 openssl
  • 已放行端口:
    • 80/tcp
    • 443/tcp
    • 3478/udp

如果你使用云服务器,还要检查云厂商安全组。

4. 目录规划

下面用 /opt/netbird 作为示例目录:

/opt/netbird/
├── docker-compose.yml
├── config/
│   └── config.yaml
└── env/
    └── dashboard.env

创建目录:

sudo mkdir -p /opt/netbird/config /opt/netbird/env
sudo chown -R "$USER":"$USER" /opt/netbird
cd /opt/netbird

5. 先生成两个随机密钥

config.yaml 里至少会用到两个随机值:

  • authSecret
  • encryptionKey

生成方式示例:

openssl rand -base64 32
openssl rand -base64 32

你可以先把它们记下来,例如:

AUTH_SECRET=替换成你生成的第一个随机串
ENCRYPTION_KEY=替换成你生成的第二个随机串

注意:

  • 这两个值不要随便改
  • encryptionKey 最好备份
  • 如果你已经有正式环境,不要轻易重建这两个值

6. 手工创建 docker-compose.yml

文件路径:

/opt/netbird/docker-compose.yml

内容如下:

services:
  dashboard:
    # NetBird 控制台前端
    image: netbirdio/dashboard:latest
    container_name: netbird-dashboard
    restart: unless-stopped

    # 只绑定到本机回环地址,避免被公网直接访问
    ports:
      - "127.0.0.1:8080:80"

    # 控制台前端的环境变量文件
    env_file:
      - ./env/dashboard.env

    networks:
      - netbird

  netbird-server:
    # NetBird combined container,内含 management / signal / embedded IdP
    image: netbirdio/netbird-server:latest
    container_name: netbird-server
    restart: unless-stopped

    ports:
      # HTTP / API / gRPC / WebSocket 只开放给本机,由宿主机 Caddy 反代
      - "127.0.0.1:8081:80"

      # STUN 端口必须直接暴露到公网
      - "3478:3478/udp"

    volumes:
      # 主配置文件
      - ./config/config.yaml:/etc/netbird/config.yaml:ro

      # 持久化数据目录,包含 sqlite 数据和 embedded IdP 数据
      - netbird_data:/var/lib/netbird

    command:
      - --config
      - /etc/netbird/config.yaml

    networks:
      - netbird

volumes:
  netbird_data:
    # 使用 Docker volume 持久化数据

networks:
  netbird:
    driver: bridge

关于镜像版本

上面示例用了 latest,是为了写法直观。

生产环境更建议你把镜像固定成明确版本,例如:

image: netbirdio/netbird-server:vX.Y.Z
image: netbirdio/dashboard:vX.Y.Z

这样后续升级更可控。

7. 手工创建 config.yaml

文件路径:

/opt/netbird/config/config.yaml

内容如下,把域名和密钥换成你自己的:

server:
  # 容器内实际监听地址
  listenAddress: ":80"

  # 对外暴露给客户端的 management 地址
  exposedAddress: https://netbird.example.com:443

  # 内置 STUN 监听端口列表
  stunPorts:
    - 3478

  # 内部指标端口,不对外暴露
  metricsPort: 9090

  # 容器内部健康检查地址,不对外暴露
  healthcheckAddress: ":9000"

  # 日志等级,可选 info / debug / warn / error
  logLevel: info

  # 日志输出到 stdout
  logFile: console

  # embedded IdP 和内部认证使用的随机密钥
  authSecret: "替换成你生成的 AUTH_SECRET"

  # 数据目录,要和 docker-compose 里的持久化目录对应
  dataDir: /var/lib/netbird

  # 让 NetBird 信任来自宿主机 Caddy 的 X-Forwarded-* 头
  reverseProxy:
    trustedHTTPProxies:
      - 127.0.0.1/32
      - ::1/128

  auth:
    # 使用内置 embedded IdP
    issuer: https://netbird.example.com/oauth2

    # 是否启用签名密钥自动轮换
    signKeyRefreshEnabled: true

    # Dashboard 登录后的回调地址
    dashboardRedirectURIs:
      - https://netbird.example.com/nb-auth
      - https://netbird.example.com/nb-silent-auth

    # CLI 登录回调地址
    cliRedirectURIs:
      - http://localhost:53000/

  store:
    # 默认使用 sqlite,数据会写到 dataDir 中
    engine: sqlite

    # sqlite 模式下可以留空
    dsn: ""

    # 用于加密数据库中敏感字段的密钥
    encryptionKey: "替换成你生成的 ENCRYPTION_KEY"

这份配置里最关键的 6 个值

  • server.exposedAddress
  • server.stunPorts
  • server.authSecret
  • server.auth.issuer
  • server.store.engine
  • server.store.encryptionKey

只要这几个值的域名和密钥没写错,基础部署通常就能跑起来。

为什么这里加了 reverseProxy.trustedHTTPProxies

官方脚本在自动生成配置时,会让服务信任它前面的反向代理。

你现在的反向代理是宿主机上的 Caddy,所以这里把 127.0.0.1/32::1/128 加进去,让 NetBird 正确识别 X-Forwarded-ForX-Forwarded-Proto 等请求头。

8. 手工创建 dashboard.env

文件路径:

/opt/netbird/env/dashboard.env

内容如下:

# Dashboard 自己对外展示的域名
NETBIRD_MGMT_API_ENDPOINT=https://netbird.example.com

# Dashboard 调用后端 API 的地址
NETBIRD_MGMT_GRPC_API_ENDPOINT=https://netbird.example.com

# embedded IdP 的发行者地址
AUTH_AUTHORITY=https://netbird.example.com/oauth2

# embedded IdP 的 audience
AUTH_AUDIENCE=netbird-dashboard

# Dashboard 的 OAuth Client ID
AUTH_CLIENT_ID=netbird-dashboard

# embedded IdP 默认无需 client secret
AUTH_CLIENT_SECRET=

# embedded IdP,不是 Auth0
USE_AUTH0=false

# Dashboard 登录回调地址
AUTH_REDIRECT_URI=/nb-auth

# 静默刷新登录态的回调地址
AUTH_SILENT_REDIRECT_URI=/nb-silent-auth

# embedded IdP 使用的 scope
AUTH_SUPPORTED_SCOPES=openid profile email groups

# dashboard 容器内部 nginx 的 HTTPS 端口变量
NGINX_SSL_PORT=443

# 关闭 dashboard 内置证书申请,由现有 Caddy 接管 TLS
LETSENCRYPT_DOMAIN=none

说明:

  • 这里的 NETBIRD_MGMT_API_ENDPOINTAUTH_AUTHORITY 都要指向同一个外部域名
  • AUTH_AUDIENCE=netbird-dashboardAUTH_CLIENT_ID=netbird-dashboard 是 embedded IdP 默认配套值
  • AUTH_REDIRECT_URIAUTH_SILENT_REDIRECT_URI 必须和 config.yaml 里的 redirect URIs 对应

9. 启动容器

启动前先检查文件:

cd /opt/netbird
docker compose config

如果没有报错,再启动:

docker compose up -d
docker compose ps

查看日志:

docker compose logs -f

10. 配置宿主机 Caddy 反向代理

如果你的 Caddy 运行在宿主机上,可以直接把下面这个站点块放进:

/etc/caddy/Caddyfile

示例配置如下:

# 你可以把这段作为可复用片段
(netbird_common) {
    # 压缩响应
    encode zstd gzip

    # 一些基础安全头,可按自己现网风格调整
    header {
        Strict-Transport-Security "max-age=31536000; includeSubDomains"
        X-Content-Type-Options nosniff
        X-Frame-Options SAMEORIGIN
        Referrer-Policy no-referrer-when-downgrade
    }

    # 访问日志,可按需删掉
    log {
        output file /var/log/caddy/netbird-access.log {
            roll_size 20MiB
            roll_keep 10
            roll_keep_for 720h
        }
        format json
    }
}

netbird.example.com {
    import netbird_common

    # gRPC 请求转发到 netbird-server
    @grpc header Content-Type application/grpc*
    reverse_proxy @grpc h2c://127.0.0.1:8081

    # API / OAuth2 / WebSocket 等后端请求也交给 netbird-server
    @backend path /relay* /ws-proxy/* /api/* /oauth2/*
    reverse_proxy @backend 127.0.0.1:8081

    # 其他前端页面请求交给 dashboard
    reverse_proxy 127.0.0.1:8080
}

如果你的系统里还没有 /var/log/caddy/,可以二选一:

  • 先创建该目录并给 Caddy 写权限
  • 或者先删掉上面示例里的 log 配置块

这段 Caddy 配置为什么不能只写一个 reverse_proxy

因为 NetBird 不是单纯的静态网页:

  • 控制台页面来自 dashboard
  • API、OAuth2、WebSocket、gRPC 来自 netbird-server

如果你把所有请求都反代到 dashboard,页面可能能打开,但登录、设备注册、CLI 登录或客户端连接经常会出问题。

11. 校验并重载 Caddy

先校验:

sudo caddy validate --config /etc/caddy/Caddyfile

再重载:

sudo systemctl reload caddy

如果你不是 systemd 管理 Caddy,按你的实际启动方式重载即可。

12. 首次初始化

浏览器访问:

https://netbird.example.com/setup

第一次访问会进入初始化页面,用于创建第一个管理员账号。

创建完成后:

  • 你可以登录 Dashboard
  • 再去添加设备
  • 客户端和 CLI 会通过这个域名完成认证和管理连接

13. 启动后建议做的 5 个检查

1)检查容器状态

docker compose ps

2)检查本地监听端口

ss -lntup | grep -E '8080|8081|3478'

你应该看到:

  • 127.0.0.1:8080
  • 127.0.0.1:8081
  • 0.0.0.0:3478 或对应的 UDP 监听

3)检查 Caddy 反代前的本地访问

curl -I http://127.0.0.1:8080
curl -I http://127.0.0.1:8081

4)检查 HTTPS 是否正常

curl -I https://netbird.example.com

5)检查初始化页面

浏览器打开:

https://netbird.example.com/setup

14. 常用维护命令

拉取最新镜像:

cd /opt/netbird
docker compose pull

重新创建容器:

docker compose up -d

查看日志:

docker compose logs -f

重启:

docker compose restart

停止:

docker compose down

15. 常见问题

1)网页能打开,但客户端连不上

优先检查:

  • 3478/udp 是否真的已放行
  • 云厂商安全组是否开放 3478/udp
  • server.exposedAddress 对应的域名是否正确
  • server.stunPorts 是否包含 3478

2)登录时报错,或者回调页失败

优先检查:

  • config.yaml 里的 dashboardRedirectURIs
  • dashboard.env 里的 AUTH_REDIRECT_URI
  • 域名是不是前后一致
  • 是否混用了 httphttps

3)证书申请失败

优先检查:

  • 域名 DNS 是否已生效
  • 80/tcp443/tcp 是否可以从公网访问
  • 是否有别的服务占用了 80/443

4)能打开控制台,但接口偶发 502

优先检查:

  • Caddy 是否按本文区分了 gRPC 和普通 HTTP 请求
  • reverse_proxy 指向的本地端口是否写对
  • 容器是否真的监听在 127.0.0.1:8080127.0.0.1:8081

5)后面我还想启用 NetBird Proxy

这时不要继续沿用本文方案。

官方说明 NetBird Proxy 依赖 Traefik 提供 TLS passthrough。也就是说:

  • 本文方案适合“管理面 + 客户端接入”
  • 如果你要启用 NetBird Proxy,请改用 Traefik 路线

16. 什么时候该考虑改成 PostgreSQL

如果你只是自用、小团队或轻量使用,先用默认 sqlite 就够了。

如果你有下面这些需求,再考虑把 store.engine 改成 PostgreSQL:

  • 用户量明显增大
  • 希望把数据库单独备份
  • 希望更方便做高可用或迁移

本文先不展开 PostgreSQL 版,先把最小可用的手工部署跑通。

17. 最小可用清单

如果你只想快速核对有没有漏项,看这 8 条就够了:

  • 有公网域名,例如 netbird.example.com
  • DNS 已解析到服务器公网 IP
  • 宿主机已安装 Docker、docker compose、Caddy、openssl
  • 防火墙已放行 80/tcp443/tcp3478/udp
  • /opt/netbird/docker-compose.yml 已创建
  • /opt/netbird/config/config.yaml 已创建
  • /opt/netbird/env/dashboard.env 已创建
  • /etc/caddy/Caddyfile 已加入 NetBird 站点块

18. 参考文档