# Renew SSL certificates automatically: Certbot, acme.sh, lego

> Set up and check automatic renewal for Certbot, acme.sh, lego, Caddy, Traefik, cert-manager and win-acme: schedules, ARI, hooks, monitoring.

Source: https://ssl.sh/guides/renew-certificates-automatically/
Last updated: 2026-11-10

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](https://ssl.sh/tools/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:

```bash
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:

```bash
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:

```bash
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:

```bash
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](https://ssl.sh/guides/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:

```bash
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](https://ssl.sh/tools/ssl-checker/) or from any terminal:

```bash
curl ssl.sh/example.com
```

For many hosts at once, use the [bulk SSL checker](https://ssl.sh/tools/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](https://ssl.sh/monitor/) (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](https://ssl.sh/timeline/certificate-validity/).

## Troubleshooting

- **Renewal fails with timeout during connect**: something changed on port 80 since issuance (new firewall, CDN, redirect); see [certbot timeout during connect](https://ssl.sh/errors/certbot-timeout-during-connect/).
- **too many certificates already issued**: a broken loop renewed over and over; see [Let's Encrypt rate limits](https://ssl.sh/errors/lets-encrypt-rate-limit/). 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](https://ssl.sh/errors/caa-record-prevents-issuance/).
- **Site shows an expired certificate although renewal succeeded**: the service was not reloaded; see [NET::ERR_CERT_DATE_INVALID](https://ssl.sh/errors/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](https://ssl.sh/errors/client-auth-eku-removed/).
