Skip to main content

Command Palette

Search for a command to run...

Your OCI Let's Encrypt automation is probably broken right now - here's why and how to fix it

Updated
4 min read
Your OCI Let's Encrypt automation is probably broken right now - here's why and how to fix it

If you're running the excellent Let's Encrypt automation for Oracle Cloud Infrastructure built by Scotti Fletcher (full details in his original post here), there's a good chance your certificates have been silently failing to renew since February 2026 - and you might not even know it yet.

We found out because our TLS expiry alerting fired. Turns out that's not just a nice-to-have! Lesson learned - if you don't have alerting on your certificate expiry dates, go set that up right now before you read the rest of this.

Seriously. I'll wait.

So what broke?

On February 11, 2026, Let's Encrypt quietly dropped the "TLS Client Authentication" Extended Key Usage (EKU) from their default certificate profile. Previously LE certs included two EKUs - TLS Server Authentication and TLS Client Authentication. Now it's just TLS Server Authentication.

This was driven by Google Chrome's root program requirements mandating a separation of the two by June 2026. Totally reasonable change - but it caught a lot of people off guard.

Why does OCI care?

OCI Certificate Service records the EKU profile of the original certificate when it was first imported. When the automation tries to renew by uploading a new version, OCI throws this back at you:

"The certificate's new extended key usages does not match the existing extended key usages."

The renewal fails, your existing cert stays in place (so users see nothing), but the clock is ticking. Not great.

There's a bonus bug too

OCI gem 2.22.0 introduced a deserialisation issue where a FAILED certificate version (with nil validity) can come back first when listing versions in descending order. The function then tries to call .validity on nil and crashes. Fun times.

Fix it by finding the first version with an actual validity date rather than blindly taking the first result:

latest_cert = cert_client.list_certificate_versions(cert.id, sort_order: 'DESC')
  .data.items.find { |v| !v.validity.nil? }

How to fix the EKU mismatch

OCI won't let you update an existing cert with different EKUs, so you need to create a new one. This requires a tweak to the worker function to allow it to create a cert with a different name. Here's the zero-downtime approach we used:

Step 1 — Add a CERT_NAME_PREFIX environment variable to your OCI Function configuration (e.g. v2-) and update your worker/func.rb to use it:

CERT_NAME_PREFIX = ENV['CERT_NAME_PREFIX'] || ''

Merge it into your cert config in run_function:

cert_config = input.merge({
  'vault_compartment_ocid' => get_vault(input).data.compartment_id,
  'cert_name_prefix' => CERT_NAME_PREFIX
})

And use it when looking up and creating the certificate:

cert = cert_client.list_certificates(
  name: "#{cert_config['cert_name_prefix']}#{cert_config['cn_name'].start_with?('*.') ? "wildcard-#{cert_config['cn_name'][2..-1]}" : cert_config['cn_name']}",
  compartment_id: cert_config['cert_compartment_ocid']
).data.items.first

Step 2 — Invoke the function. Since no cert exists with the new prefixed name, it hits the add_cert path and creates a fresh one from Let's Encrypt with the correct EKU.

You can invoke the function from OCI Cloud Shell by doing the following:

$ fn invoke acme-certbot lets-encrypt

Step 3 — Update your LB listeners to point to the new certificate. OCI does a hot swap - zero downtime, zero impact to end users.

Step 4 — Delete the old certificate and you're done.

The cert name in OCI is purely an internal label - it has absolutely no bearing on what gets served to your users over HTTPS.

One more thing to be aware of

Certificate lifetimes are also shrinking. Let's Encrypt will reduce default validity from 90 days to 64 days in 2027, and down to 45 days in 2028. If you have hardcoded renewal intervals in your setup, now is a good time to make them dynamic.

TL;DR

Check your OCI Certificate Service versions tab now. If you see a FAILED latest version with nil validity dates, your renewals have been broken since February 11. Fix the gem bug with .find, create a new cert using the prefix approach, swap your listeners, and you're done.

And if you don't have TLS expiry alerting - seriously, go set it up.