# x509: certificate signed by unknown authority（Go）怎么解决

> Go 程序报 x509: certificate signed by unknown authority，说明服务器证书链找不到受信任的根。应把根证书加入系统或 RootCAs，而不是跳过校验。

Source: https://ssl.sh/zh/errors/x509-certificate-signed-by-unknown-authority/
Last updated: 2026-11-10

**`x509: certificate signed by unknown authority` 是 Go 在告诉你：服务器的证书链最终没有落到程序信任的任何根证书上。** 常见原因有三类：服务器缺中间证书；机器或容器里没有 CA 证书包；服务器用了私有 CA，或者流量经过了解密 HTTPS 的代理，而对应的根证书没装。

## 快速修复

服务器缺中间证书就修服务器，否则把缺失的根证书加进系统证书包或 RootCAs。先看服务器发了什么、由谁签发：

```bash
openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null | grep -E 'depth|s:|i:|Verify return'
```

- **只有一张站点证书，`Verify return code: 21`：** 服务器证书链不完整，去服务器上修，见[缺少中间证书](https://ssl.sh/zh/errors/missing-intermediate-certificate/)。
- **证书链完整、根证书是公网 CA，程序仍然报错：** 客户端没有 CA 证书包或者太旧，安装 `ca-certificates`。
- **证书链最终是私有根或代理根：** 在客户端信任这张根证书：

```bash
export SSL_CERT_FILE=/etc/ssl/certs/company-bundle.pem   # Linux，只对当前进程生效
```

更推荐把根证书加入系统证书库（命令见下文），这样所有 Go 工具都能用上。

## 原因

这个报错有 4 种原因：容器里没有 CA 证书包、服务器没发中间证书、私有 CA 或代理根证书没装、本机证书包太旧。

### 1. 容器里没有 CA 证书包

`scratch`、部分 `distroless` 和精简镜像不带 CA 证书，Go 程序的所有 HTTPS 请求都会报这个错。调用微信、支付宝、阿里云 OpenAPI 时最常遇到。

- **确认：** 进入容器执行 `ls /etc/ssl/certs/`，目录为空或不存在。
- **修复（多阶段构建）：**

```dockerfile
FROM golang:1 AS build
WORKDIR /src
COPY . .
RUN CGO_ENABLED=0 go build -o /app .

FROM scratch
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=build /app /app
ENTRYPOINT ["/app"]
```

也可以在代码里 `import _ "golang.org/x/crypto/x509roots/fallback"`，把一份基于 Mozilla 的根证书编进二进制，只在系统没有根证书时启用。

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

Go 不会自动下载缺失的中间证书。只发站点证书的服务器在 Chrome 里正常，在 Go 里报错。

- **修复：** 在服务器上配置完整证书链，可用[证书链补全工具](https://ssl.sh/zh/tools/chain-builder/)检查。

### 3. 私有 CA、自签名证书或代理根证书

自建的 Harbor 镜像仓库、Vault、Consul、Kubernetes API Server 以及公司代理，用的根证书都不在公网证书包里。

- **代码里修复**，保留系统根证书，再追加你的根证书：

```go
pool, err := x509.SystemCertPool()
if err != nil {
    pool = x509.NewCertPool()
}
pem, err := os.ReadFile("/etc/myapp/company-root.pem")
if err != nil {
    log.Fatal(err)
}
if !pool.AppendCertsFromPEM(pem) {
    log.Fatal("no certificates parsed from company-root.pem")
}
client := &http.Client{Transport: &http.Transport{
    TLSClientConfig: &tls.Config{RootCAs: pool},
}}
```

### 4. CA 换了新根，本机证书包太旧

CA 切换到新根证书后，如果你的证书包早于这次切换，链到新根的证书就验证失败。更新 `ca-certificates` 包即可。

## 各环境修法

Go 在 Linux 上读系统证书包，所以修法是把根证书加进系统证书包，或用 SSL_CERT_FILE 指定证书包；镜像里先装 ca-certificates。

**Debian / Ubuntu：**

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

**CentOS / RHEL / Rocky / Alibaba Cloud Linux：** 复制到 `/etc/pki/ca-trust/source/anchors/`，执行 `sudo update-ca-trust`。

**Alpine：** `apk add --no-cache ca-certificates`，再把根证书放到 `/usr/local/share/ca-certificates/` 并执行 `update-ca-certificates`。国内构建慢时可以先把 apk 源换成阿里云、清华等镜像。

**macOS 和 Windows：** Go 使用系统验证接口，把根证书加入“系统”钥匙串并设为“始终信任”，或导入 Windows 的“受信任的根证书颁发机构”。

**具体工具：** Docker 拉镜像时验证证书的是守护进程而不是命令行，kubectl 用的是 kubeconfig 里的 CA，分别见 [Docker x509 报错](https://ssl.sh/zh/errors/docker-x509-certificate-signed-by-unknown-authority/)和 [kubectl x509 报错](https://ssl.sh/zh/errors/kubectl-x509-certificate-signed-by-unknown-authority/)。

**Go 模块下载：** 在解密 HTTPS 的公司代理后面执行 `go mod download` 也会这样报错。在系统层面信任代理根证书，或者使用 goproxy.cn 等国内模块代理（它们用公网证书），不要设置 `GOINSECURE`。

## 不要这样做

跳过校验能消除报错，但证书链和域名校验也一起关掉了。

- **`InsecureSkipVerify: true`。** 同时关闭证书链和域名校验，gosec 等静态检查工具会专门报出来。
- **`RootCAs` 里只放私有根证书**，而程序还要访问公网服务。应从 `x509.SystemCertPool()` 开始再追加。
- **用 `GOINSECURE` 绕过模块下载报错。** 它会对匹配的模块路径跳过 HTTPS 证书校验，下载内容被篡改时只能指望校验和数据库发现。
- **从网盘、论坛下载来路不明的 `ca-certificates.crt` 放进镜像。** 用发行版的软件包或 Go 官方的 fallback 包。
