200字
Nezha 面板三域名职责分离部署方案 (NPM + Cloudflare)
2026-06-17
2026-06-17

哪吒面板多域名探针部署教程(argo-nezha + NPM + CF 隧道)

本教程基于 Argo-Nezha-Service-Container 项目,在其基础上扩展多探针域名,配合 Nginx Proxy Manager (NPM) 实现职责分离架构。

主面板域名的部署请参考原项目文档,本教程不再赘述,仅聚焦于"如何额外添加直连探针域名和 CF 探针域名"。


域名说明

本教程中使用以下示例域名,请替换为你自己的真实域名:

示例域名角色说明
panel.mydomain.com主面板域名托管在 CF,按原项目文档配置即可
agent-cf.mydomain2.comCF 探针域名托管在 CF,专门给探针用,有 CDN 加速
agent-direct.mydomain3.com直连探针域名不在 CF 托管,DNS 直接指向 VPS IP

架构总览

66.png

域名用途能看面板?能连探针?拦截方式
主面板域名看数据按原项目配置
CF 探针域名CF 线路探针CF 隧道只放行 gRPC,浏览器 404
直连探针域名直连线路探针NPM 假端口 9999,浏览器 502

前置条件

  • 已按照 原项目文档 完成主面板域名的部署,面板可正常访问
  • VPS 上已部署 Nginx Proxy Manager 容器(端口映射:80:80, 443:443, 管理端口:81
  • 一个不在 CF 托管的直连域名(DNS A 记录直接指向 VPS 公网 IP)

第一部分:直连探针域名

⚠️ 直连域名没有 CF 保护,必须在 NPM 里实现拦截,防止面板被外人偷窥。

1.1 DNS 设置

在你的 DNS 服务商(非 Cloudflare)那里,添加一条 A 记录:

  • 主机名: 你的直连子域名(如 agent-direct.mydomain3.com
  • 记录值: 你 VPS 的公网 IP
  • 代理状态: 无(直连,不走 CF CDN)

1.2 NPM 配置

在 NPM 后台 → Add Proxy Host(新建一条,不要和其他域名混在一起!)

Details 选项卡(核心:用假端口实现拦截)

设置项说明
Domain Namesagent-direct.mydomain3.com你的直连域名
Schemehttp
Forward Hostname / IP127.0.0.1故意填假地址!
Forward Port9999故意填不存在的端口!

🔴 绝对不能填真实的面板端口(如 8080)! 这里填假地址是拦截浏览器访问的核心机制:

  • 浏览器访问时路径为 /,Nginx 找不到高级规则匹配,就会把请求送到这个假地址,直接 502 报错
  • 探针访问时路径为 /proto.NezhaService/,命中高级规则,走绿色通道进入真实的 8008 端口

SSL 选项卡

设置项
SSL CertificateRequest a new SSL Certificate
Force SSL✅ 开启
HTTP/2 Support✅ 开启(gRPC 强依赖 HTTP/2)
Use a DNS Challenge✅ 开启(推荐,一劳永逸免续签)
DNS ProviderCloudflare
Credentialsdns_cloudflare_api_token = 你的CF_API_Token

💡 强烈建议使用 DNS Challenge! 这样证书到期后会全自动续签,不需要开放 80 端口。
获取 API Token 的地址:https://dash.cloudflare.com/profile/api-tokens
创建令牌时选择 编辑区域 DNS (Edit zone DNS) 模板,选中对应域名即可。

Advanced 选项卡(给探针开绿色通道)

# 允许带下划线的 Header 透明传输(哪吒认证必备)
underscores_in_headers on;

# 仅针对探针通讯路径进行 gRPC 转发
location /proto.NezhaService/ {
    grpc_read_timeout 300s;
    grpc_send_timeout 300s;
    grpc_socket_keepalive on;
    
    # 透传认证信息
    grpc_set_header password $http_password;
    grpc_set_header Authorization $http_authorization;
    
    # 伪装主域名的 Host 头(哪吒服务端只认主域名)
    grpc_set_header Host panel.mydomain.com;
    
    # 转发到真实的哪吒内部通讯端口
    grpc_pass grpc://172.17.0.1:8008;
}

🔴 grpc_set_header Host panel.mydomain.com; 这行是命根子! 哪吒服务端只认最初配置的主域名,不管你从哪个域名连进来,都必须伪装成主域名才能通过认证。

1.3 工作原理

88.png

第二部分:CF 探针域名

ℹ️ 由于 argo-nezha 的 cloudflared 跑在容器内部,localhost:443 会连到容器自己的内部代理。为了走 NPM 的 gRPC 规则,必须把转发目标改成 172.17.0.1:443(指向宿主机的 NPM)。

2.1 NPM 配置

在 NPM 后台 → Add Proxy Host(同样必须单独新建!)

Details 选项卡

设置项说明
Domain Namesagent-cf.mydomain2.comCF 探针域名
Schemehttp
Forward Hostname / IP172.17.0.1真实面板地址
Forward Port8080真实面板端口

ℹ️ 这里可以填真实地址,因为 CF 隧道在海外节点已经把浏览器流量拦截了(只放行 proto.NezhaService 路径),普通浏览器根本到不了 NPM 这一层。

SSL 选项卡

设置项
SSL CertificateRequest a new SSL Certificate
Force SSL✅ 开启
HTTP/2 Support✅ 开启
Use a DNS Challenge✅ 开启
DNS ProviderCloudflare
Credentialsdns_cloudflare_api_token = 你的CF_API_Token

Advanced 选项卡

underscores_in_headers on;

location /proto.NezhaService/ {
    grpc_read_timeout 300s;
    grpc_send_timeout 300s;
    grpc_socket_keepalive on;
    
    grpc_set_header password $http_password;
    grpc_set_header Authorization $http_authorization;
    
    # 同样必须伪装主域名
    grpc_set_header Host panel.mydomain.com;
    
    grpc_pass grpc://172.17.0.1:8008;
}

2.2 CF 隧道配置

在 Cloudflare Zero Trust → Tunnels → 你的隧道 → Public Hostname 中,为 CF 探针域名添加规则:

子域名路径服务类型URL
agent-cfmydomain2.comproto.NezhaServiceHTTPS172.17.0.1:443

🔴 这里是最大的坑!必须填 172.17.0.1:443,绝对不能填 localhost:443 因为 cloudflared 跑在 Docker 容器内部,localhost 指向的是容器自己,不是宿主机。172.17.0.1 是 Docker 网桥的网关地址,从容器内部看就是宿主机。

⚠️ 只添加 proto.NezhaService 这一条路径规则!不要添加留空(匹配所有)的兜底规则! 这样当浏览器试图访问这个域名时,CF 隧道找不到匹配的路径,会直接返回 404,实现完美拦截。

额外设置(Additional application settings)

展开 TLS 设置:

设置项说明
No TLS Verify✅ 开启跳过证书验证
HTTP2 connection✅ 开启gRPC 强依赖 HTTP/2
Origin Server Nameagent-cf.mydomain2.com极度关键!见下方说明

🔴 Origin Server Name 必须填域名! 如果不填,cloudflared 在 TLS 握手时会用 IP 地址 172.17.0.1 作为 SNI。NPM 根据 SNI 来路由流量,认不出 IP 地址就会把流量扔进默认垃圾桶,返回 502。

展开 HTTP 设置

设置项说明
HTTP Host Header留空!不要填!Host 伪装在 NPM 里做,不在这里做

⚠️ HTTP Host Header 必须留空! 如果你在这里填了主域名(如 panel.mydomain.com),会导致 TLS 的 SNI(agent-cf.mydomain2.com)和 HTTP 的 Host 头(panel.mydomain.com)不一致。NPM 检测到 SNI 和 Host 头的矛盾,会直接返回 421 Misdirected Request。Host 伪装的工作交给 NPM 高级设置里的 grpc_set_header Host panel.mydomain.com; 来做。

2.3 CF 域名面板设置

在 Cloudflare 域名管理后台 → 网络 (Network) → 确保 gRPC 开关已开启。

🔴 如果不开启 gRPC,CF 会把探针的 gRPC 流量当成普通 HTTP 处理,直接返回 403 Forbidden。


第三部分:探针客户端启动命令

在需要监控的机器上,使用以下命令启动探针:

通过 CF 探针域名连接(有 CDN 加速):

./nezha-agent -s agent-cf.mydomain2.com:443 --tls --client-secret 你的密钥

通过直连探针域名连接(无 CDN,延迟更低):

./nezha-agent -s agent-direct.mydomain3.com:443 --tls --client-secret 你的密钥

踩坑记录与排查指南

坑 1:Nginx 的 "If is Evil"

🔴 永远不要在 NPM 的高级设置里使用 if 语句做全局拦截!

# ❌ 错误示范!千万不要这样写!
if ($http_content_type !~ "application/grpc") {
    return 444;
}

Nginx 的 if 语句会破坏配置继承机制,导致 underscores_in_headers on; 等指令失效,密码 Header 被吞掉,所有域名的探针全部报 Unauthenticated

✅ 正确的拦截方式:用假端口(9999)实现路由级别的物理隔离,无需 if


坑 2:不同域名必须分开配置

🔴 在 NPM 里,不同用途的域名必须各自新建一条 Proxy Host!

NPM 的一条 Proxy Host 里,Details 的转发目标对所有填入的域名一视同仁。如果你把"要看面板的主域名"和"要拦截面板的探针域名"塞进同一条规则,它们就会共享同一个转发目标,拦截必然失败。


坑 3:argo-nezha 容器内的 localhost ≠ 宿主机

🔴 argo-nezha 镜像的 cloudflared 跑在容器内部!localhost 是容器自己,不是宿主机!

如果你在 CF 隧道里填 localhost:443,流量会被送到容器内部的代理,永远不会到达宿主机的 NPM。必须改成 172.17.0.1:443(Docker 网桥网关地址 = 宿主机)。

排查方法: 检查 NPM 对应域名的访问日志是否为空。如果是空文件(0 字节),说明流量根本没进来:

# 查看 NPM 生成的配置文件
docker exec NPM容器名 sh -c 'cat /data/nginx/proxy_host/*.conf'

# 查看对应域名的访问日志
docker exec NPM容器名 sh -c 'tail -20 /data/logs/proxy-host-N_access.log'

坑 4:Origin Server Name 必须填

🔴 CF 隧道 TLS 设置里的 Origin Server Name 必须填写目标域名!

当 cloudflared 连接 172.17.0.1:443 时,如果不指定 Origin Server Name,TLS 握手的 SNI 会是 IP 地址 172.17.0.1。NPM 根据 SNI 路由流量,认不出 IP 就扔进默认垃圾桶,返回 502。


坑 5:HTTP Host Header 和 Origin Server Name 不能打架

🔴 CF 隧道的 HTTP Host Header 必须留空!

如果 HTTP Host Header 填了主域名(用于伪装),而 Origin Server Name 填了探针域名,NPM 会检测到 SNI 和 Host 头不一致,返回 421 Misdirected Request

Host 伪装应该在 NPM 的 Advanced 里用 grpc_set_header Host 来做,让 CF 隧道只负责老老实实地把流量送进 NPM 的正确大门。


坑 6:SSL 证书申请失败(Internal Error)

如果 NPM 申请 Let's Encrypt 证书时报 Internal Error,通常是因为 80 端口验证不通。

解决方案 A(临时放行):

  1. 在 CF 隧道临时加一条留空路径规则,指向 http://172.17.0.1:80
  2. NPM 申请证书成功后,立刻删掉这条临时规则

解决方案 B(一劳永逸,推荐):
使用 DNS Challenge 方式申请证书,完全不需要 80 端口。

  1. https://dash.cloudflare.com/profile/api-tokens 创建一个 "编辑区域 DNS" 的 Token
  2. 在 NPM SSL 设置里开启 Use a DNS Challenge,选 Cloudflare,填入 Token
  3. 证书自动续签,永不过期

快速排查清单

当探针连不上时,按以下顺序排查:

步骤检查项操作
1NPM 日志是否有流量docker exec NPM容器名 sh -c 'tail -20 /data/logs/proxy-host-N_access.log'
2日志为空流量没到 NPM → 检查 CF 隧道目标是不是 172.17.0.1:443
3日志有记录但 502检查 CF 隧道 TLS 的 Origin Server Name 是否填了域名
4日志有记录但 421检查 CF 隧道 HTTP 设置的 Host Header 是否留空
5报 Unauthenticated检查 Advanced 里是否有 grpc_set_header Host 主面板域名;
6报 403 Forbidden检查 CF 域名面板 Network 里 gRPC 开关是否开启
7查看实际配置docker exec NPM容器名 sh -c 'cat /data/nginx/proxy_host/*.conf'

评论