Secrets, PKI & CertificatesIX · Certificate Lifecycle and RevocationLifecycle
Renewal windows, overlapping validity and safe delivery
What you'll learn
- Size a renewal trigger as a proportion of the certificate lifetime
- Explain why two simultaneously valid certificates make zero-downtime renewal possible
- Generate key material on the host that will use it, so only public data travels
- Install a certificate and key atomically so no process observes a mismatched pair
Prerequisites
Verified against OpenSSL 3.5.x teaching target; 3.0+ minimum · OpenSSH 10.x teaching target; 8.2+ minimum for certificate workflows · OpenBao 2.6.x · Smallstep step-ca 0.30.x · Certbot / Pebble Certbot current release; Pebble 2.10.x ACME test server · Kubernetes (cross-course target) 1.36.x · PostgreSQL 17.x · 2026-08-26
Renewal is not an operation performed on an existing certificate. It is the issuance of an entirely new one for an identity that already holds a valid certificate, which is why the old and the new coexist for a while. Understanding that overlap is what turns a certificate swap from a held-breath maintenance window into a routine, reversible change that nobody notices.
Renewal replaces the statement, not the identity
The certificate is a signed statement binding a name to a public key for a bounded period. Renewal produces a second such statement about the same name, normally from the same issuer, with a different serial number and a later validity window. Nothing about the identity moves. Nothing in the trust store changes. The client that trusted the old certificate trusts the new one for the same reason it trusted the first, because the chain leads to the same anchor.
Three things typically differ between the old certificate and its replacement, and it is worth knowing which is which when you compare them during an incident.
- The serial always differs. An issuing authority must not reuse a serial, so the serial is the reliable identity of one particular issuance and the field you compare to prove a swap landed.
- The validity window always moves forward. The new
notBeforeis usually at or slightly before the moment of issuance and the newnotAftersits one full lifetime later. - The public key may or may not change. Reusing the existing key pair is common and is a separate decision with its own consequences, examined later in this part.
The renewal window is a fraction, not a fixed number of days
Almost every estate that suffers an expiry outage had a renewal threshold expressed in days, chosen when certificates lived for a year or more. That number does not survive contact with the current validity schedule.
The CA/Browser Forum Baseline Requirements shorten the maximum public TLS certificate lifetime on a published schedule. Certificates issued on or after 2026-03-15 must not exceed a 200-day validity period. From 2027-03-15 the cap becomes 100 days, and from 2029-03-15 it becomes 47 days. The recommendation text sits one day lower than each cap. Separately, a subscriber certificate now counts as short-lived when its validity period is 7 days or less.
Work through what a fixed threshold does against that schedule. A warning at 30 days was generous when certificates routinely lived for more than a year, and it is merely adequate against a 200-day one. Against a 47-day certificate it fires while roughly a third of the lifetime is still unused, so the alert becomes background noise that operators learn to ignore. Worse, it leaves no room for a renewal that fails and has to be retried across a weekend.
Express the trigger as a proportion instead. Renewing when one third of the lifetime remains gives two independent retry opportunities before any client is affected, at every lifetime.
# Renew when less than a third of the original lifetime is left.
CERT=/etc/ssl/certs/app.lab.example.pem
LIFETIME_DAYS=90
TRIGGER_SECONDS=$(( LIFETIME_DAYS * 86400 / 3 ))
if openssl x509 -in "$CERT" -noout -checkend "$TRIGGER_SECONDS"; then
echo "inside the safe zone, nothing to do"
else
echo "entering the renewal window, attempt renewal now"
fi
For the 90-day certificate in that example, issued on 26 August 2026 and expiring on 24 November 2026, the window opens on 25 October. That leaves thirty days in which a failed attempt is an inconvenience rather than an incident. Automated issuance protocols formalise the same idea: ACME Renewal Information, standardised as RFC 9773 in June 2025, lets the issuing authority hand each client a suggested renewal window instead of leaving the client to guess.
Two valid certificates for the same name is the feature
Operators new to certificate work often assume that issuing a
replacement somehow retires the old one. It does not. The previous
certificate remains exactly as valid as it was, until its own
notAfter passes, because validity is a property of a signed
document rather than of a registry entry.
flowchart LR
A["Old certificate\nvalid to 24 Nov"] --> B["New certificate\nissued 25 Oct"]
B --> C["Overlap window\n25 Oct to 24 Nov"]
C --> D["Swap and reload\nany time in overlap"]
C --> E["Roll back to old\nmaterial if needed"]
That overlap is the whole safety margin. Because both documents verify, you can install the new pair, reload one instance, watch it, and reverse the change by putting the old files back if anything misbehaves. Nothing has to happen at the instant of expiry, and no step in the procedure is irreversible while the overlap lasts. Once the old certificate has expired the same rollback is worthless, which is precisely why renewing at the last moment removes the property that makes renewal safe.
Getting new material onto the host without leaking it
The safest renewal is one in which no private key ever crosses a network, a chat window or a ticket system. That is achievable by default, because the certificate signing request contains only public data: the name, the public key, the requested extensions, and a signature made with the private key to prove possession.
Generate the key on the machine that will use it, with a restrictive umask so the file is never briefly world readable, and send only the request outward.
umask 077
KEYDIR=/etc/ssl/private
install -d -m 0750 -o root -g ssl-cert "$KEYDIR"
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 \
-out "$KEYDIR/app.lab.example.key"
openssl req -new -key "$KEYDIR/app.lab.example.key" -sha256 \
-subj "/CN=app.lab.example" \
-addext "subjectAltName=DNS:app.lab.example,DNS:www.app.lab.example" \
-out /tmp/app.lab.example.csr
Only the request file leaves the host. The certificate that comes back is public information that you could publish on a noticeboard without harm. If your workflow instead generates the key somewhere central and copies it out, every copy is a place the key can leak from, and you have created a rotation obligation for yourself the moment anyone suspects one of those copies.
Installing atomically so no process observes a mismatched pair
A certificate and its key are a matched set. If a reload happens between writing one file and writing the other, the process reads a certificate whose public key does not correspond to the key on disk and refuses to start, or worse, keeps running while the next restart fails hours later during an unrelated change.
Verify correspondence before installing anything, then move both files into place with renames rather than in-place edits.
STAGE=/root/renewal-staging
KEYDIR=/etc/ssl/private
CERTDIR=/etc/ssl/certs
# Prove the new certificate matches the new key before touching production.
KEY_HASH=$(openssl pkey -in "$STAGE/app.key" -pubout | openssl sha256)
CRT_HASH=$(openssl x509 -in "$STAGE/app.crt" -noout -pubkey | openssl sha256)
test "$KEY_HASH" = "$CRT_HASH" || { echo "key and certificate do not match"; exit 1; }
install -m 0640 -o root -g ssl-cert "$STAGE/app.key" "$KEYDIR/app.lab.example.key.new"
install -m 0644 -o root -g root "$STAGE/fullchain.pem" "$CERTDIR/app.lab.example.pem.new"
mv "$KEYDIR/app.lab.example.key.new" "$KEYDIR/app.lab.example.key"
mv "$CERTDIR/app.lab.example.pem.new" "$CERTDIR/app.lab.example.pem"
The correspondence test is the same one used to diagnose a mismatched pair after the fact. The public key derived from the private key and the public key embedded in the certificate hash to the same value when they belong together:
$ openssl pkey -in app.key -pubout | openssl sha256
SHA2-256(stdin)= 75061de3387b8969c6c33ba0931ec0e541ad4c90a4ee2ec3e734e031af66874b
$ openssl x509 -in app.crt -noout -pubkey | openssl sha256
SHA2-256(stdin)= 75061de3387b8969c6c33ba0931ec0e541ad4c90a4ee2ec3e734e031af66874b
Renaming within the same filesystem is atomic, so any process that opens the path gets either the old file or the new one and never a partially written one. Writing directly over a live key file with a redirect is the version of this that fails, because the file is briefly empty.
Production discipline
- Store the intended lifetime beside the certificate record. The renewal trigger is a fraction of it, so automation that does not know the lifetime cannot compute the window.
- Never let renewal be the first thing that touches a host after months of quiet. Configuration drift discovered during a renewal turns a routine change into an investigation.
- Keep the previous certificate and key for the length of the overlap. They are your rollback, and they stop being useful the moment the old certificate expires.
- Make the staging directory as protected as the destination. A key written world readable into a temporary path has already been exposed, whatever its final permissions are.
- Fail the renewal loudly rather than silently retrying forever. A job that has failed eight consecutive nights has to surface before the ninth, not after expiry.
Cross-course references
- Linux for Production Sysadmins - Part LXXI (TLS) covers certificate renewal and expiry as a host discipline, including the filesystem permissions this lesson relies on for safe delivery.
- Git, CI/CD & GitOps for Infrastructure Engineers - Part XCIII (CredRotation) covers scheduling credential changes in a pipeline, which is where a proportional renewal trigger is usually implemented.
- Observability for Production Sysadmins - Part XVIII (AlertingRules) covers writing the rule that fires when the renewal window opens and again when it is nearly gone.
Quiz
Knowledge check · 4 questions
Q1. A renewal threshold is currently hard-coded at 30 days. What breaks first as public TLS validity caps fall to 47 days in 2029?
Q2. Issuing a replacement certificate causes the previous certificate for that name to stop being valid.
Q3. Why does generating the key pair on the host that will use it remove an entire class of leak, and what is the only thing that then needs to travel?
Q4. Decide how to sequence this renewal and what you would refuse to do.
A 90-day certificate for app.lab.example was issued on 26 August 2026 and expires on 24 November 2026. It is now 25 October. The platform team has a replacement certificate and a freshly generated key in a staging directory on a jump host, and proposes to copy both files to the four web nodes at 23:00 on 23 November because that is the agreed change window.
Passing score: 75%. Answers are checked in this browser.