故障的表现很不讲道理:我这边打开正常,一个朋友那边一直转圈,另一个朋友能看到页面但提示证书有问题。三个人、三个网络、三种结果。

我一开始怀疑 DNS,查了一圈没有异常;又怀疑 CDN,刷新缓存后仍然有人打不开。最后把我拉出配置层的,是一条 curl -v 的输出。

先看清楚发生了什么

我用 curl 分别请求 HTTP 和 HTTPS,把跳转链完整打出来:

1
2
3
curl -I http://example.com
curl -I https://example.com
curl -vI https://example.com 2>&1 | head -n 40

HTTP 返回 301 到 HTTPS,HTTPS 又返回 301 回 HTTP。浏览器在这两个地址之间来回跳,最后报“重定向次数过多”。

问题出在 Nginx 和 CDN 的配置叠加:源站的 HTTP 配置里有一条“全站跳 HTTPS”,CDN 的回源协议又配成了 HTTP。请求到了 CDN 是 HTTPS,回源变成 HTTP,源站再把它跳回 HTTPS,循环就形成了。

这是一个典型的“每一层单独看都对,叠在一起就错”的问题。

证书链与 SNI

修好跳转之后,还有一位朋友提示证书不受信任。我一开始以为证书过期了,检查后发现有效期正常。

用 openssl 看握手过程:

1
openssl s_client -connect example.com:443 -servername example.com -showcerts

输出里能看到服务器返回的证书链。问题很明确:只返回了站点证书,没有返回中间证书。桌面浏览器通常会自动补全,但部分移动端和旧系统不会,于是报“证书链不完整”。

我把中间证书补进 fullchain.pem,重新加载 Nginx,问题消失。这件事让我第一次真正理解“证书链”不是一个抽象概念,而是服务器在握手时必须主动交给客户端的一串文件。

-servername 这个参数也很关键。一台服务器上可能有多个站点、多张证书,客户端通过 SNI 告诉服务器“我要访问哪个域名”,服务器才能返回对应的证书。不带 SNI 去测,很可能拿到默认站点的证书,然后得出错误结论。

HSTS 的教训

排查过程中我一度想直接开 HSTS,让浏览器以后只走 HTTPS,省得再跳来跳去。

幸好我先查了一下。HSTS 的 max-age 会被浏览器记住,一旦配置错误、证书又出问题,用户连“继续访问”的选项都没有,只能等过期或者手动清状态。对一个还在调整证书的站点来说,这个风险太大。

最后我只设了很短的 max-age,确认稳定后再逐步加长。协议带来的不只有安全,还有回退成本。

HTTP 这一层也不简单

修完 TLS,我又回头看 HTTP。几个以前没注意的细节,在这次排查里都变得具体:

  • 301 和 302 的区别:301 会被浏览器和 CDN 缓存,改错之后清起来很麻烦;临时跳转应该用 302。
  • Host 与 SNI 是两件事:Host 是 HTTP 层的头,SNI 是 TLS 握手时的字段,反向代理配置错任何一个,都会出现“证书对但页面不对”。
  • HTTP/2 的多路复用:开启后浏览器行为会变化,某些旧的抓包方式看不到完整的明文请求,排查时要换工具。
  • 缓存头会影响跳转:CDN 缓存了一个 301,源站改了配置,用户仍然被带到旧地址。

这些知识在教材里都写过,但只有在对不上现象的时候,才会真正记住。

排查的顺序

这次之后,我给自己定了一个排查顺序,避免一上来就乱改:

  1. 确认现象:哪些网络、哪些设备、什么错误码;
  2. 看跳转链:curl -I 跟到底,确认没有循环;
  3. 看证书链:openssl s_client 确认证书、中间证书和 SNI;
  4. 看源站:绕过 CDN 直连源站,确认问题在哪一层;
  5. 再改配置:一次只改一个变量,改完重新验证。

这个顺序不保证一次修好,但能避免“改了很多地方,不知道是哪一步生效”。

最后

以前我觉得 HTTP 和 TLS 是“底层”,离写博客很远。这次排查让我意识到,只要站点要对外提供服务,这些协议就在每一次请求里。

理解它们不是为了手写协议栈,而是为了在现象和配置之间建立联系:看到“重定向过多”,能想到跳转链;看到“证书不受信任”,能想到证书链和 SNI;看到“有的人能打开”,能想到网络路径和缓存。

这些知识不会让站点永远不出问题,但会让问题变得可解释。可解释,才有可能被修好。