# x509: certificate relies on legacy Common Name field（Go）

> Go 1.15 起不再接受只在 CN 里写域名的证书，Go 1.17 删除了 GODEBUG 开关。唯一修法是重新签发带 SAN 的证书。

Source: https://ssl.sh/zh/errors/go-x509-certificate-relies-on-legacy-common-name/
Last updated: 2026-11-10

**`x509: certificate relies on legacy Common Name field, use SANs instead` 的意思是：服务器证书只在使用者 CN 里写了主机名，没有 SAN（使用者可选名称）扩展，而 Go 已经不再接受这种证书。** 必须重新签发带 SAN 的证书，当前的 Go 版本在客户端没有任何开关可以绕过。

## 快速修复

唯一的修法是重新签发带 SAN 的证书。先确认证书确实没有 SAN：

```bash
openssl s_client -connect registry.internal.example.com:443 -servername registry.internal.example.com </dev/null 2>/dev/null \
  | openssl x509 -noout -subject -ext subjectAltName
```

如果只看到 `subject=CN = registry.internal.example.com`，没有 `X509v3 Subject Alternative Name` 段，就需要重新签发。用内部 CA 签发的做法：

```bash
# 1. 生成带 SAN 的私钥和 CSR
openssl req -new -newkey rsa:2048 -nodes -keyout server.key -out server.csr \
  -subj "/CN=registry.internal.example.com" \
  -addext "subjectAltName=DNS:registry.internal.example.com,IP:203.0.113.10"

# 2. 签发时把 SAN 写进证书
cat > san.ext <<'EOF'
subjectAltName = DNS:registry.internal.example.com, IP:203.0.113.10
extendedKeyUsage = serverAuth
EOF
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
  -days 365 -sha256 -extfile san.ext -out server.crt
```

注意 `openssl x509 -req` 默认**不会**把 CSR 里的扩展复制到证书里，网上很多教程生成的证书没有 SAN 就是这个原因；上面的 `-extfile` 解决了这一点。

## 原因

没有 SAN 的证书有 4 种来源，最常见的是多年前按教程生成、不带任何扩展的内部证书。

### 1. 老旧的内部证书或自签名证书

典型的是用 `openssl req -x509 -subj "/CN=host"` 生成的证书。

- **确认：** [证书解析工具](https://ssl.sh/zh/tools/certificate-decoder/)显示有 CN，没有 SAN。
- **修复：** 按上面的方法重新签发，或用 [CSR 生成器](https://ssl.sh/zh/tools/csr-generator/)生成（它总会带上 SAN）。

### 2. 内部 CA 签发时丢掉了 SAN

一些自己写的签发脚本和老版本 CA 软件会签发 CSR，但忽略 CSR 里的扩展。

- **确认：** CSR 里有 SAN（`openssl req -in server.csr -noout -text`），签出来的证书却没有。
- **修复：** 让 CA 复制或添加 `subjectAltName`（OpenSSL 用 `-extfile`，或在 CA 配置里写 `copy_extensions = copy`）。

### 3. 设备自带的默认证书

存储阵列、服务器 BMC 管理口、老防火墙、打印机等设备出厂证书往往只有 CN。用内部 CA 签发带 SAN 的证书替换掉。

### 4. 客户端升级了，服务器没变

服务器什么都没改，只是客户端用新版 Go 重新编译了。所以这个报错往往在升级 Docker、kubectl、Terraform 之后突然出现。

## 各环境修法

唯一的修法是用内部 CA 重签证书，把客户端用到的每个域名和 IP 都写进 SAN。

**自建 Docker 仓库（Harbor、registry）：** 国内很多团队的 Harbor 用的是几年前按教程生成的自签名证书，升级 Docker 后就报这个错。重新签发带 SAN 的仓库证书并重启 Harbor；客户端的 CA 放在 `/etc/docker/certs.d/<仓库地址>/ca.crt`，详见 [Docker x509 报错](https://ssl.sh/zh/errors/docker-x509-certificate-signed-by-unknown-authority/)。

**Kubernetes 准入 Webhook：** API Server 通过 Service 域名调用 Webhook，例如 `my-webhook.my-namespace.svc`，这个名字必须在证书 SAN 里。用 cert-manager 时写进 `Certificate` 的 `spec.dnsNames`。

**etcd 和 kubeadm 集群：** kubeadm 生成的证书本身带 SAN。照老教程手工生成的 etcd 证书可能没有，需要把节点主机名和 IP 写进 SAN 重新生成。

**阿里云 ACK、腾讯云 TKE 等托管集群：** 平台自动签发的组件证书都带 SAN，出问题的一般是自己部署的 Webhook、镜像仓库或中间件。逐个检查这些服务的证书，按上面的方法重新签发，并把新的 CA 同步到 Webhook 配置的 caBundle 里。

**Java 等其他客户端：** Java 对域名在某些情况下仍会回退到 CN，所以同一张证书可能 Java 正常、Go 报错。修好证书后两边都能用，另见 [No subject alternative names present](https://ssl.sh/zh/errors/java-no-subject-alternative-names-present/)。

## 不要这样做

下面 3 种做法都修不好证书，只会推迟问题或关掉校验。

- **为了保留回退行为锁死旧版 Go，或旧版 Docker、kubectl。** 这等于为了一张有问题的证书放弃所有安全更新。
- **`InsecureSkipVerify: true` 或 `--insecure-skip-tls-verify`。** 这会去掉全部校验，而不只是 CN 检查。
- **设置 `GODEBUG=x509ignoreCN=0` 就以为好了。** Go 1.17 及以后这个变量会被直接忽略，白白浪费排查时间。
