Skip to content
DevOps AI ToolKit
Newsletter
All guides
Docker with AI By James Joyner IV · · 9 min read

Docker Error Guide: 'x509: certificate signed by unknown authority' Registry TLS Failures

Fix Docker's x509 certificate signed by unknown authority error: install the registry CA in /etc/docker/certs.d, update the trust store, and restart dockerd.

  • #docker
  • #troubleshooting
  • #errors
  • #tls

Exact Error Message

Docker prints this when it cannot verify the TLS certificate a registry presents during a push or pull:

Error response from daemon: Get "https://myregistry:5000/v2/": x509: certificate signed by unknown authority

Related x509 variants that point at the same TLS-trust subsystem:

Error response from daemon: Get "https://myregistry:5000/v2/": x509: certificate has expired or is not yet valid
Error response from daemon: Get "https://10.0.0.5:5000/v2/": x509: cannot validate certificate for 10.0.0.5 because it doesn't contain any IP SANs
Error response from daemon: Get "https://registry.corp/v2/": tls: failed to verify certificate: x509: certificate signed by unknown authority

What It Means

When Docker connects to a registry over HTTPS, the daemon validates the registry’s certificate against a chain of trust: the certificate must be signed (directly or via intermediates) by a Certificate Authority the daemon trusts, it must be currently valid, and the hostname or IP you connected to must appear in the certificate’s Subject Alternative Names. x509: certificate signed by unknown authority means validation failed at the chain step — the signing CA is not in Docker’s trust set. certificate has expired fails at the validity step, and doesn't contain any IP SANs fails at the identity step.

The important detail is which trust store matters. The Docker daemon checks the system trust store and a Docker-specific per-registry directory, /etc/docker/certs.d/<registry:port>/. A self-signed or internal-CA registry that works in a browser (because you trusted the CA at the OS level) can still fail in Docker if the daemon was not reloaded or the per-registry CA was not placed.

It is worth being precise about what “trust” means here, because the error is often misread as “the certificate is bad”. The certificate may be perfectly valid; the daemon simply has no path from that certificate back to a root it already holds. TLS verification walks the chain from the leaf the registry presents, through any intermediate certificates, up to a self-signed root that must be present in a trust store. With a public registry that root ships in the OS bundle, so it just works. With an internal CA, that root lives only inside your organisation, and until you place it where the daemon looks, every pull is — correctly — refused. This is a security feature, not a glitch: trusting an unknown CA would let anyone who can intercept the connection impersonate your registry and serve tampered images.

Common Causes

  • Self-signed or private-CA registry not trusted by the daemon. The signing CA is absent from both the system store and /etc/docker/certs.d.
  • Missing ca.crt in /etc/docker/certs.d/<registry:port>/. Docker’s per-registry trust directory has no CA file for this registry.
  • Corporate MITM proxy CA not installed. A TLS-inspecting proxy re-signs the connection with a corporate CA the daemon does not trust.
  • Expired certificate on the registry (certificate has expired or is not yet valid).
  • SAN missing the host/IP you connected by — connecting to a bare IP when the cert only lists a DNS name (doesn't contain any IP SANs).
  • System trust store updated but daemon not restarted, so dockerd still uses the old bundle.

How to Reproduce the Error

Pull from a registry serving a self-signed certificate whose CA you have not trusted:

docker pull myregistry:5000/library/alpine:latest
Error response from daemon: Get "https://myregistry:5000/v2/": x509: certificate signed by unknown authority

You can confirm the same failure independently of Docker, which proves it is a trust problem and not a Docker bug:

curl -v https://myregistry:5000/v2/
* SSL certificate problem: unable to get local issuer certificate
curl: (60) SSL certificate problem: unable to get local issuer certificate

Diagnostic Commands

Inspect exactly what certificate the registry serves and who issued it:

openssl s_client -connect myregistry:5000 -showcerts </dev/null 2>/dev/null \
  | openssl x509 -noout -issuer -subject -dates -ext subjectAltName
issuer=CN = Corp Internal CA
subject=CN = myregistry
notAfter=Jun 20 00:00:00 2026 GMT
X509v3 Subject Alternative Name:
    DNS:myregistry

Check whether Docker’s per-registry CA directory exists and what insecure registries the daemon already allows:

ls -l /etc/docker/certs.d/myregistry:5000/
docker info --format '{{json .RegistryConfig}}' | python3 -m json.tool
docker info | grep -i -A3 'insecure registries'

Verify trust independently and read daemon logs after a reload:

curl -v https://myregistry:5000/v2/
journalctl -u docker --no-pager -n 40 | grep -i 'x509\|tls\|cert'

Step-by-Step Resolution

1. Obtain the registry’s CA certificate. Grab the issuing CA (the ca.crt, not the leaf) from whoever runs the registry, or extract it:

openssl s_client -connect myregistry:5000 -showcerts </dev/null 2>/dev/null \
  | openssl x509 -outform PEM > /tmp/ca.crt

2. Place it in Docker’s per-registry trust directory. The folder name must include the port:

sudo mkdir -p /etc/docker/certs.d/myregistry:5000
sudo cp /tmp/ca.crt /etc/docker/certs.d/myregistry:5000/ca.crt

3. Also add it to the system trust store (helps curl, the daemon, and other tooling):

sudo cp /tmp/ca.crt /usr/local/share/ca-certificates/myregistry.crt
sudo update-ca-certificates            # Debian/Ubuntu
# RHEL/Fedora: cp to /etc/pki/ca-trust/source/anchors/ && sudo update-ca-trust

4. Restart the daemon so it reloads trust:

sudo systemctl restart docker
docker pull myregistry:5000/library/alpine:latest

5. For expired certs or missing SANs, fix the registry side: reissue the certificate with a current validity window and a SAN list that includes every hostname and IP clients use. Connect by a name in the SAN rather than a bare IP.

6. As a last resort only, mark the registry insecure to skip TLS verification:

// /etc/docker/daemon.json
{ "insecure-registries": ["myregistry:5000"] }

This disables certificate validation for that registry and exposes pulls to interception. Use it only on trusted networks for testing — prefer installing the CA in every real environment. Note that insecure-registries is also what lets Docker fall back to plain HTTP and skip the TLS check entirely, which is exactly why it is dangerous: an attacker on the path can substitute images and you will pull them without complaint. Treat any standing need for this flag as an unresolved CA-distribution problem.

A note on the :port in the directory name: Docker matches the per-registry certs.d folder against the registry reference exactly as written, including the port. Pulls from myregistry:5000/... look in /etc/docker/certs.d/myregistry:5000/, while a registry served on the default 443 would use /etc/docker/certs.d/myregistry/ with no port. Getting this wrong is the single most common reason a correctly-obtained ca.crt still does not take effect.

How to Prevent the Issue

  • Distribute the internal CA to every node’s /etc/docker/certs.d/<registry:port>/ca.crt and system trust store as part of host provisioning, so new nodes trust the registry on first boot.
  • Issue registry certificates with SANs covering all hostnames and IPs clients will use, and prefer DNS names over bare IPs.
  • Monitor certificate expiry and renew well ahead of notAfter to avoid certificate has expired outages.
  • If a TLS-inspecting corporate proxy is in play, ship the corporate CA to all Docker hosts and reload the daemon.
  • Avoid insecure-registries in production; treat any need for it as a missing-CA bug to fix. See more in Docker guides.
Free download · 368-page PDF

Download the Free 500-Prompt DevOps AI Toolkit

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.