# unsupported certificate purpose：公网证书已不含 clientAuth

> 2026 年起公网 TLS 证书不再含 clientAuth 扩展用途，用 Let's Encrypt 等公网证书做 mTLS 客户端证书会失败。原因、确认方法和私有 CA 迁移方案。

Source: https://ssl.sh/zh/errors/client-auth-eku-removed/
Last updated: 2026-11-10

**这个报错的意思是：要求客户端证书的服务器收到了一张只允许“服务器认证”用途的证书。** 2026 年起，Let's Encrypt 等公网 CA 签发的 TLS 证书不再包含“TLS 客户端认证”[扩展密钥用途（EKU）](https://ssl.sh/zh/glossary/extended-key-usage/)，所以原来用公网证书做客户端一侧的[双向 TLS（mTLS）](https://ssl.sh/zh/learn/mtls/)，续期之后就会失败。

## 快速修复

修法是改用私有 CA 签发带 clientAuth 用途的客户端证书。先确认客户端出示的证书有哪些扩展用途：

```bash
openssl x509 -in client.pem -noout -ext extendedKeyUsage
```

如果只显示 `TLS Web Server Authentication`，说明证书缺少 clientAuth。也可以把证书粘贴到[证书解析](https://ssl.sh/zh/tools/certificate-decoder/)里查看。改用私有 CA 签发客户端证书，并让服务器信任这个 CA：

```bash
# 一次性：创建私有 CA（ca.key 要离线保存或放进 HSM/KMS）
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes \
  -keyout ca.key -out ca.pem -days 3650 -subj "/CN=Example Internal Client CA"

# 每个客户端：私钥、CSR，再签发带 clientAuth 的证书
openssl req -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes \
  -keyout client.key -out client.csr -subj "/CN=billing-service"
printf "extendedKeyUsage=clientAuth\nkeyUsage=digitalSignature\n" > client.ext
openssl x509 -req -in client.csr -CA ca.pem -CAkey ca.key -CAcreateserial \
  -out client.pem -days 90 -extfile client.ext
```

```nginx
ssl_client_certificate /etc/nginx/client-ca.pem;   # 只放私有 CA
ssl_verify_client on;
```

## 原因

这个报错有 3 种原因，绝大多数是公网证书续期后丢了 clientAuth。

### 1. 公网证书续期后丢了 clientAuth

- **发生了什么：** Chrome 根证书计划政策（v1.8）要求公网 CA 把服务器认证和客户端认证拆成两套体系：2026 年 6 月 15 日及以后向 CCADB 披露的中间 CA 证书只能含 serverAuth；2027 年 3 月 15 日及以后签发的用户证书必须带 EKU 且只声明 serverAuth。各家 CA 按自己的时间表更早移除了 clientAuth：Let's Encrypt 在 2026 年 2 月 11 日把 clientAuth 从默认的 `classic` 配置中移除，可选的 `tlsclient` 配置保留到 2026 年 7 月 8 日；新的 Generation Y 证书只含服务器认证。
- **确认：** 出错的证书来自公网 CA，最近刚续期，上面的命令只显示服务器认证。
- **修复：** 按上文把客户端一侧换成私有 CA 签发的证书。

### 2. 服务器之间互相认证

有些系统让同一张证书“两头用”：服务 A 调服务 B 时出示自己的网站证书；XMPP、邮件服务器在主动外连时出示服务器证书；数据库或消息队列的复制节点之间用公网证书互相认证。

- **确认：** 报错出现在接收方服务器的日志里，发起方用的是自己的公网网站证书作为客户端身份。
- **修复：** 给每个节点再配一张私有 CA 签发的“客户端角色”证书；协议允许的话，也可以改用共享密钥、OAuth 2.0 客户端凭据、签名令牌等认证方式。

### 3. 私有 CA 签发时没加对用途

如果已经在用私有 CA，仍然报错 26，说明客户端证书或某一级中间 CA 签发时没有包含 clientAuth。

- **确认：** 对客户端证书和每一级中间证书都执行上面的检查；中间 CA 如果带 EKU 扩展，也必须包含 clientAuth。
- **修复：** 用 `extendedKeyUsage=clientAuth` 重新签发。

## 各环境修法

各环境的修法相同：客户端证书改由私有 CA 签发，服务器的客户端信任库只放这个私有 CA。

**Nginx / 宝塔：** 设置 `ssl_verify_client on`，`ssl_client_certificate` 只指向私有 CA 证书文件，把公网根证书从这个文件里删掉。宝塔面板里在站点配置文件中手动加这两行即可。

**Apache httpd：** `SSLVerifyClient require`，`SSLCACertificateFile /etc/httpd/client-ca.pem`。

**Java 客户端：** 把私有 CA 签发的客户端私钥和证书导入 PKCS#12 密钥库（`-Djavax.net.ssl.keyStore=client.p12`）。只含 serverAuth 的证书会被拒绝，提示类似 “Extended key usage does not permit use for TLS client authentication”。

**Go：** 服务端用 `tls.RequireAndVerifyClientCert` 时会报 `x509: certificate specifies an incompatible key usage`。`ClientCAs` 只放私有 CA。

**Kubernetes 和服务网格：** 工作负载证书用 cert-manager 的 CA 或 Vault Issuer 签发；Istio、Linkerd 等服务网格本来就用自己的私有 CA 签发 mTLS 证书。

**对接银行、支付、政企接口：** 这类接口通常由对方指定 CA 或由对方签发客户端证书，按对方要求申请，不要拿自己网站的公网证书去对接。

## 不要这样做

以下做法都能让报错消失，但 mTLS 也就不再起认证作用。

- **关闭用途检查**（比如把 OpenSSL 的 purpose 强制设成 any，或改验证代码）。服务器会接受并非用来标识客户端的证书。
- **用 `ssl_verify_client optional_no_ca` 让报错消失。** Nginx 将接受任何客户端证书而不验证签发者，等于取消了认证。
- **让服务器用公网根证书库验证客户端。** 该 CA 的任何证书持有人都能通过认证，客户端信任锚必须是私有的。
- **到处找还在混发服务器/客户端用途的 CA 来维持旧方案。** 就算现在还有，按 Chrome 根证书计划政策，2027 年 3 月 15 日起签发的用户证书必须只声明 serverAuth，这条路有明确的终点。
