How Three WooCommerce Stores Went Down Overnight Because Certbot Stopped Renewing

· 8 min read

The call came at 6:14am. A client's WooCommerce store was showing "Your connection is not private" to every visitor. No orders had come through since midnight. By the time I'd made coffee, I'd found two more stores on the same server with the exact same problem.

All three had Let's Encrypt certificates. All three had expired within hours of each other. And the certbot auto-renewal that should have prevented this hadn't run in 93 days.

The symptom

Three WooCommerce stores on the same VPS, all serving ERR_CERT_DATE_INVALID. Browsers blocked access entirely — no "proceed anyway" option on sites with HSTS headers. Stripe and PayPal both refuse to process payments without valid TLS, so checkout was dead.

The sites were still technically running. WordPress was serving pages fine. But without a valid certificate, every visitor saw a full-screen browser warning instead of the shop. Orders dropped to zero the moment the certificates expired.

The investigation

I SSH'd into the server and checked the certificate:

openssl x509 -in /etc/letsencrypt/live/store-one.com/cert.pem -noout -dates
notBefore=Feb 14 09:22:41 2026 GMT
notAfter=May 15 09:22:40 2026 GMT

Expired at 09:22 UTC that morning. The other two stores showed near-identical timestamps — all issued on the same day, all expired on the same day.

I checked whether certbot had been attempting renewals:

sudo certbot certificates

All three listed as expired. No recent renewal attempts in the logs:

journalctl -u certbot.timer --since "3 months ago" | head -20

Nothing. The systemd timer wasn't running:

systemctl status certbot.timer
● certbot.timer
     Loaded: loaded (/lib/systemd/system/certbot.timer; disabled; vendor preset: enabled)
     Active: inactive (dead)

disabled and inactive. The timer had never been started after the last server migration.

The root cause

This VPS was rebuilt three months earlier during a migration from an older server. Certbot was installed fresh, certificates were issued for all domains, the sites loaded over HTTPS, and the migration was marked as complete.

But nobody enabled the renewal timer.

The subtle problem: the old server used certbot-auto (the standalone script). The new server installed certbot via snap, which manages renewal through its own timer (snap.certbot.renew.timer) rather than the system-level certbot.timer. Neither timer was active:

systemctl list-timers | grep -i cert

Empty output. No timer of any kind was running for certificate renewal. The certificates renewed fine during the migration because they were issued manually. But the automated renewal that keeps them alive never started.

With 90-day Let's Encrypt certificates, this meant exactly 90 days of apparent normality before everything broke at once.

The fix

Step 1: Renew immediately

sudo certbot renew --force-renewal

All three certificates renewed. Then reload nginx to serve the new certificates:

sudo nginx -t && sudo systemctl reload nginx

Verified each site in the browser — padlock restored, checkout functional, test payment through Stripe successful.

Step 2: Enable auto-renewal

Since certbot was installed via snap, I enabled the snap-managed timer:

sudo systemctl enable --now snap.certbot.renew.timer
systemctl list-timers | grep certbot
NEXT                        LEFT     LAST PASSED UNIT
Thu 2026-05-15 18:30:00 UTC  8h left -    -      snap.certbot.renew.timer

Timer active and scheduled.

Step 3: Add the nginx reload hook

Certbot renews certificate files on disk, but nginx keeps serving the old certificate from memory until it's reloaded. Without a deploy hook, a successful renewal still leaves the site serving an expired certificate.

sudo mkdir -p /etc/letsencrypt/renewal-hooks/deploy
cat << 'HOOK' | sudo tee /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
#!/bin/bash
/usr/sbin/nginx -t && /usr/bin/systemctl reload nginx
HOOK
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh

Step 4: Verify the full pipeline

sudo certbot renew --dry-run
Congratulations, all simulated renewals succeeded:
  /etc/letsencrypt/live/store-one.com/fullchain.pem (success)
  /etc/letsencrypt/live/store-two.com/fullchain.pem (success)
  /etc/letsencrypt/live/store-three.com/fullchain.pem (success)

Checking every other domain on the server

Three stores were down, but there were 11 other sites on this VPS. I checked them all:

for cert_dir in /etc/letsencrypt/live/*/; do
  domain=$(basename "$cert_dir")
  expiry=$(openssl x509 -in "${cert_dir}cert.pem" -noout -enddate 2>/dev/null \
    | cut -d= -f2)
  echo "$domain: $expiry"
done

Eight more domains had been due to expire within the next 48 hours. The forced renewal caught them, but without that audit, I'd have been back on the phone the next morning.

What I monitor now

I added SSL certificate expiry checks to every server I manage. Uptime Kuma, which I already run for HTTP monitoring, has a built-in certificate expiry check that alerts at a configurable threshold. I set it to 14 days — plenty of time to investigate if auto-renewal has broken.

For a quick manual audit across all sites on a server, I use this script:

#!/bin/bash
WARN_DAYS=14

for cert_dir in /etc/letsencrypt/live/*/; do
  domain=$(basename "$cert_dir")
  expiry_epoch=$(openssl x509 -in "${cert_dir}cert.pem" -noout -enddate 2>/dev/null \
    | cut -d= -f2 | xargs -I{} date -d {} +%s)
  now_epoch=$(date +%s)
  days_left=$(( (expiry_epoch - now_epoch) / 86400 ))

  if [ "$days_left" -lt "$WARN_DAYS" ]; then
    echo "WARNING: $domain expires in $days_left days"
  else
    echo "OK: $domain ($days_left days remaining)"
  fi
done

I run this weekly via cron on every VPS and pipe the output to a Healthchecks.io ping. If the script doesn't report in, something is wrong with the server itself. If it reports warnings, I get a notification before anything expires.

Why this is about to get worse

Let's Encrypt is shortening certificate lifetimes:

  • May 2026: Optional 45-day certificates via the tlsserver ACME profile
  • February 2027: Default lifetime drops to 64 days for all certificates
  • February 2028: All certificates move to 45 days

With 90-day certificates, a broken renewal gives you roughly 60 days of grace (certbot attempts renewal at the 30-day mark). With 45-day certificates, that window shrinks to about 15 days. A renewal timer that dies during a server update, a snap refresh, or a migration will cause expiry much faster.

If your certbot is older than version 4.x, upgrade now. Certbot 4.1.0 added support for ACME Renewal Information (ARI), which lets the CA tell your client exactly when to renew rather than relying on a fixed window:

certbot --version
sudo snap refresh certbot

The lesson

SSL certificate renewal is invisible infrastructure. When it works, nobody thinks about it. When it breaks, everything breaks — not with a helpful error message, but with a browser warning that makes your store look compromised.

For a WooCommerce store, expired SSL doesn't just show a warning. Payment gateways refuse to process. HSTS-enabled sites become completely inaccessible. Search engines flag pages that flip between HTTPS and HTTP. The damage compounds every hour.

Every server migration, OS upgrade, or control panel change is an opportunity for the renewal timer to vanish. I now add "verify certbot timer is active" and "run certbot renew --dry-run" to every post-migration checklist.

If you're running WooCommerce on a VPS and managing your own certificates, this is exactly the kind of silent failure that a server management plan catches before it costs you a day's revenue. Certificate monitoring is part of the standard setup on every server I manage — five minutes to configure, days of firefighting avoided.

Stop Firefighting. Start Maintaining.

I manage 70+ WordPress sites for agencies and businesses. Whether you need ongoing maintenance, emergency support, or a one-off performance fix — I can help.

View Maintenance Plans Get in Touch

Stop Firefighting. Start Maintaining.

I manage 70+ WordPress sites for agencies and businesses. Whether you need ongoing maintenance, emergency support, or a one-off performance fix — I can help.

View Maintenance Plans Get in Touch

Get in Touch to Discuss Your Needs