故障的表现很不讲道理:我这边打开正常,一个朋友那边一直转圈,另一个朋友能看到页面但提示证书有问题。三个人、三个网络、三种结果。
我一开始怀疑 DNS,查了一圈没有异常;又怀疑 CDN,刷新缓存后仍然有人打不开。最后把我拉出配置层的,是一条 curl -v 的输出。
先看清楚发生了什么
我用 curl 分别请求 HTTP 和 HTTPS,把跳转链完整打出来:
1 | curl -I http://example.com |
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,源站改了配置,用户仍然被带到旧地址。
这些知识在教材里都写过,但只有在对不上现象的时候,才会真正记住。
排查的顺序
这次之后,我给自己定了一个排查顺序,避免一上来就乱改:
- 确认现象:哪些网络、哪些设备、什么错误码;
- 看跳转链:
curl -I跟到底,确认没有循环; - 看证书链:
openssl s_client确认证书、中间证书和 SNI; - 看源站:绕过 CDN 直连源站,确认问题在哪一层;
- 再改配置:一次只改一个变量,改完重新验证。
这个顺序不保证一次修好,但能避免“改了很多地方,不知道是哪一步生效”。
最后
以前我觉得 HTTP 和 TLS 是“底层”,离写博客很远。这次排查让我意识到,只要站点要对外提供服务,这些协议就在每一次请求里。
理解它们不是为了手写协议栈,而是为了在现象和配置之间建立联系:看到“重定向过多”,能想到跳转链;看到“证书不受信任”,能想到证书链和 SNI;看到“有的人能打开”,能想到网络路径和缓存。
这些知识不会让站点永远不出问题,但会让问题变得可解释。可解释,才有可能被修好。