跳过正文
Background Image
  1. Posts/

Caddy 代理疑云:从指令弃用到版本“Bug

·247 字·2 分钟· loading · loading ·
yuzjing
作者
yuzjing
目录

记录了一次在使用 Caddy 反向代理中的正向代理时, 牵扯出 Caddy 指令的演进、版本间的行为差异,甚至一度让我们怀疑官方文档的准确性。

forward_proxy_url 的迷雾与版本之谜
#

我们的核心需求是在 Caddy 反向代理中,再通过一个内部的正向代理去连接最终的后端服务。这形成了一个“代理套代理”的经典场景。

目标架构用户 -> Caddy (反代) -> 内部代理 -> 后端应用

初次尝试:forward_proxy_url
#

根据经验,我们在 Caddyfile 中写下了如下配置:

1
2
3
4
5
6
7
8
9
app.example.com {
    # ...
    reverse_proxy backend-service:8188 {
        transport http {
            # 使用 forward_proxy_url 指定下一跳代理
            forward_proxy_url http://internal-proxy:1025
        }
    }
}

然而,在重载 Caddy 配置时,我收到了第一条令人困惑的信息:

{"level":"warn", ... "msg":"The 'forward_proxy_url' field is deprecated. Use 'network_proxy <url>' instead."}

这是一个明确的弃用警告。它告诉我们 forward_proxy_url 已经过时,应该使用新的 network_proxy 指令。

第二次尝试:network_proxy 的挫败
#

我们遵从警告的指示,将配置修改为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 方案 A: 将 network_proxy 放在 transport http 内部
reverse_proxy backend-service:8188 {
    transport http {
        network_proxy http://internal-proxy:1055
    }
}

# 方案 B: 将 network_proxy 作为 reverse_proxy 的直接子指令
reverse_proxy backend-service:8188 {
    network_proxy http://internal-proxy:1055
}

然而,无论采用哪种方案,Caddy 都返回了错误:unrecognized subdirective network_proxy

这让我有点疑惑:Caddy 一方面警告我们旧指令已弃用,另一方面又不认识它推荐的新指令。 另外Caddy官方文档中的介绍是forward_proxy_url, 没有任何提到network_proxy的内容。

居然是版本“Bug”
#

在反复确认我使用的 Caddy(v2.10.0)版本较新且, 并且不需要任何build自定义插件后,问题的焦点最终落在了 Caddy 自身。

最终艰难的找到了问题原因https://github.com/caddyserver/caddy/pull/6978

居然是2.10.0的版本BUG, 2.10.1中已修复…坑啊

解决方案:

最终我重新用2.10.2版本的caddy重新build了我的caddy镜像, 至于弃用警告我直接忽略了,network_proxy参数并不能正常工作, 继续使用 forward_proxy_url

1
2
3
4
5
6
# 最终有效配置:忽略警告,继续使用
reverse_proxy backend-service:8188 {
    transport http {
        forward_proxy_url http://internal-proxy:1055
    }
}

经过这次,发现即使是稳定版的软件,也可能存在文档、警告和实际行为之间的微小脱节。当遇到类似情况时,应以实际运行结果为准。


Host 头引发的浏览器重定向循环
#

在解决了代理指令的问题后,我遇到了第二个难题。通过 curl 在 Caddy 容器内测试代理链路是完全成功的:

curl -x http://internal-proxy:1055 http://backend-service:8188

curl 能完美获取后端应用的页面。然而,通过浏览器访问 https://app.example.com 却陷入了无限重定向的循环。

症状与根源
#

分析caddy日志以及研究curl和caddy动作区别发现:

curl 之所以成功,是因为它在请求后端的 Host 头中,使用的是后端的 IP 地址

而浏览器通过 Caddy 访问时,Host 头被传递为公网域名 (app.example.com)。许多后端应用在收到一个与自身监听地址不符的 Host 头时,会出于安全或规范化考虑,发起一次重定向。这个重定向往往会与 Caddy 的自动 HTTPS 功能形成冲突,导致循环。

解决方案:伪装 Host
#

为了让 Caddy 的行为与成功的 curl 命令保持一致,我们使用 header_up 强制修改了发往后端的 Host 头。

1
2
3
4
5
6
7
8
reverse_proxy backend-service:8188 {
    # 强制将 Host 头修改为后端自身的地址
    header_up Host {http.reverse_proxy.upstream.hostport}

    transport http {
        forward_proxy_url http://internal-proxy:1055
    }
}

通过这一行简单的配置,我们让后端应用收到了它“期望”的 Host 头,从而停止了不必要的重定向,浏览器访问恢复正常。

总结
#

  1. 理性看待警告:软件的弃用警告虽值得关注,但当新方案无法工作时,应相信当前版本下依然有效的旧方案。
  2. Host 头是魔鬼:在反向代理环境中,Host 头是导致“curl可以,浏览器不行”这类问题的首要疑犯。通过 header_up 控制。