eMudhra CA Compromise: Update ca-certificates Now
Table of contents
On October 17, 2026, the browser root programs were told that the private keys of eight eMudhra intermediate CA certificates had been compromised. Firefox and Chrome blocked them the same day, Microsoft followed on October 20 and Apple on October 22. If you only think about browsers, you can stop reading. But I run PHP apps that call payment gateways, ERPs and shipping APIs with Guzzle and curl, and those clients do not care what Chrome decided. They trust whatever CA bundle sits on the server or in the Docker image. Here is what happened, why your backend is the part at risk, and the ten-minute fix I applied to every server and container.
What happened with eMudhra
According to the disclosure page set up by the researchers, the private keys of the intermediates were incorrectly generated by the CA, so anyone can recover them from the certificates themselves. No break-in, no leak: the public certificate was enough. The chains lead to roots trusted by Mozilla, Microsoft, Apple and Chrome, and each intermediate also has a Sectigo cross-sign.
The consequence is as bad as it gets for a CA: with one of those keys, an attacker can issue a certificate for any domain and clients that trust the chain will accept it. That means a man-in-the-middle on api.stripe.com, your bank's SFTP gateway or your Magento instance's payment callback, with a perfectly "valid" certificate.
Two details matter for the risk assessment:
- The certificates were created in April 2025 and have no Certificate Transparency log entries. Browsers that enforce CT were therefore likely never affected.
- Non-browser clients usually do not enforce CT. curl, PHP's OpenSSL stream wrapper, Guzzle, wget, Python requests, Java: they only check the chain against a local trust store.
Why your PHP backend is the exposed part
When your Laravel job calls an external API, the TLS handshake is verified by libcurl or OpenSSL against the system bundle, typically /etc/ssl/certs/ca-certificates.crt on Debian and Ubuntu. That file comes from the ca-certificates package. If the package is old, your app still trusts the compromised intermediates, whatever the browsers did.
The places I found stale bundles on my own infrastructure:
- Long-running VPS where unattended upgrades only cover the security pocket, or are not enabled at all.
- Docker images built weeks ago and never rebuilt. A running container never updates its own trust store.
- Vendored bundles:
composer/ca-bundle(used by Composer and some HTTP libraries as a fallback), Python'scertifi, and Node.js, which ships its own CA store. - Java services with their own
cacerts, which ignore the system bundle entirely.
Test your servers in one command
The researchers host test endpoints signed through the compromised chain. From each server, and from inside each container, run:
curl https://dv-g3b-emudhra.we-have-a-problem.com/
curl https://dv-g3b-aaa-emudhra.we-have-a-problem.com/
Here you want a certificate error. A successful response means the machine still trusts the compromised intermediates. The page also lists -noct- variants of these hosts to check whether a client trusts the chain without enforcing CT.
To test what PHP itself sees (it may use a different bundle than the curl binary, depending on openssl.cafile and curl.cainfo in php.ini):
php -r 'var_dump(@file_get_contents("https://dv-g3b-emudhra.we-have-a-problem.com/"));'
bool(false) is the answer you are looking for.
How to fix it: update ca-certificates everywhere
The fix is a package update. The disclosure page lists the patched versions per platform, including:
- Debian 12:
ca-certificates 20250419~deb12u2; Debian 13:20250419+deb13u1 - Ubuntu 22.04:
20260816ubuntu0.22.04.1; Ubuntu 24.04:20260816ubuntu0.24.04.1 - Alpine:
ca-certificates 20261020-r0 - composer/ca-bundle:
1.4.16or1.6.0 - Python certifi:
2026.10.19 - Node.js:
26.12.1,24.22.1,22.23.4
On a Debian or Ubuntu server:
sudo apt update
sudo apt install --only-upgrade ca-certificates
dpkg -l ca-certificates # check the version
sudo systemctl restart php8.4-fpm # long-running workers keep the old store in memory
On Alpine-based images, apk upgrade ca-certificates. In a Laravel or Magento project, update the vendored bundle too:
composer update composer/ca-bundle
composer show composer/ca-bundle | grep versions
Don't forget to restart Horizon, queue workers and Octane after the update. They are long-lived processes and may keep the trust store they loaded at boot.
Docker images and CI: rebuild, don't patch
For containers, don't exec into a running container to patch it. Pull a fresh base image and rebuild, so the fix survives the next deploy:
docker compose build --pull
docker compose up -d
If your Dockerfile pins a base image by digest, bump it. Then run the curl test inside the new container. Do the same for CI runners that call external APIs during tests or deploys, and check the node-version in your pipelines: the same week's Node.js security releases are the versions listed above, so updating Node for Vite builds kills two birds with one stone.
This came in the same week as the php2shell PHP security update. If you are already rebuilding images for PHP 8.4.27 or 8.5.12, add the CA bundle to the same pass.
Conclusion
The eMudhra CA compromise is a reminder that "the browsers handled it" is not the same as "my stack is safe". Your PHP backend talks to the outside world over TLS all day, and it trusts whatever list of CAs you shipped with it. My checklist: run the curl test from each server and container, update ca-certificates and composer/ca-bundle, rebuild images with --pull, restart long-running workers, and test again. Ten minutes per server, and you have closed a door that a valid-looking certificate for any domain could otherwise walk through.