广告位 · 页头(后台-模块管理-新建模块后替换此区域)

Nginx SSL握手失败提示证书链不完整如何修复?

使用Nginx配置HTTPS站点时,浏览器可以正常打开页面,但换成curl、Python请求或某些移动端APP却报出SSL握手失败,提示证书链不完整或无法验证证书。这类问题经常让运维人员困惑:明明证书是CA签发的,为什么仍然不受信任?多数情况下,根因并不是证书本身有问题,而是Nginx在TLS握手阶段没有把中间证书一并发送给客户端。

Nginx SSL握手失败提示证书链不完整如何修复?

证书链由服务器证书、中间证书和根证书组成。客户端本地通常只预置了根证书和少量常用中间证书,它不会主动去服务器以外的地方获取中间证书。因此,服务器必须在Certificate消息中按顺序提供站点证书以及所有必要的中间证书。如果Nginx配置文件里的ssl_certificate指向的文件只包含站点证书,客户端就缺少验证签发关系所需的中间证书,进而认为证书不可信,握手被中断。

一、证书链不完整为什么会导致握手失败

签发SSL证书的CA机构不会直接用根证书给站点证书签名。根证书通常保存在离线设备中,CA会使用根证书签发一个或多个中间证书,再用中间证书去签发用户购买的站点证书。客户端验证站点证书时,会从站点证书开始,沿着签发者关系逐级向上验证,直到找到一个自己信任的根证书为止。这个验证过程依赖服务器在握手阶段提供完整的中间证书链。根证书可以省略,因为客户端已经有信任库;但中间证书如果缺少,验证链路就断了。

不同客户端对缺失中间证书的容忍程度不一样。浏览器有时会通过AIA扩展自动下载中间证书,所以用户用浏览器访问可能感觉不到问题。但命令行工具、服务端HTTP客户端、IoT设备、部分移动端框架通常不会自动获取中间证书,它们只信任服务器直接发送的内容。这类客户端一旦拿不到中间证书,就会直接报错,比如curl出现unable to get local issuer certificate,Java环境出现unable to find valid certification path to requested target。Nginx本身不会报证书链错误,因为它只负责把配置的证书发出去,无法发现链是否完整。

要理解这个现象,可以观察一次异常握手。服务器返回的Certificate消息可能只包含一张证书,其issuer字段指向一个中间CA,但这个中间CA并没有出现在消息中。客户端收到后无法校验,只能终止连接。因此修复方向很明确:把中间证书补进证书文件,让Nginx在握手中发送完整的链。

二、先确认当前Nginx返回的证书链是否完整

排查时不要先改配置,应该先用命令查看服务器实际返回的内容。最直接的工具是openssl s_client。执行下面的命令,可以从目标服务器抓取证书链并打印出来:

openssl s_client -connect yourdomain.com:443 -servername yourdomain.com -showcerts </dev/null

命令输出中会包含多段BEGIN CERTIFICATE到END CERTIFICATE的内容,每一段就是一张证书。证书块上方有s:和i:两行信息,s:表示该证书的subject,也就是证书主体;i:表示issuer,也就是签发者。如果输出只有一张站点证书,并且它的issuer不是根证书,而是一个没有继续出现的中间CA,就说明中间证书缺失。客户端拿不到后面的签发者,自然无法完成验证。

还可以查看本地证书文件的签发关系,确认自己持有的文件是否已经包含中间证书。命令如下:

openssl x509 -in your_domain.crt -noout -subject -issuer

如果subject和issuer一致,说明这张证书是自签名根证书;如果issuer是某个中间CA,那么你需要找到该中间CA签发的下级中间证书或根证书对应的链。对于一个正常签发的站点证书,通常至少有一级中间证书。部分证书链可能包含两到三级中间证书,特别是某些企业级CA或交叉签名场景。缺失任意一环都会导致验证失败。

还可以把服务器返回的证书链保存成文件后,用openssl verify命令验证。例如将s_client输出中所有BEGIN CERTIFICATE到END CERTIFICATE内容复制到server_chain.pem,然后执行:

openssl verify -untrusted server_chain.pem your_domain.crt

如果输出OK,说明链是完整的;如果出现unable to get local issuer certificate,就继续看具体是哪一级证书找不到签发者。这个结果可以帮助判断到底缺了哪张中间证书。

三、补齐中间证书并调整Nginx配置

中间证书通常可以从证书颁发机构提供的下载页面获取,文件扩展名可能是.pem、.crt或.ca-bundle。你需要把站点证书与中间证书合并成一个文件。顺序非常重要:站点证书放在最前面,接下来是签发站点证书的中间证书,再往后是更高级的中间证书。不要包含根证书,因为客户端已经有根证书,多余的根证书只会增加握手体积,部分老客户端还可能因此出错。

如果已有站点证书文件your_domain.crt和中间证书文件intermediate.crt,可以使用cat命令完成拼接:

cat your_domain.crt intermediate.crt > chained.crt

如果存在多级中间证书,就按层级顺序依次追加。例如顺序为站点证书、第一级中间证书、第二级中间证书,命令可以写成:

cat your_domain.crt intermediate1.crt intermediate2.crt > chained.crt

拼接完成后,可以用文本编辑器打开chained.crt,确认里面包含多个BEGIN CERTIFICATE和END CERTIFICATE块,并且站点证书在第一块。检查证书数量与签发关系是否合理,最后把Nginx配置中的ssl_certificate指向这个完整链文件。配置片段如下:

server {
    listen 443 ssl;
    server_name yourdomain.com;
    ssl_certificate /etc/nginx/ssl/chained.crt;
    ssl_certificate_key /etc/nginx/ssl/your_domain.key;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers off;
}

修改配置后不要急着reload。先执行nginx -t检查配置语法,确认没有拼写错误和路径错误。同时检查证书文件和私钥文件是否可读,私钥文件权限尽量保持600,证书文件可以是644。如果配置中原先只写了站点证书路径,那么现在指向完整链文件后,Nginx会在握手中把该文件的所有证书依次发送,客户端就能顺利完成校验。

四、重新握手验证并避开常见误区

重载Nginx后,需要再次使用openssl s_client抓取证书链,确认服务器返回的证书数量已经增加。此时输出中应该先出现站点证书,然后出现一个或多个中间证书,最后一张中间证书的issuer应当是一个客户端已信任的根证书。如果输出中仍然只有一张证书,检查是否加载了旧的配置文件或证书路径写错。

更严谨的验证方法是在本地用根证书对完整链文件做openssl verify。如果CA提供的是根证书root.pem,可以执行:

openssl verify -CAfile root.pem chained.crt

结果为chained.crt: OK即表示链本身没有问题。还可以访问SSL Labs等技术站点进行线上测试,查看Chain部分是否显示完整。需要注意的是,如果使用Let's Encrypt等自动化签发工具,证书目录中通常已经提供了fullchain.pem文件,直接把它配置为ssl_certificate即可,不需要手动拼接。如果某些面板只有证书文件和私钥文件,也可以在服务器上执行openssl x509检测证书链信息,及时发现遗漏。

常见的坑包括:中间证书顺序反了,客户端看到站点证书的issuer与下一张证书对不上;把私钥文件当成中间证书拼接进去;只把根证书拼到站点证书后面而没有放中间证书;修改了证书文件但忘记重载Nginx。只要按照握手中实际发送的内容来排查,就能快速定位并修复证书链不完整导致的SSL握手失败问题。

广告位 · 文章底部(后台-模块管理替换此区域)