ACME clients and automation

How to renew SSL/TLS certificates automatically

By · Last verified

Automatic renewal needs three parts: a scheduler that runs the ACME client at least daily, a hook that reloads every service using the certificate, and an external check that alerts you when renewal fails. The steps cover Certbot, acme.sh, lego, Caddy, Traefik, cert-manager and win-acme as of November 2026.

Prerequisites

You need to know where your certificates are and which tool issued each one.

  • Shell or console access to every server, proxy, cluster and panel that terminates TLS for your domains.
  • The list of hostnames you serve; CT search shows every certificate issued for your domain and is a good way to find forgotten ones.
  • Permission to change cron, systemd timers or scheduled tasks on those systems.

Step 1: Inventory certificates and their clients

Run the listing command for each client you use:

sudo certbot certificates              # Certbot
acme.sh --list                         # acme.sh
sudo lego certificates list --path /etc/lego   # lego v5
kubectl get certificates -A            # cert-manager

Caddy and Traefik manage their own certificates; check their logs or storage. On Windows, win-acme lists its renewals with wacs.exe --list. Anything that does not appear in one of these lists was installed by hand and needs attention.

Step 2: Make sure a scheduler runs at least daily

Each client relies on a different scheduler:

Client Scheduler Check
Certbot (snap) systemd timer, twice a day systemctl list-timers | grep certbot
Certbot (pip) cron line you add grep certbot /etc/crontab
acme.sh user crontab, every 6 hours since 3.1.4 crontab -l | grep acme.sh
lego your own systemd timer or cron systemctl list-timers | grep lego
Caddy, Traefik built into the running process service logs
cert-manager controller in the cluster kubectl get pods -n cert-manager
win-acme Windows scheduled task Task Scheduler, task named win-acme

If a scheduler is missing, add one. For Certbot installed with pip:

echo "0 0,12 * * * root /opt/certbot/bin/python -c 'import random; import time; time.sleep(random.random() * 3600)' && sudo certbot renew -q" | sudo tee -a /etc/crontab > /dev/null

Step 3: Let the client decide when to renew

Prefer ARI and lifetime-based defaults over fixed day counts. Certbot 4.1 and later check ARI during certbot renew and otherwise renew with one third of the lifetime left; acme.sh 3.1.4 and later enable ARI by default; lego v5 uses ARI unless you pass --ari-disable and computes timing from the lifetime when --renew-days is 0. Remove old overrides such as renew_before_expiry = 30 days in Certbot renewal files or hard-coded --days 60 in acme.sh unless you have a specific reason:

sudo grep -H renew_before_expiry /etc/letsencrypt/renewal/*.conf

Step 4: Reload every service that uses the certificate

A renewed file on disk does nothing until the service reloads it. Use the client's hook mechanism:

sudo certbot reconfigure --cert-name example.com --deploy-hook "systemctl reload nginx"
acme.sh --install-cert -d example.com --ecc --fullchain-file /etc/nginx/ssl/example.com.pem --key-file /etc/nginx/ssl/example.com.key --reloadcmd "systemctl reload nginx"

lego takes --deploy-hook; Caddy, Traefik and cert-manager reload automatically. Details are in Certbot renewal hooks.

Verify

A working setup passes a dry run against the staging server, and the live certificate's expiry date moves forward after the next real renewal. Test the renewal path without spending production rate limits:

sudo certbot renew --dry-run

For acme.sh and lego, run against the staging server (--staging, or --server https://acme-staging-v02.api.letsencrypt.org/directory). Then check what clients receive with the SSL checker or from any terminal:

curl ssl.sh/example.com

For many hosts at once, use the bulk SSL checker.

Automatic renewal

Once Steps 2 to 4 are in place, renewal is automatic; what remains is detecting failures. Let's Encrypt stopped sending expiry reminder emails on June 4, 2025, so set up your own alerting: ssl.sh monitoring (in development) checks the live certificate from outside and warns before expiry, which also catches a renewed certificate that was never reloaded. The schedule above already copes with Let's Encrypt's 64-day default (February 10, 2027) and 45-day default (February 16, 2028), and with the industry limits on the certificate validity timeline.

Troubleshooting

  • Renewal fails with timeout during connect: something changed on port 80 since issuance (new firewall, CDN, redirect); see certbot timeout during connect.
  • too many certificates already issued: a broken loop renewed over and over; see Let's Encrypt rate limits. ARI-directed renewals are designed to avoid this.
  • CAA record prevents issuance: a CAA record was added after the first issuance; see CAA record prevents issuance.
  • Site shows an expired certificate although renewal succeeded: the service was not reloaded; see NET::ERR_CERT_DATE_INVALID.
  • Client certificate use broke after renewal: Let's Encrypt certificates no longer include the TLS client authentication EKU; see client auth EKU removed.

FAQ

How often should the renewal job run?

At least once a day, and twice a day is common. The client only contacts the CA for certificates that are due, so frequent checks cost almost nothing. With ARI, a daily check is also how the client notices if the CA asks for an early renewal, for example before a mass revocation.

Is renewing at a fixed number of days before expiry still good enough?

It works today, but fixed values such as 30 days break when lifetimes shrink: a 30-day margin on a 45-day certificate is fine, on a 6-day certificate it is impossible. Let the client compute the timing from the lifetime and ARI instead of hard-coding days.

What about certificates I upload by hand to a CDN or appliance?

Those are the ones that expire. Replace them with provider-managed certificates, use the provider's API from a deploy hook, or at least add them to monitoring. See preparing for 47-day certificates.

Sources

  1. Certbot User Guide: Renewing certificates
  2. Let's Encrypt: Integration Guide
  3. RFC 9773: ACME Renewal Information (ARI) Extension
  4. Let's Encrypt: Ending Support for Expiration Notification Emails
  5. Let's Encrypt: Decreasing Certificate Lifetimes to 45 Days