证书链已经显示OK,加上主机名却报 error 62: hostname mismatch,先别急着重签。链信任回答"谁签的、是否可信",名称验证回答"是不是我要访问的服务"。下面用OpenSSL 1.1.1的参数把这两件事拆开,再检查SNI选证书和Shell退出码。能连上,不等于找对了人。
一、error 62检查的是名称,不是所有TLS问题
error 62是主机名不匹配,常见位置是叶子证书的depth 0;error 20/21则指向链构建问题。错误编号不等于Shell退出码,脚本不能拿进程返回值62当判断条件。多个检查也可能同时失败,要保留完整输出。
这里要纠正一个容易说满的结论:OpenSSL默认名称检查在没有DNS类型SAN时,可以回退到subject的CN;有DNS SAN时,默认不再靠CN补救。现代HTTPS应按SAN部署,RFC 9525不再接受CN-ID。命令行兼容旧证书,不代表浏览器也接受它。

二、把SAN读全,别只截两行
准备叶子证书leaf.pem、受信任根root.pem及中间证书intermediate.pem。fullchain不是天然可信的根库。先确认读的是最终证书,而非CSR或本地另一个同名文件;SAN可能换行,固定截取两行容易漏掉名称。
bash
openssl x509 -in leaf.pem -noout -subject -issuer -dates
openssl x509 -in leaf.pem -noout -ext subjectAltName
openssl x509 -in leaf.pem -noout -sha256 -fingerprint
没有DNS SAN但CN匹配时,OpenSSL可能通过;这应当促使你补齐现代客户端需要的SAN,而不是宣布证书通用兼容。记录指纹,后面才能比较"磁盘上的"和"入口发出的"是否同一张。
三、同一份材料,分开验证链与域名
在Bash里逐条运行,分别记录退出码。加入sslserver用途检查;根和中间证书角色不要对调。下例关闭默认CA目录,避免旁边的信任材料干扰结果。
bash
HOST=api.example.com
openssl verify -no-CApath -CAfile root.pem -untrusted intermediate.pem \
-purpose sslserver leaf.pem
printf "chain_rc=%s\n" "$?"
openssl verify -no-CApath -CAfile root.pem -untrusted intermediate.pem \
-purpose sslserver -verify_hostname "$HOST" leaf.pem
printf "hostname_rc=%s\n" "$?"
只验链通过、带名称失败,才把重心转向目标名称和SAN。HOST只填域名,不带https://、端口或路径。若两条都失败,先处理链和用途;不要靠关闭验证,把红灯拆掉当修好了。
四、连接IP、SNI和验证名是三个输入
连接IP决定去哪里,SNI帮助服务端选择证书,验证名决定客户端接受谁。三者应分别显式设置;固定某个节点IP时,SNI和验证名通常仍是业务域名。HTTP Host属于后续请求路由,不能替代TLS名称验证。
将示例保留地址换成获准检测的节点。下面是独立Bash片段:子Shell关闭errexit,确保失败后仍能保存PIPESTATUS;外层若启用errexit,仍可据其非零状态停止。
bash
(
set +e
set -o pipefail
IP=192.0.2.10
PORT=443
SNI=api.example.com
NAME=api.example.com
openssl s_client -connect "$IP:$PORT" -servername "$SNI" \
-verify_hostname "$NAME" -verify_return_error \
-no-CApath -CAfile root.pem </dev/null 2>&1 | tee sclient.log
st=("${PIPESTATUS[@]}")
printf "tls_rc=%s log_rc=%s\n" "${st[0]}" "${st[1]}"
(( st[0] == 0 && st[1] == 0 ))
)
普通管道的?可能只是tee成功。pipefail虽能报非零,也不能区分TLS失败还是日志写失败;这里立即复制整个数组,再分别判定。别先echo或保存?,它们会覆盖PIPESTATUS。默认s_client是诊断工具,验证报错后仍可能退出0,所以还要启用verify_return_error并看握手详情。
五、验证IP用verify_ip,别把64写成62
如果访问身份本来就是IP,应匹配iPAddress SAN,不能把IP字符串放进DNS SAN或CN就算数。此时使用专门的参数:
bash
openssl verify -no-CApath -CAfile root.pem -untrusted intermediate.pem \
-purpose sslserver -verify_ip 192.0.2.10 leaf.pem
printf "ip_rc=%s\n" "$?"
IP不匹配是error 64: IP address mismatch,不是error 62。仅仅固定连接IP、但访问身份仍为域名时,不要改用verify_ip。默认完整通配符*.example.com匹配一个左侧标签,不覆盖裸域或a.b.example.com;多个SAN也不豁免链、用途和其他策略检查。
六、换证后仍失败,先确认入口发了什么
别只盯证书文件日期。负载均衡某个节点未更新、SNI选到默认站点,或应用传入了另一个验证名,都可能让本地验证与线上结果分叉。逐入口保留名称、SAN、指纹和时间,不要把一个节点的成功外推给全站。
| 现象 | 信号 | 先做什么 |
|---|---|---|
| 链构建失败 | error 20/21 | 检查根与中间证书 |
| 域名不匹配 | error 62 | 核对验证名和DNS SAN |
| IP不匹配 | error 64 | 检查iPAddress SAN |
| 日志写入失败 | log_rc非零 | 检查路径与空间,勿归因于证书 |
七、回归要有反例,边界也要写清
正例之外,换错误验证名、错误根,确认探针确实会拒绝;再模拟日志写入失败,避免把磁盘问题报成证书故障。离线验证与回环TLS实验能说明参数行为,不能冒充浏览器或生产验收。本文配套复现限定OpenSSL 1.1.1k、临时根和中间CA,回环握手固定TLS 1.2;不修改系统CA和生产服务。
八、收尾检查与官方依据
收尾保留四组证据:完整SAN与指纹;链和名称各自结果;固定IP、SNI、验证名的严格握手;TLS与日志的独立退出码。修复后回到真实客户端验证HTTP响应和业务行为,证书名称正确不等于授权正确。共享日志前去除内部地址和会话信息,不上传私钥。
参数与规范可核对:OpenSSL verify手册、s_client手册及RFC 9525服务身份规范。老版本实验用于解释行为,不是部署版本建议。