A Docker registry can work normally for months and then suddenly start returning TLS or certificate errors after a certificate renewal or infrastructure change.
The application may still be running, the registry may be reachable from a browser, and the certificate may appear valid when inspected manually. However, Docker builds and image pulls can still fail.
Typical errors include:
x509: certificate signed by unknown authority
or:
tls: failed to verify certificate
Another common situation is a certificate that looks valid but fails because the server is not sending the complete certificate chain.
These problems often appear after a certificate authority change, certificate renewal, load balancer migration, reverse-proxy update, or registry infrastructure change.
The important point is that Docker clients validate the certificate chain differently from simply opening the registry URL in a browser. A successful browser connection does not automatically prove that every Docker client can establish a trusted TLS connection.
This article explains how to diagnose and fix Docker registry TLS problems caused by certificate chain changes.
How Docker Registry TLS Authentication Works
When Docker connects to a secure registry, the connection normally follows this sequence:
Docker Client
|
| HTTPS / TLS
v
Load Balancer or Reverse Proxy
|
v
Docker Registry
During the TLS handshake, the registry server provides its certificate.
The Docker client then verifies:
The certificate is valid.
The certificate matches the registry hostname.
The certificate has not expired.
The certificate chain can be trusted.
The trusted root or intermediate certificate is available to the client.
If any of these checks fail, Docker can reject the connection.
For example:
registry.example.com
|
v
Server Certificate
|
v
Intermediate CA
|
v
Root CA
The client must be able to build a valid trust path from the server certificate to a trusted root.
Why a Certificate Chain Change Can Break Docker
Suppose a registry previously used:
Registry Certificate
|
v
Old Intermediate CA
|
v
Trusted Root CA
After a certificate renewal, the infrastructure may change to:
Registry Certificate
|
v
New Intermediate CA
|
v
New Root CA
If the Docker host does not trust the new CA chain, image pulls can fail.
Another common problem is an incomplete server configuration:
Registry Server
|
+-- Server certificate
|
+-- Missing intermediate certificate
The server certificate may be valid, but the client cannot construct the complete chain.
This is especially common after replacing certificates on NGINX, Apache, HAProxy, cloud load balancers, or registry gateways.
Identify the Exact TLS Error
Start by reproducing the failure from the machine that is actually running Docker.
For example:
docker pull registry.example.com/myapp:latest
Do not test only from your laptop if the failure occurs in CI.
A CI runner may have a different operating system, CA store, Docker configuration, or network path.
Common errors include:
x509: certificate signed by unknown authority
This generally indicates that the certificate chain cannot be validated against the client's trusted CA certificates.
Another error may look like:
x509: certificate is valid for registry.internal.example.com,
not registry.example.com
This indicates a hostname mismatch.
An expired certificate can produce an error similar to:
certificate has expired or is not yet valid
The exact error message determines the next troubleshooting step.
Check the Certificate Presented by the Registry
OpenSSL can help inspect the certificate chain returned by the registry:
openssl s_client \
-connect registry.example.com:443 \
-servername registry.example.com \
-showcerts
The -servername option is important when the server uses SNI and hosts multiple certificates.
Look for the certificates returned by the server.
A healthy configuration commonly resembles:
Server Certificate
Intermediate CA
The root CA is normally trusted by the client rather than being sent by the server.
The important question is whether the server provides the required intermediate certificates.
Check the Certificate Dates
Inspect the server certificate:
openssl s_client \
-connect registry.example.com:443 \
-servername registry.example.com </dev/null 2>/dev/null |
openssl x509 -noout -dates
You should see:
notBefore=...
notAfter=...
Make sure the current date falls within that period.
Also check the system clock on the Docker host:
date
A significantly incorrect system clock can cause a valid certificate to appear expired or not yet valid.
Check the Certificate Subject and SAN
A certificate must contain the hostname used by Docker.
For example:
registry.example.com
should be present in the certificate's Subject Alternative Name extension.
You can inspect it with:
openssl s_client \
-connect registry.example.com:443 \
-servername registry.example.com </dev/null 2>/dev/null |
openssl x509 -noout -subject -issuer -ext subjectAltName
You may see:
X509v3 Subject Alternative Name:
DNS:registry.example.com
DNS:registry.internal.example.com
If Docker connects to:
registry.example.com
that hostname must be covered by the certificate.
Changing the CA chain will not fix a hostname mismatch.
Check Whether the Server Sends the Intermediate Certificate
One of the most common problems after certificate replacement is installing only the leaf certificate.
For example, the server may be configured with:
server.crt
when it actually needs:
fullchain.pem
The exact configuration depends on the reverse proxy or registry infrastructure.
Conceptually, the server should provide:
-----BEGIN CERTIFICATE-----
Server certificate
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
Intermediate CA
-----END CERTIFICATE-----
The private key remains separate and must not be included in the certificate chain file.
If the intermediate certificate is missing, some clients may still succeed while others fail.
This is one reason browser testing alone is not enough.
Verify the Registry With curl
Use curl from the same host where Docker fails:
curl -v https://registry.example.com/v2/
A working registry often responds with:
HTTP/1.1 401 Unauthorized
That response is not necessarily an error.
For a private registry, 401 Unauthorized can actually indicate that TLS succeeded and the registry is now asking for authentication.
The important distinction is:
TLS failure
|
v
Connection rejected before HTTP
TLS success
|
v
HTTP 401
|
v
Authentication required
If curl reports a certificate validation error, investigate the CA chain before investigating registry credentials.
Fixing an Incomplete Certificate Chain
If the server is not sending the required intermediate certificate, update the TLS configuration to use the complete certificate chain.
For example, an NGINX configuration might reference:
server {
listen 443 ssl;
server_name registry.example.com;
ssl_certificate /etc/nginx/tls/fullchain.pem;
ssl_certificate_key /etc/nginx/tls/registry.key;
location / {
proxy_pass http://registry:5000;
}
}
The exact file names and paths depend on the environment.
After updating the configuration, validate it:
nginx -t
Then reload the service:
nginx -s reload
If a cloud load balancer terminates TLS, the certificate chain must be corrected at the load balancer rather than inside the registry container.
Fixing an Untrusted CA
Sometimes the server is correctly sending the chain, but the Docker host does not trust the issuing CA.
This can happen with:
Private certificate authorities
Enterprise PKI
Newly introduced intermediate CAs
Internal registries
Custom security infrastructure
The correct solution is to install the required CA certificate into the trust store used by the Docker environment.
Do not immediately disable TLS verification.
For Linux systems, the exact CA installation process depends on the distribution.
For example, Debian- and Ubuntu-based systems commonly use the system CA directory and update mechanism:
sudo cp company-registry-ca.crt \
/usr/local/share/ca-certificates/company-registry-ca.crt
sudo update-ca-certificates
Then test again:
curl https://registry.example.com/v2/
and:
docker pull registry.example.com/myapp:latest
The certificate file should contain the public CA certificate required for trust.
Never install a private key into the system CA store.
Docker-Specific Certificate Trust
Docker also has registry-specific certificate handling.
For a private registry, Docker can use certificates placed under a registry-specific directory such as:
/etc/docker/certs.d/registry.example.com/
For example:
/etc/docker/certs.d/registry.example.com/ca.crt
The hostname and port need to correspond to the registry address used by Docker.
If the registry is accessed through a non-standard port:
registry.example.com:5000
the certificate directory must reflect that registry endpoint.
After changing Docker's certificate configuration, restart Docker when required by the environment:
sudo systemctl restart docker
Then test:
docker pull registry.example.com/myapp:latest
Do Not Disable TLS Verification as a First Fix
Docker environments sometimes use insecure-registry configuration to bypass TLS verification.
For example:
{
"insecure-registries": [
"registry.example.com"
]
}
This should not be the normal solution for a production registry.
Disabling certificate verification removes an important security control.
It can allow a client to communicate with a registry without properly verifying its identity.
The preferred order is:
Check certificate
|
v
Check hostname
|
v
Check certificate chain
|
v
Check client trust store
|
v
Fix CA configuration
|
v
Retest Docker
Only use insecure registry configuration when the environment has a specific, controlled reason for doing so.
When the Registry Uses a Reverse Proxy
Many registry deployments look like this:
Docker Client
|
v
HTTPS
|
v
NGINX / Load Balancer
|
v
Registry
|
v
Storage
In this architecture, TLS is often terminated at the proxy.
That means changing the certificate inside the registry container may have no effect.
Check where TLS termination happens before modifying configuration.
For example:
Client
|
| HTTPS
v
Load Balancer
| TLS terminates here
|
| HTTP
v
Registry
In this case, the load balancer owns the public certificate.
A common troubleshooting mistake is changing the registry container while the actual TLS problem exists at the load balancer.
CI/CD Runners Need Separate Verification
A certificate can work on a developer workstation and fail in CI.
For example:
Developer laptop
|
+-- Updated CA store
+-- Docker works
CI runner
|
+-- Older CA store
+-- Docker fails
Run the same checks inside the CI runner:
docker version
curl -v https://registry.example.com/v2/
openssl s_client \
-connect registry.example.com:443 \
-servername registry.example.com
This helps determine whether the issue belongs to the registry or the client environment.
If several independent CI runners fail simultaneously after a certificate renewal, the registry-side certificate chain should be investigated first.
Common Mistakes
Installing only the server certificate
A server certificate alone may not provide the chain required by clients.
Use the correct full certificate chain configuration.
Testing only in a browser
Browsers can have different trust stores and certificate-chain behavior.
Always test from the actual Docker client.
Updating the wrong machine
If the problem occurs in CI, installing the CA only on a developer workstation does not solve the problem.
Updating the registry when TLS terminates elsewhere
If a load balancer terminates TLS, update its certificate configuration.
Disabling certificate verification
This hides the underlying trust problem and weakens transport security.
Forgetting the registry port
Docker's certificate configuration is endpoint-specific.
A registry using:
registry.example.com:5000
must be configured differently from one using:
registry.example.com
Troubleshooting Checklist
Use this sequence when a Docker registry starts failing after a certificate change:
Capture the exact Docker error.
Test the registry from the affected Docker host.
Check the certificate expiration date.
Check the hostname in the certificate SAN.
Inspect the certificates returned by the server.
Verify that required intermediate certificates are being sent.
Determine where TLS termination occurs.
Check whether the client trusts the issuing CA.
Update the appropriate system or Docker-specific CA configuration.
Restart affected services when required.
Test with curl.
Test with Docker pull or push.
Repeat the test from CI runners.
Remove temporary troubleshooting changes after verification.
Advantages and Disadvantages of Proper Certificate Management
Advantages | Disadvantages |
|---|
Maintains TLS verification | Certificate renewals require planning |
Prevents unexpected registry outages | CA changes may require client updates |
Works consistently across Docker and CI clients | Multiple trust stores can complicate troubleshooting |
Keeps private registries securely authenticated | Reverse proxies add another TLS configuration layer |
Makes certificate problems easier to diagnose | Incorrect chain configuration can affect many clients |
Best Practices for Production Registries
Use a certificate lifecycle process instead of waiting for certificates to expire.
Track:
Certificate
|
+-- Expiration
+-- Issuing CA
+-- Intermediate CA
+-- SANs
+-- TLS termination point
+-- Client trust requirements
Before a certificate change reaches production, test the new chain from:
Developer environments
CI runners
Kubernetes nodes
Container hosts
Deployment systems
If the registry is used by multiple environments, document where the CA trust is configured.
Also keep certificate renewal separate from application deployment whenever possible. A registry certificate change should not require rebuilding every application image.
Summary
Docker registry TLS failures after a certificate change are usually caused by one of four areas: an invalid certificate, a hostname mismatch, an incomplete certificate chain, or a client that does not trust the new CA.
Start with the exact Docker error and reproduce the problem from the affected host. Use openssl s_client to inspect the certificate chain and curl to determine whether TLS succeeds before authentication begins.
If the server is missing an intermediate certificate, fix the certificate-chain configuration at the TLS termination point. If the certificate is issued by a private or newly introduced CA, update the trust store used by the Docker client.
Avoid using insecure registry configuration as a shortcut. The goal is to restore proper certificate validation rather than bypass it.
The most reliable approach is to treat registry certificates as part of the infrastructure lifecycle: monitor expiration, document the CA chain, test certificate changes before production, and verify the configuration from the same environments that actually pull and push container images.
Join the conversation! Your thoughts help the community grow.