HTTPS 相关问题最容易让人有一种“看起来都差不多”的错觉。浏览器里可能只是弹出一个证书警告,客户端里可能只打印一句握手失败,但真正出错的环节可能完全不同:证书过期、证书链不完整、域名不匹配、SNI 不正确、协议协商失败、ALPN 不一致,甚至是回源链路的 TLS 配置不兼容。
HTTPS 问题定位最需要建立的一种意识是:不要把所有握手失败都叫作“证书问题”。TLS 是一段完整协商过程,失败点不同,处理方式也完全不同。
为什么要把证书和握手分开理解
证书当然重要,但证书只是 HTTPS 成功建立的一部分。
在一次 TLS 握手里,客户端要先声明自己支持什么版本、什么密码套件、是否支持某些扩展,同时还会通过 SNI 告诉服务端自己想访问哪个域名。服务端则要返回正确证书、选定协商方案,并与客户端继续完成密钥建立。
所以 HTTPS 失败可能有两大类。
一类是“身份不可信”,更偏证书本身。
另一类是“握手协商不成立”,更偏协议兼容、扩展信息和链路过程。
把这两类混在一起,排障就很容易一开始就模糊。
证书链为什么会单独成为问题
很多时候证书看起来没过期、域名也对,但客户端还是报错,原因就在于证书链不完整。
服务端可能只返回了站点证书,却没有把中间证书一并返回;某些客户端本地缓存了中间链,所以浏览器正常,而命令行程序或部分 SDK 却失败。
这类问题特别典型,因为它很容易制造“有的人正常,有的人不正常”的表象。如果你没有“证书链是独立问题”的意识,就会很难解释这种差异。
SNI 为什么在多站点场景里特别关键
当多个 HTTPS 站点共用同一个 IP 时,服务端必须知道客户端到底要访问哪个域名,才能返回正确证书。这就是 SNI 的意义。
如果客户端没有带正确 SNI,或者中间代理把相关信息搞乱了,就可能拿到默认站点证书,最后表现为证书与域名不匹配。
这类问题在 CDN、反向代理和多租户接入场景里尤其常见。你表面上看到的是 HTTPS 失败,实质上却是“访问对象没有在握手开始时被正确声明”。
ALPN 又是在解决什么
ALPN 主要用于在握手阶段协商后续应用层协议,例如 HTTP/1.1 还是 HTTP/2。
对很多简单访问来说,它不一定是第一优先级问题;但在某些网关、CDN、代理和高性能传输场景里,ALPN 协商异常会直接影响后续协议行为,甚至表现成访问不兼容、协议降级或请求异常。
所以当你已经确认基本证书没问题,但某些客户端仍然表现怪异时,ALPN 也是值得继续看的一个方向。
排查 HTTPS 问题时最实用的命令
1 | curl -v https://example.com |
curl -v 更适合先看现象,包括连接目标、握手大致进展和错误位置;openssl s_client 则更适合继续看证书链、验证结果、协商协议和更细的 TLS 信息。
如果你拿到了完整输出,通常已经足够回答下面几个问题。
- 问题发生在建连前还是握手阶段。
- 是域名不匹配、证书不可信,还是协商失败。
- 服务端返回的证书是否完整。
- 协商出来的协议版本和握手结果是否符合预期。
常见误判
第一个常见误判,是把所有 HTTPS 异常都归因到证书过期。过期只是最显眼的一种,实际线上还有大量问题来自证书链、SNI 和兼容性。
第二个常见误判,是认为浏览器能打开就说明 HTTPS 完全正常。浏览器比很多命令行客户端和 SDK 更“聪明”,本地缓存和兼容策略也更丰富,它正常并不代表所有客户端都正常。
第三个常见误判,是只看应用日志而不看握手阶段。很多 HTTPS 失败根本没进入应用层,请求甚至还没到 Nginx 的业务处理部分。
一个值得自己做的小实验
对一个正常 HTTPS 站点执行:
1 | curl -v https://example.com |
尝试记录下面这些信息。
- 返回证书的主题名和访问域名是否一致。
- 证书链是否完整。
- 协商出来的是 TLS 哪个版本。
- 是否可以观察到 ALPN 协议协商结果。
如果条件允许,再对同一 IP 上不同域名做对比,会更容易感受到 SNI 为什么重要。
小结
HTTPS 问题之所以容易让人困惑,不是因为它太玄,而是因为它把身份验证、加密协商和应用协议协商都压缩进了一段握手里。只要你把证书链、SNI、ALPN 和 TLS 过程拆开理解,很多原本只会被统称为“HTTPS 异常”的问题,就会开始显露出更具体的结构。排障最怕把不同问题混成一个名字,而 HTTPS 恰恰最需要这种拆解能力。