PAYATHON 2026

支付宝异步通知服务器 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

不要仅凭文件名判断内容。链文件通常应按以下顺序排列:

  1. 当前域名的服务器证书。
  2. 一个或多个中间证书。

一般不需要把根证书发送给客户端,具体配置仍应以证书服务商的部署说明为准。

检查域名是否与证书匹配

假设异步通知地址是:

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 配置。

推荐的排查顺序

  1. 打印并确认实际提交的完整 notify_url。
  2. 检查 DNS 解析结果,包括 IPv4 和 IPv6。
  3. 使用带 -servername 的 openssl s_client 检查证书链。
  4. 确认证书 SAN 覆盖回调域名,并且证书处于有效期内。
  5. 使用 curl -ILv 检查跳转、TLS 握手和 HTTP 响应。
  6. 分别检查 CDN、WAF、负载均衡和所有后端节点。
  7. 查看 Nginx、Apache、Ingress 或网关的握手错误日志。
  8. SSL 问题消失后,再检查支付宝验签、幂等处理和 success 响应。

如果只能先检查一项,就确认线上入口返回的证书链是否完整。异步接口能够从外网访问,但服务器端仍提示 SSL 校验失败时,最常见的原因是缺少中间证书、域名不匹配,或者多个节点的证书不一致。

备注:内容仅供参考。