平台和 DevOps
x509: certificate signed by unknown authority(Docker)怎么修
Error response from daemon: Get "https://registry.example.com/v2/": x509: certificate signed by unknown authoritydocker pull、docker push 或 docker login 报 x509: certificate signed by unknown authority,意思是 Docker 守护进程没法用它信任的根证书验证镜像仓库的证书。 典型情况是 Harbor、Nexus、GitLab、registry:2 等私有仓库用了内部 CA 或自签名证书,或者公司代理重新签发了连接。正确做法是通过 certs.d 或系统信任库把这张 CA 交给守护进程,而不是把仓库标记为不安全。
较新的 Docker 会显示 tls: failed to verify certificate: x509: certificate signed by unknown authority,是同一个错误。
快速修复
最快的修法是把仓库的 CA 证书放进 /etc/docker/certs.d/<仓库地址>/ca.crt,只对这个仓库生效。在 Linux 的 Docker 主机上执行:
sudo mkdir -p /etc/docker/certs.d/registry.example.com
sudo cp registry-ca.crt /etc/docker/certs.d/registry.example.com/ca.crt
docker pull registry.example.com/team/app:latest
仓库带端口时目录名要带上端口:/etc/docker/certs.d/registry.example.com:5000/。这个目录下以 .crt 结尾的文件都会被当作 CA 根证书。如果仍然失败,执行 sudo systemctl restart docker 重启守护进程。
不确定仓库证书是谁签的,先看签发者:
openssl s_client -connect registry.example.com:443 -servername registry.example.com -showcerts </dev/null | grep -E 's:|i:'
原因
这个报错有 4 种原因,最常见的是 Harbor 等私有仓库用了内部 CA 或自签名证书。
1. 仓库用了内部 CA 或自签名证书
国内很多团队自建 Harbor 时按教程生成了自签名 CA。
- 确认: 签发者是公司 CA,或者证书是自签名的(
s:和i:相同)。 - 修复: 按上面的方法通过
certs.d分发 CA(证书本身是自签名时才分发服务器证书),或者在系统层面信任它。Harbor 安装目录里通常保留了当初生成的ca.crt。
2. 公司代理解密 HTTPS
在办公网络里连 Docker Hub 等公网仓库也报错,因为代理出示的是它自己签发的证书。
- 确认:
docker pull hello-world同样失败,签发者是代理设备。 - 修复: 把代理根证书加入宿主机的系统信任库,然后重启 Docker:
sudo cp proxy-root.crt /usr/local/share/ca-certificates/
sudo update-ca-certificates
sudo systemctl restart docker
在 Linux 上 Docker 会把 certs.d 的根证书和系统根证书合并使用,这样所有仓库都能生效。CentOS/RHEL 系统改用 /etc/pki/ca-trust/source/anchors/ 和 update-ca-trust。
3. 仓库没发中间证书
仓库前面的 Nginx 只配置了站点证书,没发中间证书。浏览器能打开,Docker(Go 编写,不会自动补中间证书)却报错。
- 确认:
openssl s_client -showcerts只列出一张证书。 - 修复: 在仓库上配置完整证书链,可用证书链补全工具检查。
4. 证书没有 SAN
如果报错是 relies on legacy Common Name field,说明仓库证书没有 SAN,见旧式 Common Name 报错。
各环境修法
各环境的原则相同:把 CA 交给实际发起连接的进程。Docker 守护进程、containerd、Podman 和镜像构建各读各的证书目录。
镜像加速器: daemon.json 里的 registry-mirrors 指向的加速地址如果用的是公网证书,一般不会报这个错;如果是公司内部搭的加速缓存,同样要把它的 CA 放进 certs.d。阿里云 ACR、腾讯云 TCR 等云厂商仓库使用公网证书,报错时优先怀疑公司代理。
Rootless Docker: 证书目录是 $XDG_CONFIG_HOME/docker/certs.d(默认 ~/.config/docker/certs.d),不是 /etc/docker/certs.d。
Docker Desktop(macOS/Windows): Docker Desktop 可以使用宿主机证书库里的 CA。把根证书加入 macOS“系统”钥匙串或 Windows“受信任的根证书颁发机构”,再重启 Docker Desktop。针对某个仓库的客户端证书放在宿主机的 ~/.docker/certs.d。
Windows Server 上的 Windows 容器: 放到 %PROGRAMDATA%\docker\certs.d\registry.example.com\ca.crt,仓库有端口时去掉冒号。
用 containerd 的 Kubernetes 节点: kubelet 通过 containerd 拉镜像,containerd 不读 Docker 的 certs.d。要在 containerd 自己的仓库主机配置里(config_path 指定目录下的 hosts.toml)设置 CA,然后重启 containerd。
Podman: 目录结构相同,路径是 /etc/containers/certs.d/<仓库地址>/ca.crt。
docker build 内部: 每个镜像有自己的信任库,遇到代理时在 Dockerfile 里加入根证书:
COPY proxy-root.crt /usr/local/share/ca-certificates/
RUN update-ca-certificates
不要这样做
这 3 种做法都会让镜像来源失去校验,伪造的仓库可以借此投放恶意镜像。
- 在
daemon.json里写"insecure-registries": ["registry.example.com"]。 这会关闭该仓库的证书校验,伪造的仓库可以塞给你一个带后门的镜像,而你会带着生产环境的密钥去运行它。 - 把服务器证书而不是 CA 复制进
certs.d。 仓库证书一续期就又会报错。 - 在内网关闭仓库的 TLS、改用明文 HTTP。 内网恰恰是篡改镜像最容易的地方。
常见问题
我已经在自己账号下加了 CA,为什么 docker pull 还是报错?
拉取和登录是 Docker 守护进程做的,它作为系统服务运行,只读系统信任库和 /etc/docker/certs.d,不读你家目录里的文件,也不读你在 shell 里设置的 SSL_CERT_FILE 等环境变量。
仓库带端口时目录名怎么写?
和镜像名里的写法完全一致,带上端口,例如 /etc/docker/certs.d/registry.example.com:5000/ca.crt。Windows 文件名不能有冒号,Docker 文档说明要去掉冒号(registry.example.com5000)。
报错出现在 docker build 的 RUN 步骤里,修法一样吗?
不一样。RUN apt-get、RUN pip install 在镜像内部执行,用的是镜像自己的信任库。要在 Dockerfile 里先把根证书复制进镜像并执行 update-ca-certificates,再执行下载步骤。
什么时候可以用 insecure-registries?
只适合隔离网络里的一次性实验仓库。它会关闭该仓库的 TLS 校验(还允许明文 HTTP),网络路径上的任何机器都能给你换一个镜像。