# Nezha 面板三域名职责分离部署方案 (NPM + Cloudflare)

**URL:** <https://www.nodeloc.com/t/topic/94293>\
**Category:** VPS\
**Created:** [2026 年6 月 17 日 01:51 UTC](https://www.nodeloc.com/t/topic/94293 "2026-06-17T01:51:24Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

作者： ![Wade](https://www.nodeloc.com/user_avatar/www.nodeloc.com/wade/32/20398_2.png) [@Wade](https://www.nodeloc.com/u/Wade)\
发布日期： [2026 年6 月 17 日 01:51 UTC](https://www.nodeloc.com/t/topic/94293/1 "2026-06-17T01:51:24Z")

</div>

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

> 本教程基于 [Argo-Nezha-Service-Container](https://github.com/Kiritocyz/Argo-Nezha-Service-Container) 项目，在其基础上扩展多探针域名，配合 Nginx Proxy Manager (NPM) 实现职责分离架构。
> 
> **主面板域名的部署请参考原项目文档，本教程不再赘述，仅聚焦于"如何额外添加直连探针域名和 CF 探针域名"。**

* * *

## 域名说明

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

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

* * *

## 架构总览

 ![66](https://www.nodeloc.com/uploads/default/original/3X/5/c/5c584cc03867caf819b07249be21073466067ea7.png)

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

* * *

## 前置条件

- 已按照 [原项目文档](https://github.com/Kiritocyz/Argo-Nezha-Service-Container) 完成主面板域名的部署，面板可正常访问
- 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 Names | `agent-direct.mydomain3.com` | 你的直连域名 |
| Scheme | `http` | |
| Forward Hostname / IP | `127.0.0.1` | **故意填假地址！** |
| Forward Port | `9999` | **故意填不存在的端口！** |

🔴 **绝对不能填真实的面板端口（如 8080）！** 这里填假地址是拦截浏览器访问的核心机制：

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

#### SSL 选项卡

| 设置项 | 值 |
| --- | --- |
| SSL Certificate | Request a new SSL Certificate |
| Force SSL | ✅ 开启 |
| HTTP/2 Support | ✅ 开启（gRPC 强依赖 HTTP/2） |
| Use a DNS Challenge | ✅ 开启（推荐，一劳永逸免续签） |
| DNS Provider | Cloudflare |
| Credentials | `dns_cloudflare_api_token = 你的CF_API_Token` |

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

#### Advanced 选项卡（给探针开绿色通道）

```nginx
# 允许带下划线的 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](https://www.nodeloc.com/uploads/default/original/3X/7/1/717a40862b790ac55c3c9c121cefc46b3c68e0f1.png)

* * *

## 第二部分：CF 探针域名

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

### 2.1 NPM 配置

在 NPM 后台 → **Add Proxy Host** （同样必须单独新建！）

#### Details 选项卡

| 设置项 | 值 | 说明 |
| --- | --- | --- |
| Domain Names | `agent-cf.mydomain2.com` | CF 探针域名 |
| Scheme | `http` | |
| Forward Hostname / IP | `172.17.0.1` | 真实面板地址 |
| Forward Port | `8080` | 真实面板端口 |

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

#### SSL 选项卡

| 设置项 | 值 |
| --- | --- |
| SSL Certificate | Request a new SSL Certificate |
| Force SSL | ✅ 开启 |
| HTTP/2 Support | ✅ 开启 |
| Use a DNS Challenge | ✅ 开启 |
| DNS Provider | Cloudflare |
| Credentials | `dns_cloudflare_api_token = 你的CF_API_Token` |

#### Advanced 选项卡

```nginx
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-cf | [mydomain2.com](http://mydomain2.com) | `proto.NezhaService` | HTTPS | `172.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 Name | `agent-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 加速）：**

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

```

**通过直连探针域名连接（无 CDN，延迟更低）：**

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

```

* * *

## 踩坑记录与排查指南

### 坑 1：Nginx 的 “If is Evil”

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

```nginx
# ❌ 错误示范！千万不要这样写！
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 字节），说明流量根本没进来：

```bash
# 查看 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](https://dash.cloudflare.com/profile/api-tokens) 创建一个 “编辑区域 DNS” 的 Token
2. 在 NPM SSL 设置里开启 `Use a DNS Challenge`，选 Cloudflare，填入 Token
3. 证书自动续签，永不过期

* * *

## 快速排查清单

当探针连不上时，按以下顺序排查：

| 步骤 | 检查项 | 操作 |
| --- | --- | --- |
| 1 | NPM 日志是否有流量 | `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'` |

---

<div class="post-metadata">

作者： ![xiaofan0331](https://www.nodeloc.com/user_avatar/www.nodeloc.com/xiaofan0331/32/53884_2.png) [@xiaofan0331](https://www.nodeloc.com/u/xiaofan0331)\
发布日期： [2026 年6 月 17 日 07:05 UTC](https://www.nodeloc.com/t/topic/94293/2 "2026-06-17T07:05:24Z")

</div>

学习一下

---

<div class="post-metadata">

作者： ![banainiu](https://www.nodeloc.com/user_avatar/www.nodeloc.com/banainiu/32/59618_2.png) [@banainiu](https://www.nodeloc.com/u/banainiu)\
发布日期： [2026 年6 月 17 日 09:29 UTC](https://www.nodeloc.com/t/topic/94293/3 "2026-06-17T09:29:30Z")

</div>

6A大佬厉害啊

---

<div class="post-metadata">

作者： ![haibo\_zhou](https://www.nodeloc.com/letter_avatar_proxy/v4/letter/h/58956e/32.png) [@haibo\_zhou](https://www.nodeloc.com/u/haibo_zhou)\
发布日期： [2026 年6 月 18 日 06:52 UTC](https://www.nodeloc.com/t/topic/94293/4 "2026-06-18T06:52:48Z")

</div>

学习学习
