Platforms and DevOps
Docker x509: certificate signed by unknown authority: fix
Error response from daemon: Get "https://registry.example.com/v2/": x509: certificate signed by unknown authorityx509: certificate signed by unknown authority from docker pull, docker push or docker login means the Docker daemon could not verify the registry's certificate against any root it trusts. Usually the registry (Harbor, Nexus, GitLab, registry:2) uses an internal or self-signed CA, or a proxy re-signs the connection. Give the daemon that CA through certs.d.
Newer Docker versions print tls: failed to verify certificate: x509: certificate signed by unknown authority; it is the same error.
Quick fix
On a Linux Docker host, trust the registry's CA for that registry only:
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
If the registry uses a port, the directory name includes it: /etc/docker/certs.d/registry.example.com:5000/. Files ending in .crt in that directory are treated as CA roots. If the pull still fails, restart the daemon with sudo systemctl restart docker.
To see which CA signed the registry certificate, run:
openssl s_client -connect registry.example.com:443 -servername registry.example.com -showcerts </dev/null | grep -E 's:|i:'
Causes
1. Registry uses an internal or self-signed CA
An internal or self-signed CA is the default for most self-hosted registries.
- Confirm: the issuer is your company CA or the certificate is self-signed (
s:equalsi:). - Fix: distribute the CA (not the server certificate, unless it is self-signed) via
certs.das above, or system-wide.
2. Corporate TLS-inspecting proxy
Pulls from Docker Hub or other public registries fail on office networks because the proxy presents its own certificate.
- Confirm:
docker pull hello-worldfails too; the issuer is the proxy. - Fix: add the proxy root to the host's system trust store and restart Docker:
sudo cp proxy-root.crt /usr/local/share/ca-certificates/
sudo update-ca-certificates
sudo systemctl restart docker
On Linux, Docker merges certs.d roots with the system roots, so this covers every registry.
3. Registry missing its intermediate certificate
A registry behind Nginx that sends only the leaf certificate fails in Docker (written in Go, which does not fetch intermediates) while browsers work. See missing intermediate certificate; the chain builder produces the full chain file.
- Confirm:
openssl s_client -showcertslists only one certificate. - Fix: serve the full chain on the registry; check with the chain builder.
4. Certificate without SANs
If the message instead says relies on legacy Common Name field, the registry certificate lacks SANs. See legacy Common Name.
Fixes by environment
Every runtime needs the registry's CA; only the place where it reads that CA differs.
Rootless Docker: the certificate root is $XDG_CONFIG_HOME/docker/certs.d (by default ~/.config/docker/certs.d), not /etc/docker/certs.d.
Docker Desktop (macOS/Windows): Docker Desktop can use CAs from the host's certificate store. Add the root to the macOS System keychain or the Windows "Trusted Root Certification Authorities" store, then restart Docker Desktop. Per-registry client certificates go in ~/.docker/certs.d on the host.
Windows containers on Windows Server: use %PROGRAMDATA%\docker\certs.d\registry.example.com\ca.crt; drop the colon if the registry has a port.
Kubernetes nodes with containerd: the kubelet pulls through containerd, which does not read Docker's certs.d. Configure the registry CA in containerd's own registry host configuration (hosts.toml under the directory set by config_path), then restart containerd.
Podman: uses /etc/containers/certs.d/<registry>/ca.crt with the same layout.
Inside docker build: each image has its own trust store; for proxies, add the root in the Dockerfile:
COPY proxy-root.crt /usr/local/share/ca-certificates/
RUN update-ca-certificates
Don't do this
"insecure-registries": ["registry.example.com"]indaemon.json. It turns off certificate verification for that registry, so a spoofed registry can hand you a backdoored image that you then run with your production secrets.- Copying the server certificate into
certs.dinstead of the CA. It breaks again as soon as the registry certificate is renewed. - Disabling TLS on the registry and serving plain HTTP inside the company network. Internal networks are where image tampering is easiest.
FAQ
I added the CA to my user account. Why does docker pull still fail?
Pulls and logins are performed by the Docker daemon, which runs as a system service. It uses the system trust store and /etc/docker/certs.d, not files in your home directory or environment variables like SSL_CERT_FILE set in your shell.
What directory name do I use if the registry runs on a port?
Include the port exactly as in the image name, for example /etc/docker/certs.d/registry.example.com:5000/ca.crt. On Windows hosts the colon is not allowed in file names, so Docker's documentation says to drop it (registry.example.com5000).
The error appears during docker build, in a RUN step. Is that the same fix?
No. A RUN apt-get or RUN pip install step runs inside the image, which has its own trust store. Copy the root into the image and run update-ca-certificates in the Dockerfile before the step that downloads.
Is insecure-registries ever acceptable?
Only for a throwaway lab registry on an isolated network. It disables TLS verification (and allows plain HTTP) for that registry, so any machine on the path can serve you a different image.