← Back to Blog

SSL Auto-Renewal: How to Never Let Your Certificate Expire Again

Complete guide to automatic SSL certificate renewal with Let's Encrypt and ACME. Compare Certbot, acme.sh, CertPilot strategies, cron job setup, renewal hooks, monitoring, and troubleshooting expired certificates.

SSL auto-renewalLet's EncryptACMEcertificate renewalcron jobCertbotacme.shHTTPS automation

SSL Auto-Renewal: How to Never Let Your Certificate Expire Again

An expired SSL certificate is one of the fastest ways to lose visitors, trust, and revenue. The browser shows a terrifying red "Not Secure" warning, users bounce, and if you run an e-commerce site, transactions stop cold.

According to Venafi's 2025 report, over 60% of certificate-related outages are caused by expired certificates — not security breaches, not misconfigurations — just expiration that someone forgot to handle.

The good news? Let's Encrypt certificates are designed for automation. With a properly configured auto-renewal setup, you can literally set it once and forget it forever.

This guide covers everything: how Let's Encrypt renewal works, the best ACME clients and their renewal strategies, cron job setup, renewal hooks, monitoring, and what to do when renewal fails.

How Let's Encrypt Certificate Renewal Works

Let's Encrypt certificates have a 90-day validity period — intentionally short. This isn't a limitation; it's a feature. Short-lived certificates force automation into the workflow, reducing the risk of key compromise and eliminating the "set-and-forget-but-forget-to-renew" problem.

The ACME Renewal Protocol

When a certificate is issued via ACME, the client stores:

  • The certificate file (.pem or .crt)
  • The private key (.key)
  • The full certificate chain (including intermediates)
  • An ACME account key (used to authenticate renewal requests)

Renewal works through ACME's renewal logic (defined in RFC 8555):

  1. The ACME client checks the certificate's expiration date
  2. If within the renewal window (typically 30 days before expiry), it initiates renewal
  3. The client re-authenticates using the same ACME account key
  4. A new certificate is issued for the same domains
  5. The web server reloads the new certificate — optionally via a deploy hook

This process is defined in the ACME protocol (RFC 8555), which means every compliant ACME client follows the same pattern.

When Does Renewal Trigger?

Different ACME clients use different thresholds:

ACME Client Renewal Window Default Check Frequency
Certbot 30 days before expiry Twice daily (cron)
acme.sh 60 days before expiry Daily (cron)
CertPilot 30 days before expiry On-demand (dashboard)
Caddy 30 days before expiry Continuous (built-in)
Traefik 30 days before expiry Continuous (built-in)
lego Customizable (default 30 days) Per invocation

The key insight: as long as your ACME client runs at least once during the renewal window, your certificate will be renewed automatically. Missing a single check isn't catastrophic — you have a 30-day buffer.

Setting Up Auto-Renewal with Different ACME Clients

Certbot (Standard & Reliable)

Certbot is the original Let's Encrypt client, maintained by the EFF. It's the most widely documented option.

# Install Certbot
sudo apt install certbot

# Obtain a certificate
sudo certbot certonly --standalone -d example.com -d www.example.com

# Test renewal (dry-run)
sudo certbot renew --dry-run

# Renew all expiring certificates
sudo certbot renew

Cron job — Certbot installs a systemd timer by default on modern systems, but you can also use cron:

# /etc/crontab or sudo crontab -e
0 3 * * * /usr/bin/certbot renew --quiet --post-hook "systemctl reload nginx"

The --post-hook runs a command only when renewal actually happens (not on every cron run).

acme.sh (Flexible & Lightweight)

acme.sh is a pure shell ACME client — no dependencies, works anywhere, supports 100+ DNS providers for DNS-01 validation.

# Install
curl https://get.acme.sh | sh

# Issue a certificate with DNS-01 (Cloudflare example)
export CF_Token="your_cloudflare_api_token"
acme.sh --issue --dns dns_cf -d example.com -d '*.example.com'

# Install to a specific location
acme.sh --install-cert -d example.com \
  --key-file /etc/nginx/ssl/example.com.key \
  --fullchain-file /etc/nginx/ssl/example.com.crt \
  --reloadcmd "systemctl reload nginx"

acme.sh installs its own cron job automatically during setup. You can verify with:

crontab -l | grep acme.sh
# output: 0 0 * * * /root/.acme.sh/acme.sh --cron --home /root/.acme.sh > /dev/null

CertPilot (Dashboard-Based Management)

CertPilot provides a web dashboard for managing your Let's Encrypt certificates:

  1. Add your domain on the CertPilot dashboard
  2. Complete DNS-01 validation by adding the TXT record to your DNS provider
  3. Download the certificate files once issued
  4. Get expiry reminders — CertPilot alerts you before certificates expire, with one-click renewal

Best for: Teams managing multiple domains who want centralized visibility into certificate status and expiry dates.

Comparison: Which ACME Client Should You Use?

Feature Certbot acme.sh Caddy/Traefik CertPilot
Installation apt install `curl sh` Built-in
DNS-01 support ⚠️ Plugin required ✅ 100+ providers ⚠️ Limited ⚠️ Manual TXT record
Wildcard certs ⚠️ Plugin required ✅ Native ✅ Built-in ⚠️ Manual DNS-01
Renewal cron systemd timer / cron Auto-installed Built-in Manual (expiry reminders)
Post-renewal hook --post-hook --reloadcmd Auto-reload Manual deploy
Monitoring ❌ None ❌ None ❌ None ✅ Dashboard + alerts
Learning curve Moderate Moderate Low Very low
Server footprint 50 MB+ < 5 MB Depends 0 MB (SaaS)
Best for Single-server LAMP Any server, pro Containerized Multi-domain visibility

The Critical Piece: Renewal Hooks

A renewed certificate on disk does nothing by itself. You must reload the web server to pick up the new certificate. This is where renewal hooks come in.

Common Hook Commands

# Nginx
systemctl reload nginx        # or: nginx -s reload

# Apache
systemctl reload apache2      # or: apachectl graceful

# HAProxy
systemctl reload haproxy      # or: haproxy -f /etc/haproxy/haproxy.cfg -sf

# Caddy
# Caddy reloads automatically (no hook needed)

# Custom (e.g., copy to load balancer)
cp /etc/letsencrypt/live/example.com/fullchain.pem /opt/services/tls/
cp /etc/letsencrypt/live/example.com/privkey.pem /opt/services/tls/
systemctl reload my-service

Testing Your Hook

Always test with a dry-run:

certbot renew --dry-run --post-hook "echo 'Hook would run: reload nginx'"

This verifies renewal logic without actually requesting a new certificate.

Monitoring Your Renewals

Auto-renewal can fail silently. DNS API tokens expire, server clocks drift, network changes, and ACME endpoints change. Here's how to stay ahead.

Check Certificate Expiry from CLI

# Check when a cert expires
echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null \
  | openssl x509 -noout -dates

# Check all certs in a directory
for cert in /etc/letsencrypt/live/*/fullchain.pem; do
  echo "$(openssl x509 -in "$cert" -noout -enddate) — $cert"
done

Log-Based Monitoring

Parse your ACME client logs for renewal failures:

# Certbot logs are in /var/log/letsencrypt/
grep -i "fail\|error\|expir" /var/log/letsencrypt/letsencrypt.log

# acme.sh logs
tail -f /root/.acme.sh/acme.sh.log

External Monitoring Tools

Tool Features Price
CertPilot Dashboard, expiry alerts, multi-domain management Free
CheckSSLTLS External SSL checker, email alerts Free tier
SSL Labs Deep certificate analysis, grade scoring Free
UptimeRobot SSL expiry monitoring included in uptime checks Free tier
Datadog Certificate expiry metrics + alerting (requires agent setup) Paid

CertPilot's monitoring dashboard shows all your certificates in one place with real-time expiry status and proactive email alerts when a certificate is approaching expiry.

Common Auto-Renewal Failures (and Fixes)

1. DNS API Token Expired

DNS-01-based renewal fails if your DNS provider's API token has expired or been revoked.

Fix: Check your token validity dates. Set a calendar reminder to rotate tokens before they expire.

2. Rate Limiting

Let's Encrypt has rate limits:

  • 50 certificates per registered domain per week
  • 5 failed authorizations per domain per hour
  • 10 certificates per domain per hour (combined issuance + renewal)

Fix: If you hit limits, the renewal will succeed the next day. This is rarely an issue for normal use.

3. Domain DNS Changed

If you moved your domain to a new DNS provider but didn't update your ACME client configuration, DNS-01 validation will fail.

Fix: Update your ACME client with the new DNS provider credentials. Check dig queries resolve correctly.

4. Expired ACME Account Key

Your ACME account key (stored locally) is used to authenticate renewal requests. If it's lost or corrupted:

Fix: Register a new ACME account with Let's Encrypt. You may need to re-issue certificates (they'll be signed by the new account).

5. Server Clock Drift

NTP failure can cause certificate validation errors — both for your server connecting to Let's Encrypt and for clients connecting to your server.

Fix: Ensure chronyd or ntpd is running:

timedatectl status
sudo systemctl enable --now chronyd

Best Practices for Bulletproof Auto-Renewal

Double Your Cron

Run your ACME client twice daily, not once. If one run fails due to a transient network issue, the next one (12 hours later) will catch it. With a 30-day renewal window, you have ~60 opportunities.

# Run at 3:00 AM and 3:00 PM
0 3,15 * * * /usr/bin/certbot renew --quiet --post-hook "systemctl reload nginx"

Separate Monitoring from Renewal

Don't rely on your ACME client to tell you it failed. Set up independent monitoring:

# Simple curl-based check
curl -I https://example.com 2>/dev/null | head -n 1

# Check cert expiry from outside (run on a separate machine)
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
  | openssl x509 -noout -checkend 604800  # warn if expiring in 7 days

Keep Your Renewal Infrastructure Simple

  • One ACME client per machine
  • One cron job (or systemd timer) per ACME client
  • One post-renewal hook per web server
  • One external monitoring source

Complexity is the enemy of reliability. The more moving parts, the more things break.

Automate DNS Credential Rotation

If using DNS-01 with API tokens:

# Example: rotate Cloudflare token monthly
# Use cert-manager or HashiCorp Vault for automated rotation

Use Staging for Testing

Let's Encrypt's staging environment has much higher rate limits. Test your renewal setup against staging first:

certbot certonly --staging --standalone -d example.com
certbot renew --dry-run --staging

Why 90-Day Certificates Are Better Than 1-Year Or 2-Year

This is worth emphasizing. Many administrators instinctively prefer longer validity periods, but the opposite is true for modern certificate management:

Factor 90-Day (Let's Encrypt) 1-Year (Paid SSL) 2-Year (Paid SSL)
Compromise window 90 days max 1 year max 2 years max
Rotation habit Automated, frequent Manual, annual reminder Easy to forget
Key rotation Every 90 days Once a year Once every 2 years
Failure impact Minutes to fix (auto) Days to notice + fix Days to notice + fix
Revocation cost Low (auto-reissue) High (manual re-buy) High (manual re-buy)

Short lifetimes force good security hygiene. The automation infrastructure you build for 90-day certificates creates a self-healing system that keeps your TLS posture healthy without manual intervention.

Putting It All Together: A Complete Renewal Setup

Here's what bulletproof auto-renewal looks like in practice:

For Single-Server Deployments

  1. Use Certbot with systemd timer (default on Ubuntu 24.04+)
  2. Add a post-hook to reload Nginx or Apache
  3. Set up CertPilot to monitor certificates from outside
  4. Add a cron job for additional email alerting:
# Weekly cert expiry check
0 9 * * 1 /usr/local/bin/check-certs.sh

Where check-certs.sh contains:

#!/bin/bash
DOMAINS=("example.com" "www.example.com")
for domain in "${DOMAINS[@]}"; do
  if ! openssl s_client -connect "$domain:443" -servername "$domain" </dev/null 2>/dev/null \
    | openssl x509 -noout -checkend 604800; then
    echo "Certificate for $domain expires within 7 days!" | mail -s "SSL Expiry Warning" admin@example.com
  fi
done

For Multi-Server / Multi-Domain Deployments

  1. Use a centralized ACME client like acme.sh with DNS-01
  2. Connect all DNS providers through your ACME client's DNS plugin
  3. Distribute certificates to each server via SCP, Ansible, or configuration management
  4. Use CertPilot to monitor certificate status across all domains
  5. Review the dashboard monthly and rotate DNS credentials quarterly

For Kubernetes / Container Deployments

  1. Use cert-manager (Kubernetes-native ACME client)
  2. Configure ClusterIssuer with Let's Encrypt production endpoint
  3. Use DNS-01 with a supported provider
  4. cert-manager handles renewal automatically — renews certificates and updates Secrets
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      name: letsencrypt-account-key
    solvers:
    - dns01:
        cloudflare:
          apiTokenSecretRef:
            name: cloudflare-api-token
            key: api-token

The Bottom Line

SSL auto-renewal isn't optional — it's essential. Let's Encrypt's 90-day certificate lifetime is designed around automation, and every major ACME client supports hands-off renewal. The only thing standing between you and an expired certificate is a properly configured cron job and a renewal hook.

For a single server, Certbot + cron + a reload hook takes 10 minutes to set up. For multi-domain infrastructure, CertPilot provides centralized visibility into your certificate portfolio with expiry monitoring and alerts.

Whichever route you choose, set up your renewal infrastructure today. An expired certificate is one of the cheapest outages to prevent — and one of the most expensive ones to fix after the fact.

Related Articles