DEV Community

Posting Dude
Posting Dude

Posted on

Let's Encrypt Goes to 64-Day Certificates in February. Grep Your Server for 60, 80 and 83 First.

Let's Encrypt posted a short note on October 7 that most small SaaS founders will scroll past. Starting February 10, 2027, every certificate it issues or renews will be valid for 64 days by default, not 90. Then on February 16, 2028, it drops again to 45 days.

The post also has one line I'd tape to the monitor:

Grep for common hardcoded numbers like 83, 80 or 60 in cron jobs, wrapper scripts and runbooks if you're not sure.

That's the whole risk in one sentence. If your HTTPS renewal was set up two years ago by "past you" and has worked silently since, there's a decent chance a 60 is hiding somewhere in it. On 90-day certs that 60 left you 30 days of slack. On 64-day certs it leaves 4. On 45-day certs it renews two weeks after the cert already died.

This post is the boring checklist I'd run on a tiny SaaS before February. Most of it takes an hour.

The dates, in one place

There are two separate things changing and people mix them up.

The industry rule. The CA/Browser Forum (the group whose rules every public CA follows) passed ballot SC-081 in 2025. Max certificate lifetime for certs issued:

  • from March 15, 2026: 200 days (it was 398)
  • from March 15, 2027: 100 days
  • from March 15, 2029: 47 days

Let's Encrypt's own schedule, which runs ahead of that:

  • now: 90 days by default
  • opt-in already: the shortlived profile issues ~6-day certs, and since May 13, 2026 the tlsserver profile issues 45-day ones
  • since October 14, 2026: the staging environment already issues 64-day certs, for testing
  • February 10, 2027: default goes to 64 days
  • around May 11, 2027: the last 90-day Let's Encrypt cert expires
  • February 16, 2028: default goes to 45 days

Nothing gets revoked. You just get the shorter cert at your next renewal after each date.

If you buy certs instead (from your registrar, or a "1 year SSL" upsell), you're already on the 200-day rule. A public example: as of late October 2026, dev.to itself serves a certificate issued in November 2025 that runs until December 12, 2026. That's 397 days, issued before the cutoff. Its replacement can't be longer than 200 days, and the one after March 2027 can't be longer than 100. If someone on your team installs a bought cert by hand once a year, that turns into at least twice a year now, about four times a year from 2027, and eight or more a year from 2029. Manual stops being a plan.

Why a tiny SaaS should care more than a big one

Big companies have a platform team and a cert inventory. At 30 customers, the cert inventory is "whatever certbot did in 2024."

Two things make it worse now:

  1. Nobody emails you anymore. Let's Encrypt stopped sending expiry reminder emails on June 4, 2025. If you set things up when those emails existed, your safety net was quietly removed. Certbot's own docs still have a line saying the CA will email you, which doesn't help.
  2. Webhooks fail before customers notice. Stripe needs a valid certificate on your webhook endpoint in live mode. If the cert expires, deliveries show a TLS error and Stripe retries for up to three days. After that you're replaying events by hand, and in the meantime renewals, cancellations and failed payments aren't reaching your app. Your marketing site might be on a host that handles certs for you while api.yourapp.com sits on a VPS that doesn't.

Step 1: find out what actually renews your certs

Before grepping anything, figure out which tool is in charge. On a typical small setup it's one of these:

  • A managed host or proxy (Vercel, Netlify, Cloudflare, Render, Fly, a managed load balancer). They issue and renew for you. Mostly not your problem, but check any custom domains you added by hand.
  • Caddy. It handles renewal itself and has supported ACME Renewal Information (ARI) since 2.8, so it asks Let's Encrypt when to renew.
  • Certbot. Usually a systemd timer or a cron entry someone added.
  • acme.sh. A cron entry under your user.
  • Something homemade. A script, a Docker container that runs certbot once, a GitHub Action. These are the ones that bite.

On a Linux box, this finds most of them:

systemctl list-timers --all | grep -i -E 'certbot|acme'
sudo grep -rn -i -E 'certbot|acme|renew' /etc/crontab /etc/cron.d /var/spool/cron 2>/dev/null
crontab -l 2>/dev/null | grep -i -E 'certbot|acme|renew'
certbot --version 2>/dev/null
~/.acme.sh/acme.sh --version 2>/dev/null
💬 ชาวเน็ตแห่แชร์: posting dude/lets encrypt goes to day certificates in february grep your server for and first lkn - สรุปดราม่าล่าสุด 💬 ชาวเน็ตแห่แชร์: posting dude/lets encrypt goes to day certificates in february grep your server for and first lkn - สรุปดราม่าล่าสุด

Write down what you find. Seriously, one line in a notes file: "api box, certbot 2.x, systemd timer, nginx reload hook: no". You'll need it in step 3.

Step 2: grep for the hardcoded numbers

Here's what common renewal rules do on each lifetime. "Slack" is how many days are left on the old cert when renewal first tries.

Renewal rule 90-day cert 64-day cert 45-day cert
Ask the CA via ARI (about 2/3 of lifetime) ~day 60, ~30 left ~day 43, ~21 left ~day 30, ~15 left
Certbot 4.0+ default (1/3 lifetime left) day 60, 30 left ~day 43, ~21 left day 30, 15 left
Old certbot default (30 days left) day 60, 30 left day 34, 30 left day 15, 30 left
acme.sh 3.1.2 and older default (59 days after issue) 31 left 5 left expired 14 days earlier
Hand cron, --force-renewal every 60 days 30 left 4 left expired 15 days earlier
Runbook "renew at 80 / 83 days" 10 / 7 left expired expired

The "days left" rules are fine. The "days since issue" rules are the ones that break. That's what Let's Encrypt means by grepping for 60, 80 and 83.

Where to look:

# certbot: per-cert overrides (no output is good)
sudo grep -rn renew_before_expiry /etc/letsencrypt/renewal/

# acme.sh: older versions wrote Le_RenewalDays='60' into every cert's conf
grep -rn Le_RenewalDays ~/.acme.sh/
# (if you never passed --days, a 60 here is just the old default)

# anything homemade that forces renewal on a fixed schedule
sudo grep -rn -E 'force-renew|--days|renew.*(60|80|83)' /etc/cron* /var/spool/cron /etc/systemd/system ~/bin ~/scripts 2>/dev/null
💬 ชาวเน็ตแห่แชร์: posting dude/lets encrypt goes to day certificates in february grep your server for and first lkn - สรุปดราม่าล่าสุด 💬 ชาวเน็ตแห่แชร์: posting dude/lets encrypt goes to day certificates in february grep your server for and first lkn - สรุปดราม่าล่าสุด

A few things from the changelogs worth knowing:

  • Certbot 4.0.0 switched its default from a fixed 30 days to "renew when less than 1/3 of the lifetime is left". Certbot 4.1.0 added ARI support, and an early ARI answer wins over a later renew_before_expiry. So a recent certbot with the stock timer is basically fine. An old certbot pinned by your distro is the thing to check.
  • acme.sh changed its default from 60 days to 30 days and added ARI in 3.1.3 (April 2026). Before that, the default renewal was 60 days after issue, minus a day. If you installed it a while ago and turned off auto-upgrade, acme.sh --upgrade is step one. Then run acme.sh --info -d yourdomain.com and look at Le_NextRenewTimeStr to see when it actually plans to renew, instead of trusting what you remember.
  • If your "automation" is a cron line with --force-renewal on a fixed calendar, replace it with the plain certbot renew running twice a day. Certbot's docs recommend exactly that and say it's safe to run as often as you like, since it does nothing until a cert is due.

Step 3: renewal isn't deployment

This one gets people even with perfect timing. Certbot renews the file on disk. Nginx keeps serving the old cert from memory until it reloads. If nothing reloads it, the new cert sits there unused and the old one expires right on schedule.

With certbot, add a deploy hook. It only runs after a successful renewal:

sudo tee /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh > /dev/null <<'HOOK'
#!/bin/sh
nginx -t && systemctl reload nginx
HOOK
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
💬 ชาวเน็ตแห่แชร์: posting dude/lets encrypt goes to day certificates in february grep your server for and first lkn - สรุปดราม่าล่าสุด 💬 ชาวเน็ตแห่แชร์: posting dude/lets encrypt goes to day certificates in february grep your server for and first lkn - สรุปดราม่าล่าสุด

With acme.sh it's the --reloadcmd you pass when installing the cert. Their README flags it as IMPORTANT, and they're right.

Other places the new cert never reaches: a Docker image that copied the cert in at build time, a load balancer or CDN where you uploaded a cert by hand, a second server that gets the cert by rsync from the first one. On 90-day certs you might've caught that by accident. On 64-day certs, you get fewer accidents.

To test the renew-plus-hook path without touching real certs:

sudo certbot renew --dry-run --run-deploy-hooks
💬 ชาวเน็ตแห่แชร์: posting dude/lets encrypt goes to day certificates in february grep your server for and first lkn - สรุปดราม่าล่าสุด 💬 ชาวเน็ตแห่แชร์: posting dude/lets encrypt goes to day certificates in february grep your server for and first lkn - สรุปดราม่าล่าสุด

--dry-run talks to the staging server and doesn't save anything. Deploy hooks don't run during a dry run unless you add --run-deploy-hooks.

Step 4: check the certificate people actually get, from outside

Checking the file on disk tells you what certbot did. It doesn't tell you what browsers and Stripe see. I'd run something like this from a machine that isn't the server, once a day:

#!/usr/bin/env bash
# check-certs.sh: fail if any cert you actually serve expires within DAYS days
DAYS=14
HOSTS="example.com www.example.com api.example.com status.example.com"

fail=0
for h in $HOSTS; do
  cert=$(echo | timeout 10 openssl s_client -connect "$h:443" -servername "$h" 2>/dev/null)
  end=$(echo "$cert" | openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2)
  if [ -z "$end" ]; then
    echo "FAIL $h: couldn't get a certificate"
    fail=1
  elif ! echo "$cert" | openssl x509 -noout -checkend $((DAYS * 86400)) >/dev/null; then
    echo "FAIL $h: expires $end"
    fail=1
  else
    echo "ok   $h: expires $end"
  fi
done
exit $fail
💬 ชาวเน็ตแห่แชร์: posting dude/lets encrypt goes to day certificates in february grep your server for and first lkn - สรุปดราม่าล่าสุด 💬 ชาวเน็ตแห่แชร์: posting dude/lets encrypt goes to day certificates in february grep your server for and first lkn - สรุปดราม่าล่าสุด

-checkend takes seconds and exits 1 if the cert expires inside that window. -servername matters if more than one site lives on the same IP, otherwise you might be checking the wrong cert.

Pick the alert threshold for the new lifetimes. A lot of us have a "warn at 30 days left" rule from the 90-day era. On a 64-day cert renewed at about 2/3, a healthy cert hits 30 days left around day 34, roughly 9 days before renewal even tries. So the alert fires on every healthy cert, you mute it, and then it can't help you when it matters. I'd use 14 days for 64-day certs and something like 7 to 10 once you're on 45-day ones.

Where the alert goes matters as much as the check. The SideHunt guide on monitoring a side project when you can't watch it all day splits alerts into "interrupt me now" and "tonight is fine". A cert with 14 days left is a tonight one. A cert with 2 days left, or a FAIL on the webhook host, is a now one.

And the check script is itself a cron job that can die quietly. If it stops running, "no alerts" looks exactly like "everything's fine". I wrote about heartbeat checks for the jobs a tiny SaaS runs. Same idea here: ping on success so you hear about it when the check itself stops.

Step 5: find the certs you forgot about

The hostnames list in that script is only as good as your memory. Certificate Transparency logs remember better. Every public cert gets logged, so you can see every name anyone got a cert for on your domain:

curl -s "https://crt.sh/?q=example.com&output=json" | jq -r '.[].name_value' | sort -u
💬 ชาวเน็ตแห่แชร์: posting dude/lets encrypt goes to day certificates in february grep your server for and first lkn - สรุปดราม่าล่าสุด 💬 ชาวเน็ตแห่แชร์: posting dude/lets encrypt goes to day certificates in february grep your server for and first lkn - สรุปดราม่าล่าสุด

crt.sh is free and kind of slow, and sometimes it just returns a 502. Retry in a minute. What usually turns up: an old staging. subdomain still pointing at a box, status. on some service you set up once, a mail. cert on the mail server, a customer's custom domain. Anything still live goes into HOSTS. Anything dead, delete the DNS record so it stops being a thing.

While you're thinking about expiry dates, the domain itself expires too, and a cert check won't catch that one. The IndieHunt guide on protecting a small SaaS from account lockouts covers registrar lock, auto-renew and the "renewal emails go to the domain that expired" trap.

Step 6: if anything still renews by hand, fix that now

Two cases I'd look for:

Manual DNS challenges. If you ran certbot certonly --manual with DNS and paste a TXT record each time, certbot's docs are clear that those certs won't renew automatically unless you add auth hook scripts. Today Let's Encrypt reuses a successful validation for 30 days. From February 2027 that's 10 days, and on the 45-day schedule in 2028 it's 7 hours. So basically every renewal will need a fresh challenge. Move to a DNS plugin for your DNS provider, or to HTTP validation if the box is reachable on port 80.

Bought certs. If you pay a registrar for a cert because "that's how we did it in 2019", see whether your host can do ACME instead. A lot of paid CAs support ACME too, if you need a paid one for some reason.

Step 7: try the short certs early, on something that doesn't matter

You don't have to wait for February to find out what breaks. Two options:

  • Staging is already on 64-day certs. Point a throwaway subdomain at a test box, issue with --staging, and watch it over a couple of weeks.
  • Or opt one low-stakes production name into 45-day certs now. In recent certbot that's --preferred-profile tlsserver, which falls back to the default profile if the profile ever disappears. --required-profile fails instead, which you probably don't want on a real site.

If your setup survives 45-day certs, February is a non-event.

The one-hour version

If you only do part of this:

  1. Find what renews each cert, and write it down.
  2. Grep for 60, 80, 83, --force-renewal and renew_before_expiry. Upgrade old certbot and acme.sh.
  3. Make sure a successful renewal reloads whatever serves the cert.
  4. Add the outside check on every hostname, including the webhook host, alerting at 14 days, with a heartbeat on the check.
  5. Run crt.sh once to find hostnames you forgot.

Most of the time nothing is wrong. But "the cert expired" outages are almost always on a box someone set up once and stopped thinking about, and February takes away a lot of the slack those boxes have been living on.

What renews the certs on your server right now, and do you actually know what day it fires? And if anyone has already moved a real site to the 45-day tlsserver profile, did anything break?

Top comments (7)

💬 ชาวเน็ตแห่แชร์: posting dude/lets encrypt goes to day certificates in february grep your server for and first lkn - สรุปดราม่าล่าสุด
 
rushdeploy profile image
Rishabh Mishra •

Useful checklist. The webhook host check is the one I would add first.
A renewed cert on the main site does not help if that endpoint still serves the old one.

💬 ชาวเน็ตแห่แชร์: posting dude/lets encrypt goes to day certificates in february grep your server for and first lkn - สรุปดราม่าล่าสุด
 
posting-dude profile image
Posting Dude •

good point, the webhook host is easy to forget because it sits behind a different hostname or even a different proxy. do you check that kind of endpoint with a scheduled script, or just by hand when a renewal happens?

💬 ชาวเน็ตแห่แชร์: posting dude/lets encrypt goes to day certificates in february grep your server for and first lkn - สรุปดราม่าล่าสุด
 
rushdeploy profile image
Rishabh Mishra •

I check it with a scheduled outside check, not by hand after renewal.
That way the webhook host is in the same list as the main site, and a stale cert shows up before Stripe does.
A manual check is too easy to skip when the renewal itself looked successful.

💬 ชาวเน็ตแห่แชร์: posting dude/lets encrypt goes to day certificates in february grep your server for and first lkn - สรุปดราม่าล่าสุด Thread
 
posting-dude profile image
Posting Dude •

same list for every hostname makes sense, otherwise the webhook host only gets checked when someone remembers it exists. "the renewal looked successful" is the exact trap too, certbot says it renewed and nobody asks whether the proxy in front actually reloaded the new cert. do you alert on days left, or just on the served cert not matching what's on disk?

💬 ชาวเน็ตแห่แชร์: posting dude/lets encrypt goes to day certificates in february grep your server for and first lkn - สรุปดราม่าล่าสุด Thread
 
rushdeploy profile image
Rishabh Mishra •

I alert on the days left on the served cert, so a missed reload still shows up even when renewal worked.
If the served cert differs from the one on disk, that is the clearer signal that the proxy never picked up the new one.

💬 ชาวเน็ตแห่แชร์: posting dude/lets encrypt goes to day certificates in february grep your server for and first lkn - สรุปดราม่าล่าสุด Thread
 
posting-dude profile image
Posting Dude •

Alerting on the served cert and not the file on disk makes sense, that catches the exact case where renewal worked but nobody reloaded the proxy. Do you compare the two with a small script, or just check the expiry from outside the box?

💬 ชาวเน็ตแห่แชร์: posting dude/lets encrypt goes to day certificates in february grep your server for and first lkn - สรุปดราม่าล่าสุด Thread
 
rushdeploy profile image
Rishabh Mishra •

Just the outside check for me. If the served expiry is older than the file on disk, the reload step is the one that failed, and that is the signal I would act on.