KhueApps
Home/DevOps/Fix Docker x509 error: certificate signed by unknown authority

Fix Docker x509 error: certificate signed by unknown authority

Last updated: October 07, 2025

Overview

Docker pull or docker build failing with:

x509: certificate signed by unknown authority

means a TLS certificate in the chain can’t be verified against the trust store used by the Docker Engine or the image you’re building from. Common causes:

  • Private registry with a self-signed or privately issued CA
  • TLS inspection proxy (corporate MITM) re-signing traffic
  • Incomplete certificate chain on the registry
  • Hostname mismatch (CN/SAN) or clock skew
  • Minimal base images missing CA bundles

This guide shows how to fix it for pulls and builds.

Quickstart: trust your registry’s CA on the Docker host

Use this when docker pull fails from a private registry.

  1. Obtain the registry’s CA certificate as a PEM file (base64 text) named ca.crt. Do not use the server certificate; use the issuing CA. If intermediates are used, include the full chain.

  2. Place ca.crt where the Docker Engine looks for per-registry CAs, then restart Docker.

  • Linux (Docker Engine):
    • Path: /etc/docker/certs.d/<registry-host>:<port>/ca.crt
    • Example:
sudo mkdir -p /etc/docker/certs.d/registry.example.com:443
sudo cp ca.crt /etc/docker/certs.d/registry.example.com:443/ca.crt
sudo systemctl restart docker
  • Docker Desktop (macOS/Linux/Windows):
    • Path: ~/.docker/certs.d/<registry-host>:<port>/ca.crt (Windows: %USERPROFILE%.docker\certs.d<registry-host>_<port>\ca.crt if your shell doesn’t like colons)
    • Example (macOS/Linux):
mkdir -p ~/.docker/certs.d/registry.example.com:443
cp ca.crt ~/.docker/certs.d/registry.example.com:443/ca.crt
# Restart Docker Desktop from the UI or CLI
  1. Test:
docker login registry.example.com
docker pull registry.example.com/my-team/app:latest

If this works, your host trust is fixed.

Minimal working example: build with a private CA inside the image

If the Dockerfile needs to fetch over HTTPS (apk/apt, curl, git) through a proxy or to a service signed by a private CA, add that CA to the image trust store.

Dockerfile (Alpine example):

# syntax=docker/dockerfile:1
FROM alpine:3.20
# Copy corporate CA into the distro trust store, then update.
COPY corp-ca.crt /usr/local/share/ca-certificates/corp-ca.crt
RUN apk add --no-cache ca-certificates \
    && update-ca-certificates \
    && wget -qO- https://internal.example.com/health || true
CMD ["sh", "-c", "ls -l /etc/ssl/certs | wc -l"]

Build and run:

# Put your PEM CA in the build context as corp-ca.crt
DOCKER_BUILDKIT=1 docker build -t ca-demo .
docker run --rm ca-demo

Tip (BuildKit secret, avoids baking CA into layers during build steps):

# syntax=docker/dockerfile:1.6
FROM alpine:3.20
RUN --mount=type=secret,id=corpca,dst=/usr/local/share/ca-certificates/corp-ca.crt \
    apk add --no-cache ca-certificates && update-ca-certificates

Build with secret:

DOCKER_BUILDKIT=1 docker build \
  --secret id=corpca,src=corp-ca.crt \
  -t ca-demo-secret .

Note: If your application needs the CA at runtime, ensure it remains in the final image or set SSL_CERT_FILE/SSL_CERT_DIR accordingly.

Step-by-step troubleshooting checklist

  1. Verify the hostname and SAN
  • The certificate must include the registry’s host (and port if SNI is used). Test with:
openssl s_client -connect registry.example.com:443 -servername registry.example.com -showcerts </dev/null 2>/dev/null | openssl x509 -noout -subject -issuer -dates -ext subjectAltName
  • If the SANs don’t include the hostname, fix the certificate.
  1. Verify the chain is complete
  • The server must present intermediates up to a trusted root. Validate:
openssl s_client -connect registry.example.com:443 -showcerts </dev/null
  • If intermediates are missing, configure the registry with a full chain (often fullchain.pem), then restart it.
  1. Check clock skew
date
  • If the system time is off, certificates may appear not yet valid or expired. Sync NTP.
  1. Corporate proxy/TLS inspection
  • If a proxy re-signs TLS, install the proxy’s CA both on the Docker host and inside images that need outbound HTTPS.
  • During builds, set proxies and CA:
ARG https_proxy
ENV HTTPS_PROXY=${https_proxy}
ENV SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt
  1. Minimal base images lack CA bundles
  • Debian/Ubuntu images: install ca-certificates
  • Alpine: apk add ca-certificates; update-ca-certificates
  • Distroless/scratch: copy a CA bundle from a builder stage.
  1. Test with curl/wget inside a debug container
docker run --rm -it alpine:3.20 sh -lc \
  "apk add --no-cache ca-certificates curl >/dev/null; update-ca-certificates; curl -vI https://registry.example.com/v2/" 

Trust store locations and commands

Distro/baseTrust dir/fileUpdate command
Debian/Ubuntu/usr/local/share/ca-certificates/*.crtupdate-ca-certificates
RHEL/CentOS/Fedora/etc/pki/ca-trust/source/anchors/*.crtupdate-ca-trust extract
Alpine/usr/local/share/ca-certificates/*.crtupdate-ca-certificates
DistrolessProvide SSL_CERT_FILE to a copied bundlen/a

Docker build specifics

  • Use BuildKit for better caching and secrets management: set DOCKER_BUILDKIT=1.
  • If only apt/apk needs the CA during build, mount it as a secret to avoid bloating the image. If the app needs it at runtime, COPY it in the final stage.
  • For git clones over HTTPS with a private CA, either add the CA to the system trust or configure Git:
RUN git config --system http.sslcainfo /etc/ssl/certs/ca-certificates.crt

Registry and daemon configuration

Preferred: install the CA as shown in Quickstart.

Temporary (not recommended for production): mark the registry as insecure.

daemon.json example (Linux):

{
  "insecure-registries": ["registry.example.com:5000"]
}

Apply and restart:

sudo mkdir -p /etc/docker
echo '{"insecure-registries":["registry.example.com:5000"]}' | sudo tee /etc/docker/daemon.json
sudo systemctl restart docker

Use only when you cannot deploy a proper certificate.

Pitfalls to avoid

  • Wrong directory name: include the port (e.g., registry.example.com:443)
  • Wrong filename: must be ca.crt for Docker Engine trust
  • Using the server cert instead of the issuing CA (use the CA or full chain)
  • Forgetting to restart Docker after adding host CAs
  • Fixing trust in the host but not in the image that needs outbound HTTPS
  • Missing SANs in the certificate (CN alone is insufficient in modern TLS)
  • Overriding SSL_CERT_FILE to a path that doesn’t exist

Performance notes

  • Installing ca-certificates adds a small image size overhead (a few MB). Keep it in a single, cached layer to avoid rebuild costs.
  • Use multi-stage builds: install tools and CA in a builder stage; copy only needed bundles into the final image.
  • Enable BuildKit for parallelism and better cache reuse during build steps that fetch over HTTPS.
  • Avoid disabling TLS verification; it can mask real issues and reduce security with negligible performance benefit.

FAQ

Q: curl works on my host, but docker pull fails. Why? A: Docker Engine has its own per-registry trust at /etc/docker/certs.d; the host OS trust store doesn’t automatically apply to the daemon.

Q: Do I need the full chain or just the root CA? A: Provide the full chain if your registry presents intermediates. Many clients require the chain to validate properly.

Q: Can I fix this with an environment variable? A: For builds, SSL_CERT_FILE or SSL_CERT_DIR can help inside the image. For docker pull, you must configure the daemon trust store.

Q: Is using insecure-registries safe? A: It disables TLS verification and is not recommended. Prefer installing the proper CA or using a valid certificate.

Series: Docker

DevOps