↓ 跳过正文

525 错误:我写的源站规则,吃掉了我自己的证书

·199 字·1 分钟
Dorm Room Dev
作者
Dorm Room Dev
每天挑一个 GitHub 仓库,亲手跑一遍,再把真实体验发出来——不吹、不抄 README。周五:Weekly AI Flex。

这个博客今晚上线了。不过不是挂在好看的域名上,而是挂在 blog.sp1128node.duckdns.org:9443——带着端口号,因为 Caddy 在我的小 VPS 上就是在这个端口提供服务的。最后一步本来应该是最简单的:把 blog.sp1128.top 通过 Cloudflare 指到这台机器,再加一条源站规则(Origin Rule),把回源端口改写到 9443(源站的 443 端口是另一个服务在用),然后享受干净的 URL。

结果折腾了三轮。三轮全是我自己的问题。

症状
#

DNS 记录加好了(开启代理,指向 198.44.52.74)。源站规则也建了:当主机名是 blog.sp1128.top 时,把回源端口改写为 9443。打开 https://blog.sp1128.top:

Error 525 —— SSL 握手错误。

Cloudflare 能连到源站,但 TLS 握手死掉了。而博客本体在 duckdns 地址上一切正常,证书俱全。也就是说源站不是不会 TLS——只是不给这个主机名用。

走过的弯路
#

第一个怀疑对象:Caddy 根本没有 blog.sp1128.top 的证书。这一点是对的。几个小时前装博客时,这个主机名在 DNS 里还不存在,Caddy 的日志也写得很明白:

challenge failed … "detail":"DNS problem: NXDOMAIN looking up A for blog.sp1128.top"

那好办——现在 DNS 有了,重启 Caddy,让 ACME 把证书签出来就是。结果:还是 525。而且 caddy reload 连试都不试——它回了一句 “config is unchanged”,直接跳过了重新签发。只有把容器整个重启,才逼它重新尝试了一次。

而这次尝试又失败了,但日志有意思多了。

根因
#

http-01:      "detail":"… Invalid response from
              http://blog.sp1128.top/.well-known/acme-challenge/…: 400"
tls-alpn-01:  "detail":"Cannot negotiate ALPN protocol \"acme-tls/1\"
              for tls-alpn-01 challenge"

手动探了一下验证地址,拿到了决定性证据——一个 400,响应体是 Caddy 的 TLS 端口在说:

Client sent an HTTP request to an HTTPS server.

我的源站规则把发往 blog.sp1128.top 的每一个请求都改写到了 9443——包括普通 HTTP。而 Let’s Encrypt 的 HTTP-01 验证恰恰是一个明文 HTTP GET。Cloudflare 忠实地从源站的 9443 端口去取它,而那个端口只说 TLS。明文撞上 TLS 端口,换回一个 400,验证永远过不了。TLS-ALPN-01 也救不了:中间隔着代理,acme-tls/1 这个 ALPN 协议根本协商不起来。

服务器本身是无辜的:就在几分钟前,同一套 HTTP-01 流程刚给 duckdns 那个主机名签出过证书(它直连源站,日志是 served key authentication → certificate obtained successfully)。唯一坏掉的路径,就是经过我这条规则的那条。证书生不出来,是因为我为站点写的规则,把接生婆的请求给吃了。

出门前还踩了最后一个小坑:我的第一版修法 (http.host eq "blog.sp1128.top" and http.request.scheme eq "https") 被 Cloudflare 的校验直接拒了——在源站规则这个阶段,根本没有 http.request.scheme 这个字段。

修法
#

把规则限定为只对 HTTPS 请求生效,用源站规则真正提供的那个布尔字段:

(http.host eq "blog.sp1128.top" and ssl)

现在 HTTPS 流量的端口会被改写到 9443,而普通 HTTP 流量留在 80 端口——那正是 HTTP-01 验证需要落的地方。再重启一次 Caddy(docker restart panels-caddy-1),大约 90 秒后,日志里出现了我想看的那一行:

"certificate obtained successfully","identifier":"blog.sp1128.top",
"issuer":"acme-v02.api.letsencrypt.org-directory"

curl https://blog.sp1128.top → 200。干净的 URL、真正的 Let’s Encrypt 证书,之后由 Caddy 自动续期——而且续期时的 HTTP-01 也能一直通过,正是因为这条规则再也不碰 HTTP 了。

教训
#

改写流量去向的规则,作用范围要收得越窄越好——按 scheme、按路径、按一切你能命名的条件来限定。一条不限范围的规则不只是"为你的站点服务",它会拦截一切只是提到你站点的东西,包括敲前门的证书颁发机构。