VPS 上访问 HTTPS 接口,curl 提示 curl: (60) SSL certificate problem,但浏览器能打开?先别把脚本都加上 -k。这个错误表示客户端没能验证服务端证书,原因可能在客户端信任库,也可能在服务端证书链、访问域名或系统时间。
这篇按“确认实际连接 → 检查证书 → 分清哪一端要修”的顺序排查,适合接口调用、安装脚本和自动化任务。TLS 校验失败发生在正常 HTTP 业务处理之前,不要和接口返回的 401、403、404 混在一起。
在报错的同一台 VPS、同一个容器或运行用户下检查,域名换成真实目标:
curl --version
date -u
curl -v --connect-timeout 10 --max-time 30 https://api.example.com/
看 curl 使用的 TLS 后端、连接地址、证书相关提示和是否经过代理。不同构建可能使用不同 CA 存储,宿主机命令正常,不代表容器或应用 SDK 也正常。分享 -v 输出前去掉 Authorization、Cookie、代理凭据和敏感 URL。
| 具体提示或现象 | 优先检查 | 常见修复方向 |
|---|---|---|
unable to get local issuer certificate | CA 来源、服务端中间证书 | 补全服务端链,或配置正确的客户端 CA |
certificate has expired | 证书有效期、当前时间 | 更新到正确证书,修复时钟偏差 |
| 域名与证书不匹配 | URL、DNS、证书 SAN、SNI | 使用正确域名并修正站点证书 |
| 自签名证书或内部 CA | 信任来源是否明确 | 为该调用配置经过核验的 CA |
| CA 文件打不开或格式错误 | 文件路径、权限和格式 | 修复本地 CA 文件,可能是错误码 77 |
curl 的证书校验说明强调,校验包括信任链和 URL 中的主机名。浏览器与 curl 的信任库、缓存和证书链补全行为可能不同,所以“浏览器能打开”不足以排除服务端配置问题。
有 OpenSSL 时,可以查看握手和服务端发送的证书:
openssl s_client -connect api.example.com:443 \
-servername api.example.com -showcerts \
-verify_hostname api.example.com -verify_return_error </dev/null
-servername 发送 SNI,-verify_hostname 检查主机名;这是两件事。-showcerts 显示服务端提供的列表,不等于这条链已经验证成功。s_client 文档还说明,诊断工具默认可能在验证错误后继续连接,这里使用 -verify_return_error 让错误中止握手。
留意叶子证书的域名、颁发者和有效期,以及中间证书是否齐全。公网网站通常需要发送叶子证书及必要中间证书,根证书通常由客户端信任库提供。不要仅凭“有两个证书”或文件叫 fullchain.pem 就认定配置正确。
若问题出在自己的 Nginx,按证书签发方要求配置完整链,语法检查后重载,再重新握手。证书申请或续期本身失败,可以看 Let’s Encrypt 续期排查。
证书签给 api.example.com,直接用 https://服务器IP/ 访问,除非证书包含该 IP 的相应 SAN,否则可能校验失败。只改 HTTP Host 请求头也不能把 URL 的证书校验目标变成另一个域名。
需要直连指定服务器来排查时,可以保留域名,覆盖该次连接的解析地址:
curl --resolve api.example.com:443:203.0.113.10 \
https://api.example.com/
这里 IP 是文档示例,必须换成已确认的目标地址。--resolve 不会关闭校验,URL 中的域名仍用于 TLS。若源站使用的证书只供特定代理信任,直连客户端仍可能失败,应核对该部署的信任设计,参考 Cloudflare 525 / 526 与源站证书排查。
如果多个正常公网 HTTPS 站点都失败,而域名和时间没有问题,检查基础镜像或系统的 CA 包是否缺失、过旧,或者被自定义路径覆盖。
在使用 apt 的 Debian / Ubuntu 系统,可按维护安排更新或修复发行版证书包:
sudo apt update
sudo apt install --reinstall ca-certificates
sudo update-ca-certificates
update-ca-certificates 手册说明了系统信任库的更新方式。这不是所有系统和 TLS 后端都通用的命令;容器也需要自己的证书包,重建镜像时应将修复写入构建过程。
同时核对是否显式配置了 --cacert、CURL_CA_BUNDLE、SSL_CERT_FILE 或 SSL_CERT_DIR。先确认这些设置原本服务于哪个业务,再修正路径或内容,不要随手清空生产环境配置。curl 的 CA 配置也不一定被 Python、Java、Node.js 等应用直接复用。
内部服务使用自建 CA 时,应从可信管理渠道取得并核验 CA 证书,而不是从当前报错连接中随便下载一个证书就信任。针对单个调用,可以显式指定:
curl --cacert /path/to/verified-root-ca.pem https://internal.example.com/
这仍然验证证书链和域名,不会让错误域名或过期证书变得有效。文件应是合适的 PEM 证书内容,不是私钥、网页或随手保存的报错页;路径也要能被实际运行用户读取。--cacert 的行为以当前 TLS 后端和 curl 参数文档为准。
把内部 CA 加入整台机器的信任库会扩大信任范围,应按组织的证书管理流程处理。若只有一个应用需要访问,优先考虑该应用明确支持的独立 CA 配置,并记录后续轮换方式。
-k / --insecure 跳过证书验证,最多说明跳过这一步后连接能继续。它不能告诉你到底是缺中间证书、不信任 CA,还是连到了错误站点,不应留在生产接口、安装脚本或定时任务中当作修复。
最终用不带 -k 的真实命令复测,并从实际运行环境确认:证书校验通过,随后得到预期的 HTTP 状态和业务响应。TLS 通过后出现 401 或 404,要进入鉴权或路由排查,不要继续改 CA。
本文在 macOS 上用 curl 8.7.1(SecureTransport)和 OpenSSL 3.6.0 完成 6 项本机检查:缺中间证书、CA 不受信和域名错误均触发 curl 60;完整链配合指定 CA 返回 200;CA 文件不存在触发 77;OpenSSL 链与域名校验通过。测试只使用临时证书与回环服务,不修改系统信任库,也不覆盖公网证书续期、Ubuntu 包安装、代理链、容器镜像或真实业务 SDK。
