How to Fix "x509: certificate signed by unknown authority" in Docker

VigilDog Team · October 15, 2026 · 6 min read

You build a Docker image, run it, and a request to an internal API or registry dies with "x509: certificate signed by unknown authority." The certificate is valid, the server is fine, and yet your container refuses it. The error isn't lying: from inside that minimal image, the authority really is unknown. Here's what's actually happening and how to fix it without disabling verification.

What the error actually means

TLS clients trust a server by walking its certificate chain up to a root certificate they already hold in a local trust store. The x509 certificate signed by unknown authority error means that walk failed: somewhere in the chain is a CA your container has never heard of. The certificate is genuine; your container just wasn't given the root, or the intermediate, needed to verify it.

This is a container problem far more often than a certificate problem. Slim base images like alpine or distroless ship with a tiny or empty trust store, and internal CAs are never in the public bundle to begin with. So the same request that works on your laptop fails in the container, because your laptop's trust store is fuller than the image's.

The four usual causes in Docker

Before editing anything, figure out which of these you're hitting. The fix differs slightly for each, but they all come down to a missing certificate in the trust store.

  • A corporate proxy that intercepts TLS (MITM). Your traffic is re-signed by an internal CA the container doesn't trust.
  • A private registry or internal service using a self-signed or internal-CA certificate.
  • A minimal base image (alpine, scratch, distroless) with no ca-certificates package installed at all.
  • An incomplete chain: the server sends its leaf certificate but omits the intermediate, so no client can build a full path to a root.
Root CAin the trust storeIntermediatemust be installedYour certificateleaf, for your domain
The TLS certificate chain of trust

The fix: add the CA to the image's trust store

For the first three causes, the fix is to bake the missing CA into the image. Get the root (and any intermediate) as a PEM file, copy it into the system trust directory, and rebuild the trust bundle. On Debian or Ubuntu bases, the file must end in .crt and be PEM-encoded:

COPY corp-root-ca.crt /usr/local/share/ca-certificates/corp-root-ca.crt then RUN update-ca-certificates. That command requires the ca-certificates package, so add RUN apt-get update && apt-get install -y ca-certificates first if it isn't already present. On Alpine it's RUN apk add --no-cache ca-certificates, then the same COPY into /usr/local/share/ca-certificates/ followed by update-ca-certificates.

One caveat: some runtimes ignore the OS store. Go binaries read it, but you can also point them at a file with SSL_CERT_FILE. Node needs NODE_EXTRA_CA_CERTS set to your PEM path. Whatever you do, don't reach for the shortcut of disabling verification (insecure-skip-verify, NODE_TLS_REJECT_UNAUTHORIZED=0), which trades a clear error for a silent hole.

When the chain itself is incomplete

If adding the root doesn't help, the server may be shipping an incomplete chain, the fourth cause, and no client-side trust store will fix that. Verify what the server actually sends with openssl s_client -showcerts -connect host:443. If you see the leaf certificate but no intermediate, the server is misconfigured, not your image.

The fix belongs on the server: install the full chain (leaf plus every intermediate, in order) so any client can build a path to a public root. Browsers often paper over this using cached intermediates, which is why it works in Chrome but fails in your container. Our free SSL checker reports a broken chain in plain language, and the guide on expired SSL certificates covers the neighboring failure mode where the cert is trusted but out of date.

Verify the fix inside the container

After rebuilding, prove it from inside the image rather than trusting that the Dockerfile ran. Exec into the container and run openssl s_client -connect host:443; a clean handshake ends with "Verify return code: 0 (ok)." If your app is Go or Node, retry the actual request, since those runtimes may need the env var pointed at your PEM even after the OS store is updated.

The deeper lesson is that trust stores and certificates both expire and drift. An internal CA gets rotated, an intermediate changes, a cert lapses, and a build that passed last quarter starts failing today. VigilDog's monitoring watches certificate chains and expiry across your domains and internal endpoints, so a chain that quietly breaks becomes an alert instead of a 3am container crash.

Questions

Frequently asked

Why does the request work on my machine but fail in Docker?

Your machine has a full trust store with many root CAs and cached intermediates. A slim base image often has a tiny or empty store, and internal CAs are never in the public bundle, so the same chain that verifies locally has no path to a trusted root inside the container.

Can't I just disable certificate verification to get past this?

You can, but you shouldn't. Flags like insecure-skip-verify or NODE_TLS_REJECT_UNAUTHORIZED=0 turn a clear, fixable error into a silent vulnerability that accepts any certificate. Adding the missing CA to the trust store is only a couple of Dockerfile lines and keeps verification intact.

I added the root CA and it still fails. What now?

The server is likely sending an incomplete chain, omitting an intermediate. Run openssl s_client -showcerts against it: if only the leaf appears, fix the server to serve the full chain. No client-side trust store can compensate for a broken chain.

Catch broken chains before your builds do

VigilDog monitors certificate chains and expiry across your domains and internal endpoints, so a silent break becomes an alert.

Your first domain is free forever