The Error
You run docker pull registry.internal:5000/myapp:latest and get hit with:
Error response from daemon: Get "https://registry.internal:5000/v2/": x509: certificate signed by unknown authority
Docker verifies TLS certificates against its trusted CA store before accepting any connection. Your internal registry is using a self-signed cert โ or one signed by a corporate CA Docker has never heard of โ so Docker refuses to talk to it.
Root Cause
Docker's TLS verification is strict. No click-through warnings like a browser. If the registry cert falls into any of these buckets, Docker rejects it outright:
- Self-signed certificate
- Signed by an internal/corporate CA that's not in the system trust store
- Missing intermediate CA certificates in the chain
The fix depends on whether you want to trust the cert properly or just get unblocked fast in a dev environment.
Fix 1: Add the Registry CA Certificate to Docker (Recommended)
This is the right approach for production. You're telling Docker to trust your specific registry's CA โ not throwing security out the window.
Step 1: Get the CA certificate from your registry
# If you have shell access to the registry server:
scp user@registry.internal:/etc/docker/certs/ca.crt /tmp/registry-ca.crt
# Or pull it directly over the wire with openssl:
openssl s_client -connect registry.internal:5000 -showcerts < /dev/null 2>/dev/null \
| openssl x509 -outform PEM > /tmp/registry-ca.crt
Step 2: Install the cert for Docker
# Create the per-registry certs directory
sudo mkdir -p /etc/docker/certs.d/registry.internal:5000
# Drop the CA cert in place
sudo cp /tmp/registry-ca.crt /etc/docker/certs.d/registry.internal:5000/ca.crt
No Docker restart required. Docker scans /etc/docker/certs.d/ on each pull โ the cert takes effect immediately.
For macOS/Windows Docker Desktop
Add the CA to the system keychain and Docker Desktop will pick it up:
# macOS
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain /tmp/registry-ca.crt
# Then restart Docker Desktop
On Windows, import the cert into the Windows Certificate Store under "Trusted Root Certification Authorities", then restart Docker Desktop.
Fix 2: Add CA to System Trust Store (Linux)
Want the cert trusted system-wide โ not just by Docker? Install it at the OS level instead.
# Ubuntu/Debian
sudo cp /tmp/registry-ca.crt /usr/local/share/ca-certificates/registry-internal.crt
sudo update-ca-certificates
# RHEL/CentOS/Fedora
sudo cp /tmp/registry-ca.crt /etc/pki/ca-trust/source/anchors/registry-internal.crt
sudo update-ca-trust
Then restart Docker so it picks up the updated system trust store:
sudo systemctl restart docker
Fix 3: Mark Registry as Insecure (Dev/Test Only)
Just need it working in five minutes on a local dev box? Tell Docker to skip TLS verification for that specific registry.
Edit (or create) /etc/docker/daemon.json:
{
"insecure-registries": ["registry.internal:5000"]
}
Restart Docker:
sudo systemctl restart docker
Then retry:
docker pull registry.internal:5000/myapp:latest
Warning: This disables TLS verification for that registry entirely. Anyone who can intercept your network traffic can serve you malicious images. Never use this in production.
Fix 4: Fix the Registry Certificate Itself
If you control the registry, fixing the cert once is cleaner than patching every client machine. Generate a proper self-signed cert with a Subject Alternative Name (SAN) โ modern Docker and browsers require it:
# Generate CA key and cert
openssl genrsa -out ca.key 4096
openssl req -new -x509 -days 1826 -key ca.key -out ca.crt \
-subj "/C=US/O=Internal/CN=Internal CA"
# Generate registry key and CSR
openssl genrsa -out registry.key 4096
openssl req -new -key registry.key -out registry.csr \
-subj "/CN=registry.internal"
# Sign with SAN extension
cat > san.ext <<EOF
[SAN]
subjectAltName=DNS:registry.internal,IP:192.168.1.100
EOF
openssl x509 -req -days 730 -in registry.csr \
-CA ca.crt -CAkey ca.key -CAcreateserial \
-out registry.crt -extfile san.ext -extensions SAN
Configure your registry to use registry.crt and registry.key, then distribute ca.crt to all Docker clients via Fix 1.
Verifying the Fix
After applying any fix, confirm TLS is actually working before calling it done:
# Test the TLS handshake directly
curl -v --cacert /tmp/registry-ca.crt https://registry.internal:5000/v2/
# Expect: HTTP 200 and an empty JSON body: {}
# Test Docker pull end-to-end
docker pull registry.internal:5000/myapp:latest
# See exactly what cert the registry is serving
openssl s_client -connect registry.internal:5000 -showcerts
If docker pull completes without the x509 error, you're done.
Still Failing After Fix 1?
- Wrong cert copied: You need the root CA cert, not the server cert. Verify with
openssl x509 -in /etc/docker/certs.d/registry.internal:5000/ca.crt -text -nooutโ look forCA:TRUEunder Basic Constraints. - Hostname mismatch: The hostname in your
docker pullcommand must match the cert's CN or SAN exactly. Check withopenssl x509 -in registry.crt -text | grep -A1 "Subject Alternative". - Incomplete cert chain: Got intermediate CAs? Concatenate the full chain into one file:
cat intermediate.crt root-ca.crt > /etc/docker/certs.d/registry.internal:5000/ca.crt. - Docker Desktop cache: On macOS or Windows, a full restart of Docker Desktop (not just the daemon) is required after adding certs.
Prevention
- Distribute the internal CA cert as part of machine provisioning โ Ansible, Chef, or a startup script. New machines arrive already trusting your registry.
- Add the CA cert location to your team's onboarding docs. It's a five-minute fix, but only if you know what to look for on day one.
- For registries accessible via a public DNS name, Let's Encrypt eliminates the self-signed cert problem entirely โ free, auto-renewing, universally trusted.

