On 20 October 2026 Docker shipped Docker Engine 29.9.2, a patch release that looks routine: one Go runtime bump. It is not routine. The new runtime makes the daemon refuse any TLS certificate chain that passes through one of eight eMudhra intermediate CAs whose private keys were compromised. If your private registry, mirror or artifact server bought its certificate from one of those intermediates, docker pull and docker push against it stop working the moment you upgrade.
The error message makes it worse, because it points at the wrong cause. Below is what changed, how to check your hosts and registries in a few minutes, and what to change in daemon config, Dockerfiles and CI.
What happened, in order
According to the public disclosure (the page Docker's own release notes link to), a researcher proved control of the private keys of eight eMudhra intermediate certificates by signing a message with each of them and reported it to the browser and OS root programs on 17 October. The same page says the keys "were incorrectly generated by the CA", so anyone could recover them from the certificates. Chrome and Firefox blocked the intermediates from 17 October, Microsoft from the 20th and Apple from the 22nd.
Browsers were fixed through revocation feeds. Many other clients do not read revocation data at all, and the disclosure is blunt about it: those clients "require software updates to ensure they no longer trust the compromised intermediates." Docker's CLI, daemon, Buildx and Compose are Go programs, so their fix came from Go.
- Go 1.26.10 was released on 19 October with a crypto/x509 security fix. The Go issue for CVE-2026-111943 says "Verify will now filter out chains which contain these intermediates." The same fix was backported to Go 1.27 as Go 1.27.3.
- Docker Engine 29.9.2 and Buildx 0.38.1 were published on 20 October, both built with Go 1.26.10.
- Docker Compose 5.6.1 followed on 21 October with the same Go update.
- Docker Desktop got the fix in pieces: 4.96.1 on 21 October (Engine 29.9.2, Buildx 0.38.1), then 4.96.2 and 4.96.3 on 22 October for the other bundled components.
The eight blocked intermediates, as listed in the Engine release notes, are EM DV TLS CA - G3B, EM DV TLS CA - G3C, EM OV TLS CA - G3B, EM OV TLS CA - G3C, EM EV TLS CA - G3B, EM EV TLS CA - G3C, and two code-signing CAs, EM OV CS CA - G3B and EM OV CS CA - G3C.
What breaks, and what the error looks like
The 29.9.2 release notes put it in one line: "Image pulls and pushes fail TLS certificate verification if a registry's certificate chain relies on one of the affected TLS intermediates." There is no flag to turn the filter off.
The disclosure site runs test hosts that serve certificates from each blocked intermediate, so we pointed our own Docker 29.9.2 host at one of them on 2 November:
$ docker pull dv-g3c-emudhra.we-have-a-problem.com/test:latest
Error response from daemon: Get "https://dv-g3c-emudhra.we-have-a-problem.com/v2/": tls: failed to verify certificate: x509: certificate signed by unknown authority
Read that error carefully. "Unknown authority" is the same message you get when a registry uses a self-signed certificate, so the first instinct of most people on call is to mark the registry as insecure or to add a CA file. Neither is the right fix here, and the first one makes things worse (more on that below).
The big public registries are not affected. On 2 November we checked the chains served by Docker Hub (registry-1.docker.io), quay.io and public.ecr.aws (all issued under Amazon roots), ghcr.io and registry.gitlab.com (Sectigo) and gcr.io (Google Trust Services). None of them go through eMudhra. The risk sits with self-hosted Harbor, Nexus, Artifactory or plain registry:2 deployments, pull-through mirrors, and internal servers that CI downloads from.
Step 1: see which versions you are running
These three commands are read-only and safe on a production host:
docker version --format '{{.Client.Version}} / {{.Server.Version}}'
docker buildx version
docker compose version --short
On our host that prints 29.9.2 / 29.9.2, v0.38.1 and 5.6.1. Anything older than Engine 29.9.2, Buildx 0.38.1 or Compose 5.6.1 from Docker's own packages was built with an older Go runtime and still accepts the compromised chains. That is the security problem the update exists to fix, so the answer is to upgrade and then deal with whatever breaks, not to stay behind.
Step 2: check every registry you pull from
List the registry hosts your stacks reference. This catches compose files on disk, including stacks that are stopped right now:
grep -rhoE 'image: *"?[a-z0-9.-]+\.[a-z]{2,}(:[0-9]+)?/' --include='*compose*.y*ml' /srv /opt 2>/dev/null \
| sed -E 's/image: *"?//; s#/$##' | sort -u
Then ask each one which chain it serves and look for the eMudhra intermediate names:
for reg in registry.example.com mirror.example.com:5000; do
host=${reg%%:*}; port=${reg##*:}; [ "$port" = "$reg" ] && port=443
if echo | openssl s_client -connect "$host:$port" -servername "$host" -showcerts 2>/dev/null \
| grep -qE 'CN ?= ?EM (DV|OV|EV) TLS CA - G3[BC]'; then
echo "AFFECTED: $reg"
else
echo "ok: $reg"
fi
done
We ran this loop against ghcr.io and against the G3C test host: the first came back ok, the second AFFECTED. Add the hosts your CI pipelines fetch from as well, since a build that downloads a binary from an internal server is just another TLS client.
Step 3: fix the registry, not the client
Docker's notes say it directly: "Affected registries need a replacement certificate chain that does not use these intermediates." In practice that means a new certificate from a different CA (or a different issuing chain from your vendor), installed on whatever terminates TLS in front of the registry, whether that is the registry itself, nginx, Traefik or a load balancer.
The tempting shortcut is this, in /etc/docker/daemon.json:
{
"insecure-registries": ["registry.example.com"]
}
Do not ship it. It tells the daemon to skip certificate checks for that host, which is exactly the protection the Go update added. Anyone who can sit in the network path with a certificate from one of those leaked keys can then serve you any image they like under your registry's name.
If the registry is internal and you would rather not buy another public certificate, issue one from your own CA and give Docker the CA file the way the Docker documentation on registry certificates describes:
/etc/docker/certs.d/
registry.example.com/
ca.crt
A chain that ends at your own CA never touches eMudhra, so the filter has nothing to reject. Once the new certificate is live, run the loop from Step 2 again and pull one image to confirm.
Step 4: your images need the same fix
Upgrading Docker protects Docker. It does nothing for the software inside your containers. The disclosure lists the clients still at risk: "applications and libraries that ship their own CA bundle," plus Electron apps, and it notes that many non-browser clients do not check revocation.
A rebuilt image with a fresh ca-certificates package does not solve this on its own. A CA bundle lists trusted roots. It has no way to say "trust this root, except for these intermediates under it." The rejection has to come from the TLS library, which is what Go did. So for Go services, the fix is a rebuild with a patched toolchain. In a multi-stage Dockerfile:
# before
FROM golang:1.26.9 AS build
# after
FROM golang:1.26.10 AS build
Both golang:1.26.10 and golang:1.27.3 are on Docker Hub. If your go.mod pins a toolchain, raise it too, or the old one gets downloaded anyway:
# before
toolchain go1.26.9
# after
toolchain go1.26.10
A rebuild only helps if the containers get recreated from the new image. We wrote about that trap in our PHP2Shell rebuild guide: a patched base image does nothing for a container started from the old one.
CI runners deserve a look too. If a workflow pins Buildx through docker/setup-buildx-action, the pin decides which Go runtime does your pushes:
# before
- uses: docker/setup-buildx-action@v4
with:
version: v0.38.0
# after
- uses: docker/setup-buildx-action@v4
with:
version: v0.38.1
Laptops on Docker Desktop
Docker flagged in the 4.96.1 notes that "This fix is being rolled out in batches as individual Docker Desktop components are updated." A team on 4.96.0 has none of it, and a team on 4.96.1 has the Engine part but not the rest. Get everyone to at least 4.96.3.
Mac users have one more gap. A 28 October update on the disclosure page says the developer tools Apple ships with macOS, including git, curl and python3, still trust the compromised certificates, and that macOS 27.2 undid fixes users had applied by hand. A patched Docker Desktop on that laptop does not change what git clone trusts.
What we did on our own hosts
We run our production services in Docker, so this was a short checklist rather than a project: confirm the Engine, Buildx and Compose versions above, run the chain check against every registry our compose files reference, and confirm none of our own certificates come from eMudhra. Nothing was affected, and that is the outcome you want to know about before an upgrade, not after a failed deploy at 2 a.m. If you keep a habit of checking versions across hosts, the same approach from our PostgreSQL 14 upgrade guide works here, and our write-up on how we run our own CMS on Docker shows where TLS terminates in a typical self-hosted stack.
Frequently asked questions
Can I downgrade Docker Engine to get pulls working again?
You can, but the old engine would also accept a forged certificate for any registry, Docker Hub included, signed with one of the leaked keys. Replace the registry's certificate instead.
Is Docker Hub affected?
No. On 2 November 2026 the certificate chain for registry-1.docker.io went through Amazon RSA 2048 M01 and Amazon Root CA 1, not eMudhra. The same was true for ghcr.io, quay.io, public.ecr.aws, registry.gitlab.com and gcr.io.
Our registry uses a self-signed or internal CA certificate. Do we need to do anything?
Not because of this change. The filter only removes chains that contain the eight eMudhra intermediates. A chain that ends at your own CA, configured under /etc/docker/certs.d, keeps working.
Does updating ca-certificates in our images block the compromised intermediates?
No. A CA bundle only lists roots, so it cannot exclude specific intermediates under a root that stays trusted. The block has to come from the TLS library or from revocation checking. For Go programs, rebuild with Go 1.26.10 or 1.27.3.
Which Docker versions include the fix?
Docker Engine 29.9.2, Buildx 0.38.1 and Compose 5.6.1, released on 20 and 21 October 2026. On Docker Desktop the fix arrived across 4.96.1, 4.96.2 and 4.96.3.
Sources: Docker Engine 29 release notes, moby/moby docker-v29.9.2 release, Buildx v0.38.1, Compose v5.6.1, Docker Desktop release notes, Go issue 82345, Go release history and the eMudhra intermediate CA compromise disclosure. Commands tested on Docker Engine 29.9.2, Buildx 0.38.1 and Compose 5.6.1 on 2 November 2026.



