Security

Key reuse at renewal: when a new certificate isn't a new key

August 18, 20269 min readCertPulse Engineering

I have a question I ask people who tell me their certificate renewal is fully automated. When that cert renewed last Tuesday, did the private key change?

Roughly half the time I get an instant answer. The other half I get a pause. Either answer is fine, reuse and rotation are both defensible positions. The pause is what worries me, because whether renewal produces a new keypair is a decision your ACME client made for you, possibly years ago, possibly because someone pasted a flag out of a 2021 forum thread and it stuck.

The CA has nothing to do with it. ACME (RFC 8555) is a protocol for getting a certificate issued over a public key you already hold. You generate the keypair, you build the CSR, the CA signs it. Renewal isn't something a CA does to an existing certificate. It's just another order for the same names. Hand the CA the same public key you handed it 47 days ago and you get a fresh certificate over the same key, and nothing in the protocol blinks. "Renewal" is a word from the dashboard. Issuance is the only thing that actually happens.

Which makes key rotation a client-side policy question, and most people are running whatever their client shipped with.

What your client actually does

Certbot generates a fresh keypair on every renewal. The --reuse-key flag flips that, and the part that bites people is that it's sticky. Certbot writes reuse_key = True into the lineage's renewal config under /etc/letsencrypt/renewal, in the [renewalparams] section, and every unattended renewal after that picks it up. Someone sets it once during a migration. They forget. They leave the company. Three years later that certificate is on its twentieth issuance over its first key. Clearing it takes --no-reuse-key or an edit to the conf file. Certbot 2.x also added --new-key, which forces one fresh key without touching the reuse setting. Exactly what you want mid-incident, and exactly the flag nobody remembers exists.

acme.sh goes the other way. It keeps the domain key in the certificate directory and reuses it across renewals unless you force it with --always-force-new-domain-key, settable per certificate or globally in the account config. lego generates a new key on renew unless you pass --reuse-key to the renew subcommand.

Two of the three most common ACME clients ship opposite defaults. Versions drift and defaults change, so verify against your actual installation rather than trusting any blog post, this one included.

cert-manager turns it into a field: privateKey.rotationPolicy on the Certificate resource, Never or Always. Never keeps the existing Secret's key forever, through every reissuance. That was the long-standing default and newer releases have moved toward Always, so an unset field means different things depending on your version, and a cluster upgraded in place doesn't always behave like a fresh install. Go read the rendered Certificate specs in your cluster. Don't assume.

The cloud services are their own category. AWS ACM, Azure App Service managed certificates, Google Certificate Manager's Google-managed certs: the key is generated inside the provider and never comes out. You can't inspect it, pin it, or rotate it on a schedule you chose. I don't think that's bad. A key you can't extract is a key that can't leak out of a backup tarball someone dropped in a bucket with public read. But it does put key-pinned architectures off the table for those endpoints, and it means the stable identifier you're looking at, the ACM ARN that survives managed renewal, tells you exactly nothing about key continuity. Azure Key Vault is the interesting middle case. Its certificate policy has an explicit reuse-key setting in the key properties, off by default, so Key Vault certs get a new key each renewal unless you say otherwise.

Reuse turns one bad day into a permanent one

A key that never changes converts a single disclosure into an indefinite compromise. The leaked backup. The Heartbleed-class memory read. The laptop with a PEM sitting in ~/Downloads. The CI runner that echoed key material into a build log in 2023 and nobody noticed. Any one of those hands an attacker something that stays valid for as long as you keep reissuing over it.

The failure mode I keep running into is reissue-without-rekey during incident response. You suspect key compromise. You revoke. You run the same renewal command you always run, get a new certificate, close the ticket. If the client was set to reuse, the attacker now holds a valid private key for the replacement certificate. You did the paperwork of an incident response and changed nothing.

The CA/Browser Forum Baseline Requirements put key compromise in the 24-hour revocation bucket, and they also oblige CAs to reject requests for keys known to be compromised or weak: the Debian OpenSSL keys, and increasingly anything published to services like pwnedkeys. Let's Encrypt maintains its own blocklist and will fail the order with an ACME badPublicKey problem document. Nice backstop. Terrible plan. It only fires if someone published your key, and it fires at finalization, in the middle of your outage.

Revocation is about the certificate. Rekeying is about the key. One without the other is theatre.

Things bound to the key, not the cert

Anything downstream that identifies your service by public key instead of by certificate breaks the instant the key changes.

HPKP is dead in browsers, but SPKI pinning is alive and well in mobile clients: OkHttp's CertificatePinner, TrustKit, Android's network security config pin-sets. Pin the leaf SPKI, rotate the key on renewal, and every installed copy of that app fails the handshake until a new build clears app store review. Backup pins exist for exactly this, and they only save you if someone generated the backup key ahead of time and can still find where they put it.

A DANE TLSA record of 3 1 1 asserts the SHA-256 of the leaf certificate's subject public key info. Selector 1 is why key reuse is so attractive to mail operators: keep the key and renewal needs no DNS change at all. Rotate it and you owe the rollover choreography from RFC 7671. Publish the new record alongside the old, wait out the TTL plus negative-caching margin, deploy the new key, then retire the old record. Get the ordering wrong and SMTP peers that validate DANE stop delivering to you, quietly, because DANE failure is hard failure. A 2 1 1 record pins the issuing CA instead, so leaf rotation costs nothing, but a CA or intermediate change takes you down. Both are defensible. Not knowing which one you published is not.

Hardware-backed keys have their own problem. A key born inside an HSM behind PKCS#11, or sealed to a TPM, or carrying a hardware attestation, does not get rotated by a cron job. It gets rotated by a procedure, sometimes with dual control and someone signing a piece of paper. "Every renewal" is not a sane cadence for a ceremony.

Then there's distribution. If the key has to land on forty hosts and the copy step is the fragile part of your pipeline, doubling key churn doubles your exposure to half-applied rollouts. A host holding the new certificate and the old key doesn't degrade gracefully. It fails the handshake or refuses to start.

The math at 47 days

SC-081v3 dropped maximum certificate lifetime to 200 days back in March. It hits 100 days in March 2027 and lands at 47 in March 2029. At 47 days, renewing exactly at expiry is about eight issuances per certificate per year. Nobody renews at expiry. Clients that follow the usual one-third-remaining rule, or take their window from ARI, land closer to every 31 days, so call it eleven or twelve. Across a 500-certificate estate that's five to six thousand issuances a year.

If key generation is a local ECDSA P-256 keygen, rotating every time costs nothing you can measure and you should just do it. No debate there. If every key means an HSM session, an attestation, a DNS rollover, or a mobile release, rotating every time is a full-time job you didn't hire for.

The compromise that actually works is decoupling the two cadences. Reuse the key across a bounded number of renewals, rotate on a fixed calendar (quarterly, semiannually, pick one), and rotate immediately on any compromise signal no matter where you are in the cycle. Be honest about what that buys you. A key reused for a year has a one-year exposure window, which is precisely what you had before lifetimes shortened. Shorter certificates limit the damage from misissuance and stale revocation data. They do nothing for key exposure. That axis only moves when you move it.

Practical checks

First, find out whether two consecutive certificates share a key:

openssl x509 -in cert.pem -pubkey -noout \
  | openssl pkey -pubin -outform der \
  | openssl dgst -sha256 -binary \
  | openssl base64

Same value on both certs, same key. That digest is the exact thing your SPKI pins carry, and drop the -binary for hex output and it's what a 3 1 1 TLSA record publishes. One value, three consumers. Run it across the last two issuances for a handful of certificates and you'll learn more about your fleet's real behavior in ten minutes than from an afternoon of reading configs.

Then make the decision explicit. Grep your certbot renewal configs for reuse_key. Set rotationPolicy on every cert-manager Certificate instead of letting a version bump decide for you. Check the reuse-key flag in your Key Vault certificate policies. An inherited default isn't a policy. It's an accident nobody has noticed yet.

Write the rekey step into the revocation runbook. Not "reissue the certificate." Write "generate a new key, then reissue." At 3am, people run the command they always run.

And check what's actually being served. The rotation that counts is the one that reached the load balancer. Monitoring your pipeline tells you the ACME client succeeded. Monitoring the endpoint tells you the terminating proxy picked it up. That's the gap CertPulse watches: it probes what your endpoints present, certificate and chain, so a rotation that never made it past the proxy shows up as an observation instead of a green checkmark in a log nobody reads.

Pick a policy, write it down, set the flag on purpose. The worst outcome isn't reuse, and it isn't rotation. It's finding out during an incident that you had no idea which one you were doing.

This is why we built CertPulse

CertPulse connects to your AWS, Azure, and GCP accounts, enumerates every certificate, monitors your external endpoints, and watches Certificate Transparency logs. One dashboard for every cert. Alerts when auto-renewal fails. Alerts when certs approach expiry. Alerts when someone issues a cert for your domain that you didn't request.

If you're looking for complete certificate visibility without maintaining scripts, we can get you there in about 5 minutes.

Key reuse at renewal: when a new certificate isn't a new key | CertPulse