支付宝异步通知服务器 SSL 证书校验失败如何解决?
结论
支付成功、款项到账,只能证明支付链路正常。异步通知报 “SSL 证书校验失败”,通常说明支付宝服务器访问 notify_url 时,无法完成 HTTPS 握手或验证服务器证书。
常见的配置问题有:
- 服务器没有返回完整证书链,尤其是缺少中间证书。
- 证书绑定的域名与
notify_url域名不一致。 notify_url使用了 IP、内网域名、非标准端口或错误域名。- 证书已经过期、尚未生效,或者使用了自签名证书。
- 服务器的 TLS 协议或加密套件配置过旧、过于特殊。
- CDN、负载均衡、WAF 或反向代理对外提供了另一张证书。
- 回调地址发生 HTTPS、域名或端口跳转,而跳转后的证书有问题。
- 同一 IP 部署多个 HTTPS 站点时,SNI 或默认虚拟主机配置错误。
“浏览器能打开” 和 “接口能从外网访问” 都不能证明证书配置正确。浏览器可能缓存或自动补全中间证书,服务器端程序通常会严格校验证书链。
优先检查证书链是否完整
证书链不完整是最常见的原因。服务器不能只配置站点证书,还要同时返回证书颁发机构提供的中间证书链。
可以执行:
openssl s_client \
-connect pay.example.com:443 \
-servername pay.example.com \
-showcerts
重点查看输出末尾:
Verify return code: 0 (ok)
如果出现以下内容,通常说明证书链有问题:
unable to get local issuer certificate
unable to verify the first certificate
self signed certificate in certificate chain
也可以使用 curl 检查实际握手和请求过程:
curl -Iv https://pay.example.com/alipay/notify
如果 curl 报出以下错误,应先修复证书,不必急着检查业务代码:
SSL certificate problem: unable to get local issuer certificate
SSL: no alternative certificate subject name matches target host name
certificate has expired
正确配置 Web 服务器证书
以 Nginx 为例,ssl_certificate 通常应指向包含站点证书和中间证书的完整链文件,不能只配置单张站点证书。
server {
listen 443 ssl;
server_name pay.example.com;
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/private.key;
ssl_protocols TLSv1.2 TLSv1.3;
location /alipay/notify {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
}
}
修改后先检查配置:
nginx -t
确认没有问题,再重新加载:
nginx -s reload
不同证书服务商提供的文件名可能不同,例如:
fullchain.pem
certificate_chain.crt
server.crt
ca_bundle.crt
不要仅凭文件名判断内容。链文件通常应按以下顺序排列:
- 当前域名的服务器证书。
- 一个或多个中间证书。
一般不需要把根证书发送给客户端,具体配置仍应以证书服务商的部署说明为准。
检查域名是否与证书匹配
假设异步通知地址是:
https://pay.example.com/alipay/notify
证书的 Subject Alternative Name,也就是 SAN,必须覆盖 pay.example.com。
可以用下面的命令查看证书中的域名:
openssl s_client \
-connect pay.example.com:443 \
-servername pay.example.com </dev/null 2>/dev/null |
openssl x509 -noout -subject -issuer -dates -ext subjectAltName
检查时注意:
example.com的证书不一定覆盖pay.example.com。*.example.com可以覆盖pay.example.com,但不能覆盖a.pay.example.com。- 证书绑定域名后,不要在
notify_url中改用服务器 IP。 www.example.com和example.com是两个不同的主机名,除非证书同时覆盖两者。
检查支付宝实际访问的地址
应核对实际提交给支付宝的 notify_url,不能只看浏览器中手动测试的地址。
例如:
https://pay.example.com/alipay/notify
需要确认:
- 协议是否确实为
https。 - 域名是否拼写正确。
- 端口是否正确。
- 是否误用了测试环境、旧域名或内网地址。
- URL 中是否包含空格、换行或错误转义。
- 是否先访问一个域名,再通过
301、302跳转到另一个域名。 - 是否经过 CDN、WAF、SLB、Ingress 或网关。
可以查看完整的跳转过程:
curl -ILv https://pay.example.com/alipay/notify
异步通知接口通常不应依赖重定向。建议直接将最终处理通知的 HTTPS 地址配置为 notify_url,避免跳转到其他域名或端口。
排查 SNI 和多站点配置
如果同一台服务器的同一个 IP 上配置了多个 HTTPS 域名,需要确认携带 SNI 访问时返回的是正确证书。
对比下面两个命令的结果:
openssl s_client \
-connect 203.0.113.10:443 \
-servername pay.example.com </dev/null
openssl s_client \
-connect pay.example.com:443 \
-servername pay.example.com </dev/null
如果返回了其他网站的证书,通常是以下配置出了问题:
server_name配置不正确。- 默认虚拟主机返回了错误证书。
- 负载均衡没有正确传递或处理 SNI。
- DNS 指向了错误服务器。
- 部分节点仍在使用旧证书。
如果域名解析到多个 IP,应分别检查每个节点:
dig +short pay.example.com
然后逐个指定 IP 测试:
curl -Iv \
--resolve pay.example.com:443:203.0.113.10 \
https://pay.example.com/alipay/notify
所有对外节点都必须返回有效且一致的证书链。只修复其中一台,仍可能发生间歇性失败。
检查 TLS 协议和握手配置
如果证书本身没有问题,还要确认服务器支持通用的 TLS 协议和加密套件。
可以分别测试:
openssl s_client \
-connect pay.example.com:443 \
-servername pay.example.com \
-tls1_2
openssl s_client \
-connect pay.example.com:443 \
-servername pay.example.com \
-tls1_3
不建议只启用过旧协议,也不要为了 “安全加固” 只保留少量特殊加密套件,否则服务端调用方可能无法完成握手。具体兼容范围要结合当前支付宝产品文档、服务器版本和安全要求确定,不能只看浏览器测试结果。
检查 CDN、网关和防火墙
如果回调地址前面还有 CDN、云负载均衡、WAF、API 网关或 Kubernetes Ingress,支付宝连接的是最外层入口。因此,应检查入口提供的证书,而不只是源站证书。
常见问题有:
- 源站证书正确,但 CDN 仍在使用旧证书。
- CDN 没有配置完整证书链。
- IPv4 节点证书正常,IPv6 节点证书错误。
- 国内节点和海外节点返回不同证书。
- WAF 根据 User-Agent、来源地区或 IP 返回不同结果。
- 防火墙在握手阶段直接断开连接。
- 入口要求客户端证书,普通 HTTPS 客户端因而无法连接。
如果配置了双向 TLS,也就是服务端强制要求客户端证书,应先确认对应的支付宝产品是否明确支持这种接入方式。普通异步通知地址通常不能自行增加客户端证书要求。
接口业务层也要满足回调要求
SSL 问题解决后,还要确认接口能够正常处理通知。下面是简化的 PHP 示例:
<?php
$notifyData = $_POST;
// 1. 使用支付宝 SDK 验签。
// 2. 校验 app_id、seller_id、out_trade_no、金额等业务字段。
// 3. 保证重复通知时可以幂等处理。
// 4. 业务处理成功后返回纯文本 success。
$verified = verifyAlipayNotification($notifyData);
if (!$verified) {
http_response_code(400);
echo 'failure';
exit;
}
$processed = processOrderIdempotently($notifyData);
if ($processed) {
header('Content-Type: text/plain; charset=utf-8');
echo 'success';
} else {
http_response_code(500);
echo 'failure';
}
需要分清两个阶段:
- SSL 证书校验失败发生在 HTTPS 握手阶段,此时请求通常还没有进入应用代码。
- 验签失败、返回值错误或业务处理超时,发生在 HTTP 请求已经到达应用之后。
如果 Web 服务器访问日志中完全没有对应请求,应优先排查证书、DNS、端口、TLS、CDN 和防火墙。已经有请求记录时,再检查验签和业务处理。
不要用关闭证书校验来规避问题
在自己的测试程序中设置以下选项,不能解决支付宝服务器遇到的校验失败:
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
这些配置只会影响自己的客户端请求,无法改变支付宝服务器访问回调地址时的验证行为,还会降低连接安全性。
也不要因为 HTTPS 配置失败就直接改用 HTTP。是否允许 HTTP,取决于具体支付宝产品和当前接入要求。即使技术上可以使用,也可能不符合安全要求或平台规范。应当修复证书链和 TLS 配置。
推荐的排查顺序
- 打印并确认实际提交的完整
notify_url。 - 检查 DNS 解析结果,包括 IPv4 和 IPv6。
- 使用带
-servername的openssl s_client检查证书链。 - 确认证书 SAN 覆盖回调域名,并且证书处于有效期内。
- 使用
curl -ILv检查跳转、TLS 握手和 HTTP 响应。 - 分别检查 CDN、WAF、负载均衡和所有后端节点。
- 查看 Nginx、Apache、Ingress 或网关的握手错误日志。
- SSL 问题消失后,再检查支付宝验签、幂等处理和
success响应。
如果只能先检查一项,就确认线上入口返回的证书链是否完整。异步接口能够从外网访问,但服务器端仍提示 SSL 校验失败时,最常见的原因是缺少中间证书、域名不匹配,或者多个节点的证书不一致。
备注:内容仅供参考。
