docs.sujiuovo.top

Nginx 的 TLS 排错:SNI、证书链与 443 是谁在听

2026-07-11nginx 1.24 / OpenSSL 3.0

一台机器上同时跑三四个站点是很常见的事。为了省一个 IP,大家都挤在 443 上,靠 SNI 分流。这时候如果某个域名握手报错,第一反应通常是去看证书有没有过期——但十次里有七八次,问题不在这里。

先确认站在哪块 server 里

server_name 匹配不上时,Nginx 会把请求交给该端口的 default_server。如果 default_server 是一块 return 444 的兜底配置,你看到的现象就是连接被直接掐断,而不是一个像样的错误页。

# 看 Nginx 自己怎么理解这些配置(会展开 include)
nginx -T | grep -nE '^\s*(listen|server_name|ssl_certificate) '

# 用目标 SNI 握手,看实际返回哪张证书
echo | openssl s_client -connect 127.0.0.1:443 \
  -servername example.com 2>/dev/null \
  | openssl x509 -noout -subject -issuer -dates

如果 subject 不是你期望的域名,说明要么 SNI 没传到,要么这块 server 没匹配上。前者多半是中间还有一层转发(CDN、负载均衡、或者另一个监听 443 的进程),后者通常是拼写、通配符优先级,或者 server_name 后面多了个点。

容易忽略的一点:同一端口上如果既有 listen 443 ssl 又有 listen 443 ssl http2,它们的协议能力并不一致。http2 写在 listen 行里的老语法在新版本里已经弃用,混用时行为很难预期。

证书链:单张证书不等于能验证

把 fullchain.pem 写成了 cert.pem,浏览器有时能过(因为它会去补中间证书),但 curl、Go、Java 这些严格的客户端会直接报 unable to get local issuer certificate。

# 看服务端到底发了几张证书
echo | openssl s_client -connect example.com:443 -servername example.com \
  -showcerts 2>/dev/null | grep -c 'BEGIN CERTIFICATE'

# 应该是 2 张(站点证书 + 中间证书),1 张就是链不全

链不全的修法很简单:签发工具一般都会输出 fullchain.pem,ssl_certificate 指向它就行,不要指向只有叶子证书的那个文件。

ALPN 与 HTTP/2 的坑

有些客户端(尤其是自己写的健康检查)只支持 HTTP/1.1。如果 server 只协商 h2,握手会失败在 ALPN 阶段,日志里连一条 access 记录都没有。用 openssl s_client -alpn http/1.1 手动指定一次,能快速区分是「协议没谈拢」还是「证书不对」。

echo | openssl s_client -connect example.com:443 \
  -servername example.com -alpn http/1.1 2>/dev/null | grep 'ALPN protocol'

反向代理场景下的 421

Nginx 1.19 之后对 Host 和 SNI 不一致的连接会返回 421 Misdirected Request。前面挂了一层 CDN、回源时 Host 被改写,很容易命中。要么让回源保持同一个域名,要么在明确知道来源可信时关掉这项检查:

server {
    listen 8443 ssl;
    server_name example.com;
    # 回源由固定的一层代理发起,可以放宽
    http2 on;
    # 旧版本没有这个指令,靠 http2 指令控制
}

小结

← 返回归档