# curl: (60) SSL certificate problem 报错原因与正确修法

> curl 报错 60 表示无法验证服务器证书链。多数是服务器没发中间证书或本机 CA 证书包缺失，应修证书链或指定 CA，而不是加 -k。

Source: https://ssl.sh/zh/errors/curl-60-ssl-certificate-problem/
Last updated: 2026-11-10

**`curl: (60) SSL certificate problem` 的意思是：curl 连上了服务器，但没法从服务器证书一路验证到它信任的根证书。** 大多数情况是服务器没发送中间证书；其余情况是本机 CA 证书包缺失、过旧，或者缺少公司内网的私有根证书。

## 快速修复

先分清是服务器证书链不完整，还是本机信任库有问题，两者修法不同。在一台干净的机器上执行：

```bash
openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null
```

- 输出里只有一张证书（只有 `depth=0`），最后是 `Verify return code: 21 (unable to verify the first certificate)` 或 `20 (unable to get local issuer certificate)`：**服务器证书链不完整**，到服务器上装完整证书链，见[缺少中间证书](https://ssl.sh/zh/errors/missing-intermediate-certificate/)。
- 服务器发了完整的链，其他机器访问也正常：**是你这台机器的信任库有问题**。更新 CA 证书包，或者让 curl 用正确的根证书：

```bash
curl --cacert /path/to/company-root.pem https://example.com
# 当前 shell 里所有 curl 都生效
export CURL_CA_BUNDLE=/path/to/ca-bundle.pem
```

也可以把域名输入 [SSL 检测工具](https://ssl.sh/zh/tools/ssl-checker/)，直接看服务器实际发出的证书链。

## 原因

服务器没发中间证书是最常见的原因，其次是本机 CA 证书包缺失或过旧。

### 1. 服务器没发中间证书

这是最常见的原因。服务器应该发送站点证书和全部中间证书（根证书可以不发）。浏览器会帮你掩盖这个错误，curl 不会。

- **确认：** `openssl s_client -showcerts` 只列出一张证书，它的 `issuer` 是某个中间 CA 而不是根证书。
- **修复：** Nginx 的 `ssl_certificate` 要指向 `fullchain.pem`（站点证书在前、中间证书在后），不要用 `cert.pem`；Apache 2.4.8 以上把完整链写进 `SSLCertificateFile`。阿里云、腾讯云下载的证书包里一般已有 bundle 或 chain 文件，按服务器类型选对文件。手上只有站点证书时，可以用[证书链补全工具](https://ssl.sh/zh/tools/chain-builder/)生成完整链。

### 2. 本机 CA 证书包缺失或过旧

精简版 Docker 镜像、老旧的 CentOS 6/7 系统经常没装或没更新 CA 证书包，新根证书签发的网站就会验证失败。

- **确认：** `curl -v https://example.com 2>&1 | grep -i cafile` 显示的文件不存在，或者里面没有服务器所用的根证书。
- **修复：** 安装或更新系统的 `ca-certificates` 包；也可以从 curl.se 下载 Mozilla 证书包，用 `--cacert` 指定。

### 3. 企业代理、上网行为管理或杀毒软件做了 HTTPS 解密

很多公司的上网行为管理、安全网关会解密 HTTPS 再用自己的根证书重新签名，部分杀毒软件的“HTTPS 扫描”也是这样。系统证书库里装了这个根证书，但 curl 用的证书包里没有，于是报 60。

- **确认：** `openssl s_client` 输出里的 `issuer` 是代理厂商或公司名，而不是公开 CA。
- **修复：** 向 IT 部门要这张根证书，加入系统信任库（见下文），或用 `--cacert` 指定。

### 4. 自签名证书或内部私有 CA

内网服务、测试环境常用自签名证书或公司自建 CA，这时报错文字通常是 `self-signed certificate` 或 `self-signed certificate in certificate chain`。

- **修复：** 分发内部根证书并显式信任，详见[证书链中有自签名证书](https://ssl.sh/zh/errors/self-signed-certificate-in-certificate-chain/)。

### 5. 域名不匹配或证书过期

如果报错里有 `subject name does not match` 或 `certificate has expired`，说明证书链本身验证通过了，但证书的域名或有效期不对。修改证书的 [SAN](https://ssl.sh/zh/glossary/san/) 列表或重新签发、续期。

## 各环境修法

修法只有两种：更新系统 CA 证书包，或者把私有根证书追加进系统信任库；各环境只是命令不同。

**Debian / Ubuntu**

```bash
sudo apt-get install --reinstall ca-certificates
sudo cp company-root.crt /usr/local/share/ca-certificates/
sudo update-ca-certificates
```

**CentOS / RHEL / Rocky / 阿里云 Alibaba Cloud Linux**

```bash
sudo yum reinstall -y ca-certificates
sudo cp company-root.pem /etc/pki/ca-trust/source/anchors/
sudo update-ca-trust
```

**Alpine 和 Docker 镜像**

```dockerfile
RUN apk add --no-cache ca-certificates
COPY company-root.crt /usr/local/share/ca-certificates/
RUN update-ca-certificates
```

国内构建时如果 `apk add` 很慢，可以先把 Alpine 源换成阿里云或清华等国内镜像，但不要因此跳过 `ca-certificates` 的安装。Debian 系镜像用 `apt-get install -y ca-certificates`。

**macOS：** 系统自带的 curl 用钥匙串。在“钥匙串访问”里把根证书导入“系统”钥匙串并设为“始终信任”。Homebrew 安装的 curl 用自己的证书包，用 `curl -V` 和 `curl -v` 输出里的 `CAfile:` 确认。

**Windows：** Windows 10/11 自带的 curl.exe 使用 Schannel 和 Windows 证书库，在管理员命令行执行 `certutil -addstore -f Root company-root.cer` 导入根证书即可。

**PHP、Git 等基于 libcurl 的程序**会遇到同样的问题，见 [PHP cURL error 60](https://ssl.sh/zh/errors/php-curl-error-60/) 和 [Git SSL certificate problem](https://ssl.sh/zh/errors/git-ssl-certificate-problem/)。

## 不要这样做

`-k` 一类的做法会关闭证书校验，换证书包则可能引入来历不明的信任根。

- **在脚本里用 `curl -k` / `--insecure`。** 它会关闭全部证书校验，连域名校验也一起关掉。中间人可以直接读到 API 密钥、篡改下载的安装包。
- **往 `~/.curlrc` 里写 `insecure`。** 这会让该账号以后所有 curl 调用悄悄失去校验，问题被掩盖很久都不会被发现。
- **用论坛、网盘里来路不明的证书包替换系统证书包。** 用系统包管理器或 curl.se 发布的证书包，私有根证书是“追加”进去，而不是“替换”掉原有的。
