远程配置下发

Cloudflare 的 dashboard / API 可以把 ingress 规则推到连接上(cloudflared 的 update-configuration:边缘在隧道连接上另开一条 h2 流,body 是 {"version":<int32>,"config":{"ingress":[…],"originRequest":{…}}})。

本模块 默认不收这份配置 —— 注意是「连 features 里都不宣告 allow_remote_config」: 宣告了却不处理才是最坏的不对齐。想收就显式打开:

nginx
http {
    cfd_tunnel_token       eyJhIjoi...;
    cfd_tunnel_edge        region1.v2.argotunnel.com:7844;
    cfd_tunnel_remote_config on;
}

两条指令 ​

指令取值含义
cfd_tunnel_remote_config / 块内 remote_configon / off(默认 off)on = 接受远程配置;off = 连 allow_remote_config 都不宣告
cfd_tunnel_host_override / 块内 host_override一个主机名注入时把 Host 改成它,并把访客原来的 hostname 放进 X-Forwarded-Host(与 cloudflared 的 originRequest.httpHostHeader 逐字对齐)

冲突时谁赢:本地永远优先 ​

项优先级(从高到低)
是否直接应答状态码命中规则的 http_status:NNN(含 catch-all 兜底)→ 否则才谈「送到哪」
注入端口本地 inject_listen → 命中规则的 service 端口 → 503(没有任何可用规则)
注入 Host本地 host_override → 命中规则的 originRequest.httpHostHeader → 访客 hostname(不改写)

细则:

  • 开了 remote_config 就可以不写 inject_listen(块里也是):注入端口改由「命中规则的 service 端口」决定。扁平写法下原来「没写 inject_listen 就自动挑监听器」的默认行为被这个 显式开关取代 —— 开了开关就是明确要求由远程决定。
  • http_status:NNN 是「不进源站」这个决定,压过本地 inject_listen。 本地 inject_listen 替代的是源站(端口),替代不了「不进源站」。所以本地写了 inject_listen 又开了 remote_config 时,命中 http_status:403 的请求仍回 403、dashboard 默认那条 catch-all http_status:404 仍回 404,两者都不进 nginx。
  • 只对隧道流量生效。 这套判断只长在注入路径上,直连 nginx 的普通请求完全不经过远程规则表 —— 拿任意 Host 直连 inject_listen 那个端口,照样是本地的 200。
  • 没有任何规则时:没写 inject_listen ⇒ 503(cloudflared 的「没有 ingress 规则」兜底); 写了 inject_listen ⇒ 照本地端口注入。
  • 未命中任何 hostname 规则时用最后一条(cloudflared 也要求最后一条是 catch-all, 通常写 {"service":"http_status:404"})。

哪些 service 支持、哪些明确拒绝 ​

先分清两件事:非 HTTP 服务的流量根本到不了规则匹配那一步(传输层就丢了),只有 HTTP 服务 才会走到规则匹配。这不是两套可选策略,是 cloudflared 自己的分流结构。

哪一层判定覆盖的 service处置
传输层(只看流头 Cf-Cloudflared-Proxy-Src 是否非空)tcp:// ssh:// rdp:// smb:// socks-proxy bastion(含 warp-routing)这些是非 HTTP 服务,流量是被边缘塞进裸 TCP 隧道流送来的 ⇒ 直接 RST_STREAM(REFUSED_STREAM) + ERR 日志,不进 nginx。因此它们永远不会出现在规则匹配里
配置层(cfd_remote_pick())http:// https:// ws:// wss://(只取端口)、http_status:NNN、unix: unix+tls:、hello_world、未知 scheme都是 HTTP 请求,正常匹配 hostname/path。前两类逐条有确定行为;后三类本模块没有对应能力 ⇒ 本地没写 inject_listen 时回 502 + ERR(不静默改路由)

补充:

  • unix: / unix+tls: 是 HTTP 服务(官方文档原话 "Same as HTTP, but uses a Unix socket"), 不是裸 TCP。本模块没有 dial unix socket 的能力;但本地写了 inject_listen 时照常注入本地端口 —— 那是「本地即源站」的直接含义,而且这条配置能真正跑通:本地 nginx 自己就会 proxy_pass http://unix:/run/app.sock。
  • hello_world 是 cloudflared 内置的 HTTP 测试服务器(官方注明只用于本地管理的隧道), 本模块不实现:本地没写端口 ⇒ 502;写了端口 ⇒ 按「本地即源站」注入本地 nginx。
  • path 正则只支持字面量、两端的 ^/$ 与 .*/.+。用到别的正则语法([0-9]、|、?、 {n,m}、\ 等)时整份配置被拒(200 + err:"…不支持的正则语法…",旧规则表一字不动), 绝不拿一个近似的匹配顶上。
  • 远程配置体上限 4 MiB(超了按「解不出来」处理,回 502);path 超过 4096 字节时本次不做 规则匹配、按最后一条兜底(记一条 WARN)。

应答与 cloudflared 逐字对齐 ​

边缘收到的是 200 + {"lastAppliedVersion":<已应用版本>,"err":null|<原因>},或解析失败时的 502(带 cf-cloudflared-response-meta: {"src":"cloudflared"})。

版本语义也一样:currentVersion 从 -1 起,version <= 已应用版本 就整个忽略 (原样回当前版本)。配置不必等 END_STREAM —— 读到完整的一个 JSON 值就立刻应答 (json.Decoder 的行为);body 在 JSON 值结束前就断了、或外层根本解不出来(不是 JSON、 version 不是 int32)都回 502。

host_override 单独也能用 ​

不开 remote_config 也能写 host_override:本机路由时不一定知道远端的 hostname (快速隧道的随机域名就是典型),需要「按固定 Host 命中某个 server {}」时用它。

nginx
http {
    cfd_tunnel_quick_tunnel on;
    cfd_tunnel_inject_listen 127.0.0.1:8090;
    cfd_tunnel_host_override  app.internal;     # 注入时 Host: app.internal
    # X-Forwarded-Host 仍是访客请求的那个 hostname
}

模块文档 · 对齐依据是 cloudflared 的源码与实测行为