The escape hatch closed on July 8
If your services still authenticate each other with client certificates from Let's Encrypt, one of two things is true right now. Either you already moved off them, or something in your fleet is quietly failing a handshake and nobody has traced it back to this yet.
July 8, 2026 was the last day Let's Encrypt issued a certificate carrying the TLS Client Authentication EKU. The tlsclient ACME profile, the one they kept alive specifically so you'd have time to migrate, is gone. Every certificate they hand out now asserts serverAuth and nothing else. If you were leaning on a public certificate to prove a client's identity, that crutch has been kicked out.
This isn't a Let's Encrypt quirk you can dodge by switching CAs. It's the whole public Web PKI splitting down the middle, and Let's Encrypt just happened to get there first.
What serverAuth and clientAuth actually are
Every certificate carries an Extended Key Usage extension that spells out what it's allowed to do. For years a typical publicly-trusted certificate asserted two purposes: id-kp-serverAuth and id-kp-clientAuth. One certificate, two jobs. It could identify a server to a browser, and it could also identify a client to a server during a mutual-TLS handshake. Cheap and convenient. Also, as far as the browser vendors are now concerned, a mistake.
The direction of travel is single-purpose hierarchies. A certificate that chains to a root trusted by web browsers is going to be allowed to do exactly one thing: authenticate a server. Client authentication gets pushed out of the public Web PKI entirely and into private infrastructure, where it honestly always belonged.
Why Chrome forced the split
The deadline everyone is reacting to comes from the Chrome Root Program Policy, version 1.8, last updated February 5, 2026. Two dates matter:
- June 15, 2026. Any newly disclosed subordinate CA certificate under a root in the Chrome Root Store must assert
serverAuthonly. Multipurpose intermediates are out. - March 15, 2027. Every newly issued leaf certificate that chains to a Chrome-trusted root must assert
serverAuthonly too.
The reasoning is dedicated hierarchies. A PKI trusted to identify servers on the public internet should do that and nothing else, because bolting clientAuth onto the same certificates broadens the attack surface and muddies what a browser is actually trusting. Mozilla's Root Store policy has been steering the same way. This is not one vendor's opinion you can wait out.
Chrome's own deadline for leaf certificates is March 2027. Let's Encrypt chose to move well ahead of it, which is why this landed on operators more than a year early.
The Let's Encrypt timeline, in the order it hit you
Three dates, and the middle one is the one that quietly broke things:
- October 1, 2025. Let's Encrypt launched the
tlsclientACME profile, which kept the Client Authentication EKU for anyone who needed more time. - February 11, 2026. The default
classicprofile stopped including the Client Authentication EKU. This is the silent one. If you renewed on the default profile any time after that date, you got a certificate withoutclientAuthand probably didn't notice until a rotation later. - July 8, 2026. The
tlsclientprofile was retired. No further certificates with the Client Authentication EKU are issued, full stop.
So the failure mode isn't a single cliff. Some teams broke in February when a routine renewal swapped in a serverAuth-only cert. Others held on with tlsclient and broke last month. If your mTLS is still standing, you either migrated or you're running on certificates issued before July 8 that haven't rotated yet.
How to tell whether this is your problem
Pull the EKU straight off a certificate file:
openssl x509 -in client.pem -noout -ext extendedKeyUsage
If the output lists TLS Web Client Authentication, that certificate can still act as a client credential. If it only says TLS Web Server Authentication, it can't act as one. Not against any peer that actually checks, anyway.
To inspect a certificate a live service is presenting:
echo | openssl s_client -connect gateway.internal:8443 2>/dev/null \
| openssl x509 -noout -ext extendedKeyUsage
Then check the other side of the handshake, because the EKU only bites if the verifier enforces it. Plenty do. Go's crypto/tls verifies the chain against ExtKeyUsageClientAuth when you set RequireAndVerifyClientCert. OpenSSL applies purpose checks when you ask it to. nginx's ssl_verify_client on leans on the same machinery underneath. A minimal Go server that will reject a serverAuth-only cert looks like this:
cfg := &tls.Config{
ClientAuth: tls.RequireAndVerifyClientCert,
ClientCAs: clientPool,
}
Here's the trap. Some verifiers never check the EKU at all, so a serverAuth-only cert keeps sailing through, right up until you bump a TLS library that starts enforcing it. That's a stay of execution, nothing more. If your inventory still has public certs doing client duty, treat them as already broken.
What actually breaks
The blast radius is narrower than the panic suggests, but it's real:
- Service-to-service mTLS where the client certificate came from Let's Encrypt or another public CA.
- XMPP server-to-server authentication, which historically used public certs on both ends.
- API gateways and VPN concentrators configured to require a client certificate with the
clientAuthEKU. - Anything that pins or explicitly validates
clientAuthon an incoming certificate.
If you run public-facing HTTPS and nothing else, you are not affected. Your website certificate needs serverAuth, that's exactly what you still get, and nothing changes for you. This whole story is about the certificates you point inward.
The fix is a private CA, and it always was
A public CA was the wrong tool for internal client identity from the start. Nobody outside your own systems needs to validate a service's client certificate. Paying for public trust, and now fighting it, bought you nothing but rate limits and other people's policy changes. If you've read our take on why a single public CA is a liability, this is the same lesson arriving from a different direction.
A private CA hands you back control. Your own EKUs, your own naming, your own certificate lifetimes, no ACME rate limits on a bad Friday, and no root program deciding what your certificate is permitted to assert. For client authentication that was always the right setup. Most people just reached for Let's Encrypt because it was already there.
Standing up a private CA without ruining your month
Smallstep's step-ca is the fastest way to a working internal CA. Initialize one:
step ca init --name "Internal mTLS CA" \
--dns ca.internal --address :443 \
--provisioner admin
Then issue a client certificate for a service, with a lifetime you control:
step ca certificate svc-a.internal svc-a.crt svc-a.key \
--not-after 720h
If you live in Kubernetes, cert-manager does the same job declaratively. Bootstrap a self-signed issuer, use it to mint a CA certificate, then wrap that CA in an issuer your workloads can reference:
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: internal-ca-issuer
spec:
ca:
secretName: internal-ca-key-pair
Now request a client certificate. Spell out the usages explicitly, because this is the part people miss and cert-manager won't guess:
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: svc-a-client
spec:
secretName: svc-a-client-tls
issuerRef:
name: internal-ca-issuer
kind: Issuer
commonName: svc-a.internal
duration: 720h
usages:
- client auth
- digital signature
- key encipherment
Distribute that private CA's certificate as the trust anchor on the verifying side, and your handshake is back. This time on a certificate you own end to end. If your renewals get stuck, our notes on debugging cert-manager issuance loops will save you an afternoon.
What to do this week
Order of operations, roughly:
- Find every client certificate that chains to a public CA. If you don't have a real inventory, that gap is its own problem.
- Check the EKU on each one with the
opensslcommand above. - Stand up a private CA for internal client authentication.
- Reissue the client certs, swap the trust bundles on the verifying side, and roll it out the way you'd roll any client cert rotation, carefully, one side at a time.
- Keep watching expiry across both public and private certs, because a private CA doesn't email you when something is about to lapse.
None of this is hard. It's just work nobody scheduled, triggered by a deadline set in a room you weren't in. The teams that sailed through February already knew which certificates did what. The ones still finding broken handshakes didn't. Which one you are is mostly a question of inventory.
Frequently asked questions
Will my website's HTTPS certificate stop working?
No. A server certificate only needs the serverAuth EKU, and that's exactly what public CAs still issue. If all you do is terminate public HTTPS, nothing about this change touches you.
Can I keep renewing an old client certificate until it expires?
You can't renew it with the clientAuth EKU from Let's Encrypt anymore. The default profile dropped it in February 2026 and the tlsclient profile is gone as of July 8. A certificate already issued with clientAuth keeps working until it expires or you rotate it, but that's a countdown, not a plan.
Is this only Let's Encrypt, or every public CA? Every publicly-trusted CA under the Chrome Root Program. Let's Encrypt moved early and loudly, so it's the one people noticed, but the June 2026 and March 2027 deadlines apply across the public Web PKI. Switching to another public CA buys you nothing.
Do I need a public CA to sign my private CA's server certificates? No. Internal services validate against your private CA's certificate, which you distribute as a trust anchor yourself. Public trust is only for things the public browser needs to verify.
How do I stop this from surprising me again? Inventory first, then monitor. Know which certificates you have, where they chain, what EKUs they assert, and when they expire. That goes for private certificates as much as public ones. Most of these outages are inventory failures wearing a policy costume.