远程配置下发
Cloudflare 的 dashboard / API 可以把 ingress 规则推到连接上(cloudflared 的 update-configuration:边缘在隧道连接上另开一条 h2 流,body 是 {"version":<int32>,"config":{"ingress":[…],"originRequest":{…}}})。
本模块 默认不收这份配置 —— 注意是「连 features 里都不宣告 allow_remote_config」: 宣告了却不处理才是最坏的不对齐。想收就显式打开:
http {
cfd_tunnel_token eyJhIjoi...;
cfd_tunnel_edge region1.v2.argotunnel.com:7844;
cfd_tunnel_remote_config on;
}两条指令
| 指令 | 取值 | 含义 |
|---|---|---|
cfd_tunnel_remote_config / 块内 remote_config | on / 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-allhttp_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 {}」时用它。
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
}