这个博客今晚上线了。不过不是挂在好看的域名上,而是挂在 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、按路径、按一切你能命名的条件来限定。一条不限范围的规则不只是"为你的站点服务",它会拦截一切只是提到你站点的东西,包括敲前门的证书颁发机构。