Fix x509: certificate signed by unknown authority in Docker and Kubernetes

intermediate๐Ÿ”’ SSL/TLS2026-05-14| Docker 20+, Kubernetes 1.20+, Linux (Ubuntu/Debian/RHEL/Alpine), Go-based tools

Error Message

x509: certificate signed by unknown authority
#docker#kubernetes#x509#tls#private-ca#self-signed

The Error

You're pulling an image from a private registry, making an API call inside a pod, or configuring a webhook โ€” and you hit this:

x509: certificate signed by unknown authority

Sometimes it comes with more context:

Error response from daemon: Get "https://registry.internal/v2/": x509: certificate signed by unknown authority

Post "https://webhook.internal/validate": x509: certificate signed by unknown authority

The underlying problem is always the same: Go's TLS stack (Docker, kubectl, kubelet โ€” all written in Go) can't verify the certificate chain. The CA that signed the certificate simply isn't in the system trust store. That's it.

Diagnosis

Before touching any config, confirm what certificate the server is actually serving:

# Check what cert the server is serving
openssl s_client -connect registry.internal:443 -showcerts </dev/null 2>&1 | openssl x509 -noout -issuer -subject

# Or with curl to see the full chain
curl -v https://registry.internal/v2/ 2>&1 | grep -A5 "SSL connection"

See an issuer like CN=My Corp CA or CN=registry.internal? That's a private or self-signed cert. No public CA signed it, so you need to add that CA to the trust store yourself.

Fix 1: Add CA cert to the Docker daemon (private registry)

Pulling from an internal registry with a self-signed or private-CA cert? This fix handles it. Docker has its own per-registry certificate directory, separate from the system store.

# Create the directory for the registry
sudo mkdir -p /etc/docker/certs.d/registry.internal:5000

# Copy your CA cert (get it from your infra team or extract from the server)
sudo cp ca.crt /etc/docker/certs.d/registry.internal:5000/ca.crt

# Restart Docker
sudo systemctl restart docker

The directory name must match exactly: /etc/docker/certs.d/<host>:<port>/ca.crt. On port 443, you can drop the port entirely.

Verify the fix right away:

docker pull registry.internal:5000/myimage:latest

Fix 2: Add CA to the system trust store (Linux)

kubelet, kubectl plugins, custom operators โ€” any Go binary on the host reads the system trust store, not Docker's cert directory. That's where this fix applies.

# Ubuntu/Debian
sudo cp ca.crt /usr/local/share/ca-certificates/my-corp-ca.crt
sudo update-ca-certificates

# RHEL/CentOS/Fedora
sudo cp ca.crt /etc/pki/ca-trust/source/anchors/my-corp-ca.crt
sudo update-ca-trust extract

# Verify it was added
certutil -L -d /etc/pki/nssdb | grep "My Corp"  # RHEL
# or just test with curl
curl https://registry.internal/v2/

Fix 3: Kubernetes โ€” add CA to containerd (node-level)

Pods can't pull images, or kubelet is logging this error? The fix lives on each node, in containerd's config โ€” not in Kubernetes itself.

# On each node, edit containerd config
sudo mkdir -p /etc/containerd/certs.d/registry.internal:5000

cat <<EOF | sudo tee /etc/containerd/certs.d/registry.internal:5000/hosts.toml
[host."https://registry.internal:5000"]
  ca = "/etc/ssl/certs/my-corp-ca.crt"
EOF

# Copy your CA cert
sudo cp ca.crt /etc/ssl/certs/my-corp-ca.crt

# Restart containerd
sudo systemctl restart containerd

On managed clusters (EKS, GKE, AKS), you can't SSH into nodes freely. Use a DaemonSet or wire the CA into the node pool's bootstrap script instead.

Fix 4: Kubernetes โ€” inject CA via ConfigMap into pods

The error happens inside a running pod โ€” your app is calling an internal HTTPS service and the Go HTTP client rejects the cert. Mount the CA as a volume and point Go to it via an environment variable.

apiVersion: v1
kind: ConfigMap
metadata:
  name: corp-ca-bundle
data:
  ca.crt: |
    -----BEGIN CERTIFICATE-----
    MIIBxTCCAW+gAwIBAgIJA...
    -----END CERTIFICATE-----
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  template:
    spec:
      volumes:
        - name: ca-bundle
          configMap:
            name: corp-ca-bundle
      containers:
        - name: myapp
          image: myapp:latest
          volumeMounts:
            - name: ca-bundle
              mountPath: /etc/ssl/certs/corp-ca.crt
              subPath: ca.crt
          env:
            - name: SSL_CERT_FILE
              value: /etc/ssl/certs/corp-ca.crt

SSL_CERT_FILE is a Go-specific env var that overrides the default CA bundle path. Another approach: mount the cert into the default bundle directory and run update-ca-certificates in an init container before your app starts.

Fix 5: Kubernetes admission webhooks

Seeing this error in kube-apiserver logs when a webhook fires? The caBundle field in your MutatingWebhookConfiguration or ValidatingWebhookConfiguration is either wrong or missing entirely. The API server uses that bundle to verify the webhook server's certificate.

# Check what CA bundle the webhook is currently using
kubectl get mutatingwebhookconfiguration my-webhook -o jsonpath='{.webhooks[0].clientConfig.caBundle}' | base64 -d | openssl x509 -noout -text

# Patch in the correct CA
CA_BUNDLE=$(cat ca.crt | base64 -w0)
kubectl patch mutatingwebhookconfiguration my-webhook \
  --type='json' \
  -p="[{'op': 'replace', 'path': '/webhooks/0/clientConfig/caBundle', 'value': '${CA_BUNDLE}'}]"

Running cert-manager? Add the cert-manager.io/inject-ca-from annotation and it handles rotation automatically โ€” no manual patching needed.

Getting the CA certificate

No CA cert file on hand? Pull it straight from the server. The safest approach is saving the full chain and using that:

# Save the full chain and use it as the CA bundle
openssl s_client -connect registry.internal:443 -showcerts </dev/null 2>&1 \
  | sed -n '/BEGIN CERTIFICATE/,/END CERTIFICATE/p' > chain.crt

The chain extraction one-liner you'll find online (with nested openssl calls and awk offsets) is fragile and breaks on chains longer than two certs. Don't use it in scripts. Ask your infra team for the CA cert directly โ€” it's the right call.

Verification

# Docker registry
docker pull registry.internal:5000/myimage:latest

# curl with system trust store
curl https://registry.internal/v2/

# From inside a pod
kubectl run -it --rm debug --image=alpine --restart=Never -- wget -O- https://internal-service.default.svc/health

# Go binary respects SSL_CERT_FILE
SSL_CERT_FILE=/path/to/ca.crt your-go-binary

Quick reference

  • Docker pull fails โ†’ /etc/docker/certs.d/<registry>/ca.crt
  • kubelet/containerd pull fails โ†’ containerd hosts.toml on the node
  • App inside pod fails โ†’ mount CA via ConfigMap + SSL_CERT_FILE
  • Webhook validation fails โ†’ fix caBundle in webhook config
  • CLI tools fail โ†’ add CA to system trust store + update-ca-certificates

Related Error Notes