TLS certificates drop to 47 days: the dates and what to automate
Public TLS certificates max out at 100 days from March 15, 2027 and 47 from 2029. Our simulation shows which renewal rules let them lapse.
The short read
A public TLS certificate issued from March 15, 2029 may last 47 days at most, down from 398 days.
- The next cut, to 100 days, applies from March 15, 2027. Let's Encrypt moves its default from 90 to 64 days on February 10, 2027.
- In our simulation, rules that renew at a fixed certificate age, such as 60 days, let a 47-day certificate expire.
What you can do with this
Search your cron jobs and timers for fixed renewal ages, move them to renew with a third of the lifetime left, and alert on a share of the lifetime instead of a fixed 30 days.
In this article
- The schedule, from the requirements’ own text
- Why the browsers pushed for it
- Let’s Encrypt moves ahead of the deadline
- Our test: which renewal rules survive 47 days
- Rules keyed to a certificate’s age fail first
- Step 1: list every public certificate you own
- Step 2: find hardcoded renewal ages
- Step 3: let the ACME client choose when to renew
- Step 4: make the new certificate reach the server
- Step 5: alert on a share of the lifetime, not on 30 days
- What the script did on our test certificates
- When automation is hard: paid certificates and appliances
- When not to use a public certificate at all
- The simulation code
- How we made this
Publicly trusted TLS certificates issued from March 15, 2027 may last 100 days at most, and from March 15, 2029 just 47, under the CA/Browser Forum’s Baseline Requirements. The first cut, to 200 days, has applied since March 15, 2026. A certificate someone renews by hand once a year becomes eight renewals a year, and the domain check behind each one shrinks to a 10-day reuse window.
If you run a website or an appliance with a public certificate, this guide is for you. It gives the dates from the rule text and a simulation of which renewal rules survive. It ends with a tested script that alerts before a certificate lapses. We read the requirements and the Let’s Encrypt and Certbot documents on October 27, 2026.
The schedule, from the requirements’ own text
The schedule comes from ballot SC-081, which passed on April 11, 2025. It was proposed by Clint Wilson of Apple and endorsed by Sectigo, Google Chrome and Mozilla, according to the ballot page. Certificate issuers voted 25 for, none against and 5 abstaining; all four browser makers that voted (Apple, Google, Microsoft and Mozilla) voted for it.
The dates below are copied from sections 4.2.1 and 6.3.2 of the Baseline Requirements, version 2.3.1, dated October 4, 2026.
| Certificate issued | Maximum validity | Domain validation reuse | Organization details reuse (OV) |
|---|---|---|---|
| Before March 15, 2026 | 398 days | 398 days | 825 days |
| March 15, 2026 to March 14, 2027 | 200 days | 200 days | 398 days |
| March 15, 2027 to March 14, 2029 | 100 days | 100 days | 398 days |
| From March 15, 2029 | 47 days | 10 days | 398 days |
Three details in the text matter more than the headline number.
The limit follows the issue date. The tables are keyed to “Certificate issued on or after”, so a certificate already in use keeps its full term. A 200-day certificate issued on March 1, 2027 still runs into September.
Expect a day less than the maximum. Section 6.3.2 says certificates “SHOULD NOT” exceed 199, 99 and 46 days in the three periods. It counts a day as 86,400 seconds and treats any extra fraction as another day, so it tells CAs not to issue the full maximum by default.
Domain checks shrink faster than lifetimes. Each certificate needs proof that you control the domain. A CA may reuse that proof for 200 days now, 100 days from 2027 and 10 days from 2029. By 2029, nearly every renewal repeats the check. Verified organization details, which OV certificates carry, can still be reused for 398 days.
The rules cover publicly trusted certificates only. Section 1.1 says they apply to certificates trusted because their root ships “in widely-available application software”. A private CA for internal names does not have to follow them.
Why the browsers pushed for it
The ballot’s stated rationale is short: “Certificates are representations of a point in time state of reality.” A domain can change owners, and a key can leak, while a certificate keeps vouching for the old facts until it expires. A shorter term caps how long a wrong certificate stays valid.
The ballot page names wider use of two-way automation as a side benefit. That is the part that costs you work. When a renewal comes every few weeks, a reminder in someone’s calendar stops being a plan.
Here is the arithmetic, computed by us from the table. Renewing at the last possible moment, one certificate needs about 1 renewal a year at 398 days, 2 at 200 days, 4 at 100 days and 8 at 47 days. Renewing with a third of the lifetime left, as Certbot does by default, a 47-day certificate renews every 31 days or so, close to 12 times a year.
Let’s Encrypt moves ahead of the deadline
Let’s Encrypt, the free CA, shortens its own certificates before the rules force it. Its December 2025 announcement and its October 7, 2026 update give this schedule:
| Date | Default certificate | Authorization reuse |
|---|---|---|
| Until February 9, 2027 | 90 days | 30 days |
| From February 10, 2027 | 64 days | 10 days |
| From February 16, 2028 | 45 days | 7 hours |
The staging environment switched to 64-day certificates on October 14, 2026, so you can test against it now. Let’s Encrypt expects the last 90-day certificate to expire on May 11, 2027, and says it will not revoke valid certificates as part of the change. Its profiles page lists a tlsserver profile that already issues 45-day certificates and a shortlived profile at 160 hours, about six days.
One safety net is already gone. Let’s Encrypt stopped sending expiration emails on June 4, 2025, citing its cost and privacy, and noting that more subscribers now automate renewal. If nobody set up monitoring after that, nobody hears about a failed renewal until the site breaks.
Our test: which renewal rules survive 47 days
Let’s Encrypt’s advice is to stop renewing on a fixed schedule. Its October post tells subscribers to renew at “approximately ⅔ of the lifetime”. It also says to “grep for common hardcoded numbers like 83, 80 or 60 in cron jobs, wrapper scripts and runbooks.”
We wanted to see how much margin each common rule leaves. So we wrote a short simulation (code below) of six renewal rules at six certificate lifetimes. For each pair it tries every start time of the scheduled job in one-hour steps and keeps the worst case. It reports the days left on the certificate at the first renewal attempt, and how many attempts fit before expiry if every attempt fails.
These are modeled schedules, not measurements of real servers. Each cell reads “days left at first attempt / attempts before expiry”.
| Rule | 398 d | 200 d | 100 d | 90 d | 64 d | 47 d |
|---|---|---|---|---|---|---|
| Renew at age 60 days, checked daily | 337 / 338 | 139 / 140 | 39 / 40 | 29 / 30 | 3 / 4 | Expires |
| Renew at age 83 days, checked daily | 314 / 315 | 116 / 117 | 16 / 17 | 6 / 7 | Expires | Expires |
| Renew under 30 days left, checked daily | 29 / 30 | 29 / 30 | 29 / 30 | 29 / 30 | 29 / 30 | 29 / 30 |
| Renew under 30 days left, checked monthly | under 1 hour / 1 | same | same | same | same | same |
| Renew under 1/3 of lifetime left, checked daily | 132 / 132 | 66 / 66 | 32 / 33 | 29 / 30 | 20 / 21 | 15 / 15 |
| Renew under 1/3 of lifetime left, checked weekly | 126 / 18 | 60 / 9 | 26 / 4 | 23 / 4 | 14 / 3 | 9 / 2 |
Rules keyed to a certificate’s age fail first
Rules keyed to the certificate’s age are the trap. “Renew at 60 days” was fine for 90-day certificates. It leaves 3 days on a 64-day certificate and never fires on a 47-day one. “Renew at 83 days” already fails at 64 days, the Let’s Encrypt default from February 2027.
A job that checks monthly is unsafe at any lifetime. With a 30-day threshold, the worst start time leaves one attempt with under an hour to spare, so a single failure means an expired certificate. The “under 30 days left” rule checked daily keeps working at 47 days, but it renews after 17 days, more often than needed.
The rule Certbot uses since version 4.0, a third of the lifetime, scales with the certificate. Checked daily, it gives a 47-day certificate 15 attempts, about two weeks to notice and fix a failure.
Step 1: list every public certificate you own
Start with an inventory: the names, where each certificate is installed and what renews it. Look beyond web servers. Load balancers, mail servers, VPN gateways, printers with web consoles and vendor appliances often carry a certificate someone uploaded by hand.
Write down for each one whether a tool renews it (an ACME client, a hosting platform, a cloud load balancer) or a person does. Every manual entry is a job that runs eight or more times a year from 2029.
Step 2: find hardcoded renewal ages
Search scheduled jobs for fixed intervals near renewal commands. On a Linux server:
sudo grep -rnE '\b(30|60|80|83|90)\b' /etc/crontab /etc/cron.d /var/spool/cron 2>/dev/null | grep -iE 'cert|acme|renew|ssl'
systemctl list-timers --all | grep -iE 'certbot|acme|lego'
The first line lists cron entries that mention a certificate tool and a suspicious number. The second shows systemd timers for common ACME clients. Also search deployment scripts and runbooks in your repositories for the same numbers.
Replace anything that says “renew every N days” or “renew when the certificate is N days old” with a rule based on time left, as in step 3.
Step 3: let the ACME client choose when to renew
If you use Certbot, check its version with certbot --version. The changelog shows two changes that matter here:
- Version 4.0.0 (April 7, 2025): certificates renew with a third of their lifetime left, or half for lifetimes under 10 days. Before that, the threshold was a fixed 30 days.
- Version 4.1.0 (June 10, 2025):
certbot renewchecks ACME Renewal Information (ARI), a standard published as RFC 9773, and may renew early when the CA asks. For Let’s Encrypt certificates that typically means renewal at about two thirds of the lifetime.
The Certbot user guide says most installations come with a scheduled task that runs certbot renew, which you can find in the crontab or with systemctl list-timers. Check that a renewal configuration file under /etc/letsencrypt/renewal/ does not pin an old renew_before_expiry value. Without an ARI response, that setting still overrides Certbot’s default, according to the 4.1.0 changelog entry.
Test against Let’s Encrypt’s staging environment before February 10, 2027. With Let’s Encrypt certificates, certbot renew --dry-run uses staging, according to the changelog, so it now exercises 64-day certificates.
Other ACME clients differ. Let’s Encrypt says to check your client’s documentation for ARI support; if it has ARI, the CA can tell it when to renew.
Step 4: make the new certificate reach the server
A renewed certificate on disk does nothing until the service loads it. Certbot runs a --deploy-hook command only after a successful renewal, or any executable placed in /etc/letsencrypt/renewal-hooks/deploy/, per its user guide. Use it to reload nginx or Apache, or to copy the files to the load balancer or appliance that serves them.
This step fails quietly. The renewal log says success while the old certificate stays loaded in memory. A monitor that connects to the live service, like the script below, is the only way to catch it.
Step 5: alert on a share of the lifetime, not on 30 days
A fixed “alert under 30 days” breaks as lifetimes shrink. Take a 47-day certificate renewed with a third of its lifetime left. Even when healthy, it spends about two weeks of each cycle under 30 days, so the alert fires constantly and people learn to ignore it.
This script reads each certificate’s own start and end dates and alerts relative to its lifetime. It connects to a host, or reads a PEM file, and prints one line per target:
- OK: more than a third of the lifetime left.
- RENEW_DUE: under a third left, the window where ACME clients renew.
- ALERT: under a quarter left (change it with
--alert-fraction), so renewal should have happened days ago. - EXPIRED or ERROR: past its end date, or the target could not be read.
It also makes a second, normal TLS connection to check the chain and hostname, and notes when that fails. It exits with code 2 when anything needs attention, so a cron job or CI step can page someone. It needs Python 3 and pip install cryptography.
#!/usr/bin/env python3
"""Report days left on TLS certificates and alert relative to each one's lifetime.
Targets are host, host:port or a path to a PEM file. Exit code 0 when all are
fine, 2 when any is expired, close to expiry or could not be read.
Needs: pip install cryptography
"""
import argparse
import datetime as dt
import socket
import ssl
import sys
from cryptography import x509
UTC = dt.timezone.utc
def not_before(cert):
# cryptography 42+ has the *_utc properties; older versions return naive UTC
return getattr(cert, "not_valid_before_utc", None) or cert.not_valid_before.replace(tzinfo=UTC)
def not_after(cert):
return getattr(cert, "not_valid_after_utc", None) or cert.not_valid_after.replace(tzinfo=UTC)
def fetch(target, timeout):
"""Return (certificate, trusted) for a host[:port] or a PEM file path."""
if "/" in target or target.endswith((".pem", ".crt")):
with open(target, "rb") as f:
return x509.load_pem_x509_certificate(f.read()), None
host, _, port = target.partition(":")
port = int(port or 443)
# First handshake without verification, so expired or untrusted
# certificates can still be read.
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
with socket.create_connection((host, port), timeout=timeout) as sock:
with ctx.wrap_socket(sock, server_hostname=host) as tls:
der = tls.getpeercert(binary_form=True)
return x509.load_der_x509_certificate(der), verify(host, port, timeout)
def verify(host, port, timeout):
"""Second handshake with normal checks: chain, dates and hostname."""
ctx = ssl.create_default_context(cafile=ARGS.cafile)
try:
with socket.create_connection((host, port), timeout=timeout) as sock:
with ctx.wrap_socket(sock, server_hostname=host):
return True
except ssl.SSLCertVerificationError as e:
return e.verify_message or "verification failed"
def main():
targets = list(ARGS.targets)
if ARGS.file:
with open(ARGS.file) as f:
targets += [l.strip() for l in f if l.strip() and not l.startswith("#")]
if not targets:
sys.exit("no targets: pass host[:port] or PEM paths, or --file list.txt")
now = dt.datetime.now(UTC)
bad = 0
print("target\tstatus\tdays_left\tlifetime_days\texpires_utc\tnote")
for t in targets:
try:
cert, trusted = fetch(t, ARGS.timeout)
except Exception as e: # one failing target must not stop the rest
bad += 1
print(f"{t}\tERROR\t\t\t\t{type(e).__name__}: {e}")
continue
start, end = not_before(cert), not_after(cert)
life = (end - start).total_seconds() / 86400
left = (end - now).total_seconds() / 86400
if left <= 0:
status = "EXPIRED"
elif left < life * ARGS.alert_fraction:
status = "ALERT" # renewal was due days ago and has not happened
elif left < life / 3:
status = "RENEW_DUE" # ACME clients normally renew in this window
else:
status = "OK"
note = "" if trusted in (True, None) else f"untrusted: {trusted}"
if status in ("EXPIRED", "ALERT") or note:
bad += 1
print(f"{t}\t{status}\t{left:.1f}\t{life:.0f}\t{end:%Y-%m-%d %H:%M}\t{note}")
sys.exit(2 if bad else 0)
if __name__ == "__main__":
p = argparse.ArgumentParser(description=__doc__)
p.add_argument("targets", nargs="*", help="host, host:port or cert.pem")
p.add_argument("--file", help="text file with one target per line")
p.add_argument("--alert-fraction", type=float, default=0.25,
help="alert when less than this share of the lifetime is left (default 0.25)")
p.add_argument("--cafile", help="extra CA bundle for the trust check (private CAs)")
p.add_argument("--timeout", type=float, default=10)
ARGS = p.parse_args()
main()
Run it as python3 check_certs.py example.com mail.example.com:465, or put one target per line in a file and pass --file hosts.txt. Schedule it daily from a machine other than the one it checks, so it still runs when that server is down.
What the script did on our test certificates
We made a local test CA with OpenSSL on October 27, 2026 and issued six certificates for localhost with backdated start dates. They covered every status, from a fresh 398-day certificate to a 90-day one that had expired 2 days earlier. We served three of them with openssl s_server and pointed the script at the files, the three servers and one closed port.
| Target | Status | Days left | Lifetime |
|---|---|---|---|
| 398-day file | OK | 398.0 | 398 |
| 200-day file | OK | 80.0 | 200 |
| 47-day file, 14 days left | RENEW_DUE | 14.0 | 47 |
| 47-day file, 9 days left | ALERT | 9.0 | 47 |
| 6-day file, 1 day left | ALERT | 1.0 | 6 |
| 90-day file, expired | EXPIRED | -2.0 | 90 |
| Server with the 47-day certificate | RENEW_DUE | 14.0 | 47 |
| Server with the expired certificate | EXPIRED | -2.0 | 90, noted “untrusted: certificate has expired” |
| Server with the 398-day certificate | OK | 398.0 | 398 |
| Closed port | ERROR | ConnectionRefusedError |
It exited with code 2, and the error on the closed port did not stop the other checks. Without --cafile, the server with our test CA’s certificate was flagged “unable to get local issuer certificate” and the exit code was 2. An empty target list stopped with a message instead of printing an empty report.
A fixed 30-day alert would have flagged the healthy 47-day certificate with 14 days left. The script marks it RENEW_DUE, which is the normal state while the ACME client works.
When automation is hard: paid certificates and appliances
Paid certificates get no exemption. An OV certificate’s organization check can be reused for 398 days. But the domain check falls to 10 days in 2029, so a portal that emails a validation link would send one for nearly every certificate.
The requirements offer a way to make the repeated check hands-off. Method 3.2.2.4.22, added by ballot SC-088 in 2025, lets you publish one standing TXT record at _validation-persist.<your domain> that names your account at the CA. A validation done this way counts for at most 10 days, so the CA checks the record again, but nobody has to change DNS for each renewal.
Let’s Encrypt wrote in February 2026 that it targeted production support for some time in the second quarter of 2026. Ask your own CA whether it supports the method.
The trade-off, in Let’s Encrypt’s words, is that protecting the ACME account key “becomes the central concern”.
If a device cannot run an ACME client, put a reverse proxy that runs one in front of it. Or check whether the vendor’s current firmware supports ACME.
When not to use a public certificate at all
A service that only your own staff and machines reach can use a private CA. The Baseline Requirements do not bind it, so you choose the lifetime. The price is distributing your root certificate to every device and container that connects, and keeping its key safe. For anything the public or customers reach, stay with a publicly trusted certificate and automate it.
Do not jump to six-day certificates just because they exist. Let’s Encrypt’s shortlived profile suits teams whose renewal already runs on ARI with monitoring. In our simulation, a weekly check with the one-third rule left a 47-day certificate only 2 attempts. A six-day certificate leaves far less room.
The simulation code
This is the script behind the renewal-rule table. It uses only the Python standard library.
"""Which renewal rules survive shorter certificate lifetimes?
For each rule and lifetime, try every start time of the job (in one-hour
steps) and record the worst case: days left on the certificate at the first
renewal attempt, and how many attempts fit before it expires.
"""
import math
LIFETIMES = [398, 200, 100, 90, 64, 47]
# name, days between job runs, function(age, remaining, lifetime) -> renew?
RULES = [
("Renew at age 60 days, checked daily", 1, lambda age, rem, life: age >= 60),
("Renew at age 83 days, checked daily", 1, lambda age, rem, life: age >= 83),
("Renew under 30 days left, checked daily", 1, lambda age, rem, life: rem < 30),
("Renew under 30 days left, checked monthly", 30, lambda age, rem, life: rem < 30),
("Renew under 1/3 of lifetime left, checked daily", 1, lambda age, rem, life: rem < life / 3),
("Renew under 1/3 of lifetime left, checked weekly", 7, lambda age, rem, life: rem < life / 3),
]
STEP = 1 / 24 # one hour, in days
def worst_case(period, rule, life):
worst_left, worst_attempts = math.inf, math.inf
for k in range(int(period / STEP)):
phase = (k + 0.5) * STEP # half-step offset avoids runs exactly at expiry
# certificate issued at day 0; job runs at phase, phase + period, ...
t = phase
while t < life:
age, rem = t, life - t
if rule(age, rem, life):
attempts = math.ceil(rem / period) # runs at t, t+p, ... before expiry
worst_left = min(worst_left, rem)
worst_attempts = min(worst_attempts, attempts)
break
t += period
else:
# no run renewed before expiry
return 0.0, 0
return worst_left, worst_attempts
def main():
print("rule\t" + "\t".join(f"{l}d" for l in LIFETIMES))
for name, period, rule in RULES:
cells = []
for life in LIFETIMES:
left, attempts = worst_case(period, rule, life)
cells.append("EXPIRES" if attempts == 0 else f"{left:.2f}d/{attempts}")
print(name + "\t" + "\t".join(cells))
if __name__ == "__main__":
main()
Change LIFETIMES or add a rule that matches your own cron job to see its margin before February 10, 2027, when Let’s Encrypt’s 64-day default arrives.
For scheduled jobs beyond certificates, our guide to making scheduled scripts report failures covers heartbeats and alerts, and the eMudhra case shows what happens on the trust-store side when a CA fails.
How we made this
We read the Baseline Requirements (version 2.3.1), the SC-081 ballot page, Let’s Encrypt’s announcements and profiles page, and Certbot’s user guide and changelog on October 27, 2026. We wrote and ran the renewal simulation, which models schedules rather than measuring real servers. We tested the expiry script on certificates from a local OpenSSL test CA, served on localhost. We did not test against public websites, because the sandbox’s network proxy replaces their certificates, and we did not try any paid CA’s portal or a vendor appliance.
Sources
- CA/Browser Forum, Baseline Requirements for TLS Server Certificates, version 2.3.1, sections 1.1, 1.2.2, 3.2.2.4.22, 4.2.1 and 6.3.2
- CA/Browser Forum, Ballot SC-081v3: Introduce schedule of reducing validity and data reuse periods
- Let’s Encrypt, Decreasing Certificate Lifetimes to 45 Days (December 2, 2025)
- Let’s Encrypt, 64-Day Certificate Lifetimes Coming Feb 2027 (October 7, 2026)
- Let’s Encrypt, Profiles
- Let’s Encrypt, Ending Support for Expiration Notification Emails (January 22, 2025)
- Let’s Encrypt, Simplifying Certificate Renewals with ACME Renewal Information (March 17, 2026)
- Let’s Encrypt, DNS-PERSIST-01: A New Model for DNS-based Challenge Validation (February 18, 2026)
- Certbot, User guide and changelog