Back to Learning Center
DevOps & Automation

How to Automate SSL Certificate Renewal: The Complete Guide

14 min read·Updated October 2026·By CertNotify Team

Manual SSL certificate renewal is one of the leading causes of preventable outages. In 2026, with Let's Encrypt issuing free 90-day certificates and cloud providers offering managed TLS, there is no excuse for a manual renewal workflow. This guide covers every major approach — from Certbot on Linux servers to Kubernetes cert-manager and cloud-managed certificates.

The Cost of Manual Renewal — An expired SSL certificate immediately blocks all traffic to your site with a browser security warning. Search engines de-index HTTPS-only content. E-commerce loses all transactions. Every minute of downtime compounds trust damage.

Understanding the ACME Protocol

Automated Certificate Management Environment (ACME) is the protocol that powers automated certificate issuance. Originally created by Let's Encrypt, ACME is now an IETF standard (RFC 8555). It works through domain validation challenges — the CA proves you control the domain before issuing a certificate.

There are three challenge types:

HTTP-01

CA checks a specific file at http://yourdomain.com/.well-known/acme-challenge/. Requires port 80 to be open. Simplest method for single servers.

DNS-01

CA checks a TXT record in your DNS. Works behind firewalls, supports wildcard certificates. Requires DNS API access for automation.

TLS-ALPN-01

CA validates via a special TLS handshake. Requires port 443. Useful when both 80 and DNS APIs are unavailable.

Which Method Fits Your Setup

The right tool depends less on preference than on where TLS terminates — the process that actually presents the certificate to visitors. Automate renewal there, and nowhere else.

If TLS terminates at…Use
Nginx or Apache on a Linux serverCertbot with the matching plugin (Method 1)
Many subdomains, or a server port 80 cannot reachA wildcard or DNS-01 certificate (Method 2)
A Kubernetes Ingresscert-manager (Method 3)
A cloud load balancer, CDN or hosting platformThe provider's managed certificate (Method 4)
IIS on Windowswin-acme (Method 5)
Caddy, Traefik or another ACME-aware proxyThe proxy's built-in ACME support (Method 6)
An internal service using Active Directory certificatesAD CS autoenrollment (Method 7)

Method 1: Certbot on Linux (Nginx/Apache)

Certbot is the official Let's Encrypt client. It handles certificate issuance, server configuration, and renewal automatically.

# Install Certbot (Ubuntu/Debian)

sudo apt update
sudo apt install certbot python3-certbot-nginx

# Issue certificate for your domain
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com

# Certbot automatically configures Nginx and adds renewal cron
# Verify auto-renewal works:
sudo certbot renew --dry-run

Certbot installs a systemd timer (or cron job on older systems) that runs twice daily and renews certificates when they have less than 30 days remaining. For Apache, replace --nginx with --apache.

Check that Certbot auto-renew is actually set up

"Certbot renews automatically" is true only if its scheduler is installed and running, and which scheduler you have depends on how Certbot was installed. These three commands answer the questions that matter: is anything scheduled, which certificates does Certbot manage, and how will it renew each one.

# Is a renewal job scheduled? The snap installs snap.certbot.renew.timer,
# Debian and Ubuntu packages install certbot.timer
systemctl list-timers | grep -i certbot

# Older packages use cron instead
ls /etc/cron.d/certbot

# Every certificate Certbot manages, with its expiry date
sudo certbot certificates

Certbot remembers how each certificate was issued in a file under /etc/letsencrypt/renewal/, one per certificate, and certbot renew replays exactly what that file says:

# /etc/letsencrypt/renewal/yourdomain.com.conf (excerpt)
# renew_before_expiry = 30 days
[renewalparams]
authenticator = nginx
installer = nginx
server = https://acme-v02.api.letsencrypt.org/directory

This file explains a whole class of renewals that fail although the first issuance worked. A certificate obtained with --standalone renews with standalone too, which means Certbot tries to bind port 80 itself — and fails once Nginx is listening there. Rather than editing the file by hand, issue the certificate once more with the method you want, for example certbot certonly --webroot -w /var/www/html --cert-name yourdomain.com -d yourdomain.com, and Certbot rewrites the renewal file to match. Then confirm with certbot renew --dry-run.

If a server still runs certbot-auto, replace it. That wrapper script was deprecated in 2020 and no longer maintained; remove it and any cron entry that calls it, install Certbot from the snap (sudo snap install --classic certbot) or your distribution's packages, and run the checks above. The existing files in /etc/letsencrypt carry over unchanged. When a renewal does fail, the Let's Encrypt renewal troubleshooting guide walks through each Certbot error in turn.

Method 2: Wildcard Certificates with DNS Challenge

Wildcard certificates (*.yourdomain.com) cover all subdomains but require DNS-01 challenge — meaning your DNS provider must have an API that Certbot can use to create TXT records automatically.

# Example: Wildcard cert with Cloudflare DNS plugin

pip install certbot-dns-cloudflare

# Create credentials file
cat > ~/.secrets/cloudflare.ini << EOF
dns_cloudflare_api_token = YOUR_CLOUDFLARE_API_TOKEN
EOF
chmod 600 ~/.secrets/cloudflare.ini

# Issue wildcard certificate
certbot certonly \
  --dns-cloudflare \
  --dns-cloudflare-credentials ~/.secrets/cloudflare.ini \
  -d "*.yourdomain.com" \
  -d "yourdomain.com"

DNS plugins exist for Route53, Cloudflare, Namecheap, GoDaddy, DigitalOcean, and most major DNS providers. Check the Certbot DNS plugins documentation for the full list.

Method 3: cert-manager on Kubernetes

For Kubernetes deployments, cert-manager is the standard tool. It integrates with Ingress controllers (nginx-ingress, Traefik, etc.) to automatically provision and renew certificates for all Ingress resources.

# Install cert-manager

kubectl apply -f https://github.com/cert-manager/cert-manager/releases/latest/download/cert-manager.yaml

# Create a ClusterIssuer for Let's Encrypt
cat <<EOF | kubectl apply -f -
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: your-email@domain.com
    privateKeySecretRef:
      name: letsencrypt-prod
    solvers:
    - http01:
        ingress:
          class: nginx
EOF

Then annotate your Ingress resource with cert-manager.io/cluster-issuer: letsencrypt-prod and cert-manager handles the rest — issuance, storage as Kubernetes Secrets, and automatic renewal.

Method 4: Cloud-Managed Certificates

The simplest approach for cloud deployments is using your cloud provider's managed certificate service. These renew automatically and require zero ongoing maintenance:

PlatformServiceCostNotes
AWSAWS Certificate ManagerFree for ALB/CloudFrontFully managed, auto-renewal
GCPGoogle-managed SSLFreeWorks with Load Balancers
AzureApp Service CertificatesFrom $69/yearOV available
CloudflareEdge CertificatesFree with any planEdge-only, origin cert separate
Vercel / NetlifyAuto TLSFreeAutomatic for all custom domains

Method 5: Windows and IIS

Certbot is Linux-first, so the Windows equivalent is win-acme. It issues the certificate, installs it into the Windows certificate store, binds it to the right IIS site, and registers a scheduled task for renewal — all in one interactive run.

# Interactive: pick the IIS binding and win-acme does the rest

wacs.exe

# Or unattended, for one site:
wacs.exe --target iis --siteid 1 --installation iis --accepttos --emailaddress you@yourdomain.com

# Check the scheduled task exists and has run:
wacs.exe --list
Get-ScheduledTask -TaskName "win-acme*" | Get-ScheduledTaskInfo

The failure specific to Windows is the private key permission: the renewed certificate lands in the store but the application pool identity cannot read its key, so IIS serves nothing or falls back to a default binding. If a renewal reports success and the site breaks at the same moment, check the key permissions before suspecting the certificate.

Method 6: Docker, Caddy and Traefik

Reverse proxies now do this without being asked. Caddy issues and renews certificates automatically for any hostname in its config — the hostname itself is the entire configuration.

# Caddyfile — this is the whole thing
yourdomain.com {
  reverse_proxy localhost:3000
}

Traefik needs a certificate resolver and a label on each service. Both store their ACME state in a JSON file that has to sit on a persistent volume: leave it inside the container layer and every restart re-issues from scratch, which is the quickest route to a rate limit.

# traefik.yml
certificatesResolvers:
  le:
    acme:
      email: you@yourdomain.com
      storage: /letsencrypt/acme.json   # must be a mounted volume
      httpChallenge:
        entryPoint: web

That file must also be mode 600 or Traefik refuses to start — a deliberate check, because it holds your ACME account key.

Method 7: Active Directory Certificates and SQL Server

Internal certificates issued by Active Directory Certificate Services never touch ACME, but they expire just the same, and they are the ones most likely to be tracked in someone's calendar. AD CS has its own automation: autoenrollment. It needs two things in place. The certificate template must grant the computers or users that should receive it Read, Enroll and Autoenroll permissions. And a Group Policy setting must switch the client on: under Computer Configuration → Policies → Windows Settings → Security Settings → Public Key Policies, set Certificate Services Client – Auto-Enrollment to Enabled and tick both boxes, for renewing expired certificates and for updating certificates that use templates.

Clients then renew on their own once a certificate enters the template's renewal period, which defaults to six weeks before expiry. To test without waiting:

# Apply policy and trigger autoenrollment now
gpupdate /force
certutil -pulse

# Certificates in the machine store expiring in the next 30 days
Get-ChildItem Cert:\LocalMachine\My |
  Where-Object NotAfter -lt (Get-Date).AddDays(30) |
  Select-Object Subject, NotAfter, Thumbprint

Getting a fresh certificate into the store is only half the job for applications that pin a specific one. SQL Server is the usual example: its TLS certificate is configured by thumbprint, and a renewed certificate has a new thumbprint, so the instance carries on presenting the old one until the configuration is updated and the service restarted. The SQL Server service account also needs read permission on the new private key, or the service may refuse to start. Whatever issues the certificate — autoenrollment, win-acme's script step, or a scheduled task — the automation has to include those last two steps, and the check afterwards should connect to the instance and look at the certificate it presents, not at the store.

When renewal fails: reading the error

Renewal failures stop being mysterious once you have seen each one. The log lives at /var/log/letsencrypt/letsencrypt.log, and five messages account for most of what ever appears in it.

MessageWhat actually happened
Invalid response ... 404The HTTP-01 challenge file was never served. Usually a redirect: forcing every request to HTTPS, or to a canonical host, breaks the challenge path unless it is excluded from the rule. The Redirect Checker shows where a request for the challenge URL actually ends up.
Timeout during connectPort 80 is closed inbound. HTTP-01 validation begins on port 80 and follows redirects from there; it cannot start on 443.
DNS problem: NXDOMAINThe name no longer resolves. Common after a migration where a record was renamed and the renewal config still lists the old one.
DNS problem: SERVFAILOften a broken DNSSEC chain rather than a missing record — the zone is signed, the DS record no longer matches, and validating resolvers refuse the answer. The DNSSEC Checker shows which link in the chain is broken.
acme:error:rateLimitedToo many attempts. See below; this is the one that turns an afternoon into a week.

A wildcard certificate cannot produce the 404 case at all, because DNS-01 never serves a file. When a wildcard renewal fails it is nearly always the DNS API behind it: an expired token, a permission scope that does not cover TXT records, or a provider slow enough to publish that the client gives up before the record becomes visible. Asking your authoritative nameserver for the challenge record while a renewal runs — dig TXT _acme-challenge.yourdomain.com @ns1.your-dns-host.net +norecurse — shows whether it has been published yet, and if your provider is simply slow, each Certbot DNS plugin takes a --dns-<plugin>-propagation-seconds option, such as --dns-cloudflare-propagation-seconds 60, to wait longer before asking for validation.

The renewal worked and the old certificate is still being served

This is the failure monitoring exists for, because every local check says everything is fine. certbot renew writes new files into /etc/letsencrypt/live/, but Nginx, Apache and HAProxy each read the certificate once at startup and hold it in memory. Until the process is reloaded it keeps presenting the old one, while the files on disk look perfect.

A deploy hook fixes it, and runs only when a certificate was actually replaced:

certbot renew --deploy-hook "systemctl reload nginx"

# Or permanently, for every certificate on the host:
echo 'systemctl reload nginx' | sudo tee /etc/letsencrypt/renewal-hooks/deploy/reload.sh
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload.sh

Use reload rather than restart: a reload keeps established connections alive, and a restart in the middle of a renewal window drops them.

Then verify against what is being served, not what is on disk. These two can disagree for months, and only the second is what a visitor sees:

# What the file says
openssl x509 -enddate -noout -in /etc/letsencrypt/live/yourdomain.com/fullchain.pem

# What the server is actually presenting
openssl s_client -connect yourdomain.com:443 -servername yourdomain.com -brief

Rate limits turn a small failure into a week-long outage

Let's Encrypt limits how often you may ask. The allowances are generous for normal use and unforgiving once something is looping: a cron retrying a misconfiguration every few hours will exhaust its budget and then be unable to issue at all, including after the original fault is fixed. The two that bite are the failed-validation limit per hostname per hour and the duplicate-certificate limit for an identical set of names. Check the current rate limits rather than trusting a number you read somewhere — they have been revised more than once.

Two habits avoid the problem entirely: rehearse against the staging endpoint, which has far looser limits and issues an untrusted certificate you could never deploy by accident, and stop a failing renewal rather than leaving it to retry into the ceiling.

# Rehearse — does not count against production limits
certbot renew --dry-run

# Issue from the staging CA explicitly
certbot certonly --server https://acme-staging-v02.api.letsencrypt.org/directory -d yourdomain.com

Shorter certificate lifetimes make manual renewal untenable

Automation used to be the sensible choice; it is becoming the only workable one. Under CA/Browser Forum ballot SC-081, the maximum validity of a publicly trusted TLS certificate falls in steps: 200 days for certificates issued from 15 March 2026, 100 days from 15 March 2027, and 47 days from 15 March 2029. A certificate bought from a commercial CA and installed by hand once a year now has to be replaced at least twice a year, and from 2029 roughly eight times a year, on every server that uses it.

Most commercial CAs now offer ACME as well, so the same clients described above work with paid certificates. They authenticate your ACME account to your CA account with External Account Binding: in Certbot that is --server with the CA's ACME directory URL, plus --eab-kid and --eab-hmac-key with the credentials from the CA's dashboard. It is also worth checking whether your client supports ACME Renewal Information (ARI), which lets the CA tell clients when to renew instead of relying on a fixed 30-day threshold — useful if a certificate ever has to be replaced early.

Monitor the certificate being served, not the renewal job

Every failure on this page has the same shape: the renewal job reports success, or reports nothing, while visitors keep receiving a certificate that is running out. So the check that matters connects to the site the way a browser does and reads the expiry date of whatever comes back. A few lines of shell do it, and exit non-zero so cron or any scheduler can raise an alert:

#!/bin/sh
# Warn when the certificate a server presents expires within 21 days
host="${1:-yourdomain.com}"
if ! echo | openssl s_client -connect "$host:443" -servername "$host" 2>/dev/null \
    | openssl x509 -noout -checkend $((21 * 24 * 3600)) >/dev/null; then
  echo "Certificate for $host expires within 21 days, or could not be read" >&2
  exit 1
fi

Pick a threshold below the point at which your client renews — 30 days before expiry for Certbot with 90-day certificates — so that an alert means renewal has already been failing for over a week rather than simply not having run yet. Run it from outside the server: a check on the machine itself shares its DNS, its firewall and its fate. For a one-off look at any certificate's dates, issuer and names, paste the PEM into the Certificate Decoder, and for a broader plan covering every certificate a team owns, see how to prevent SSL certificate expiry.

Automation Checklist

Renewal runs at least 30 days before expiry (not at the last minute)
Dry-run tests pass: certbot renew --dry-run or equivalent
Systemd timer or cron job is enabled and running
Renewal hook restarts web server after certificate update
Monitoring alerts if certificate age drops below 14 days (renewal failed)
Test renewal in staging environment before production
Log output from renewal jobs is captured and reviewed

Use our free SSL Certificate Checker to verify any domain's certificate expiry date and TLS configuration — instantly confirm renewals completed successfully.

Common questions

How does Let's Encrypt automatic renewal work?

Certbot checks certificate expiry daily via a cron job or systemd timer. When expiry is within 30 days, it requests a new certificate using the same challenge method (HTTP-01 or DNS-01) and replaces the existing one. The web server is reloaded via a --deploy-hook. Failures are logged but do not send alerts unless configured.

Why does automated SSL renewal sometimes fail silently?

Renewal failures happen when port 80 is blocked (HTTP-01 challenge), DNS propagation fails (DNS-01), the domain A record changed, or the certbot service stopped running. Failures appear in /var/log/letsencrypt/letsencrypt.log but you only see them if monitoring logs. Use independent certificate expiry monitoring to catch failures before expiry.

Can I automate renewal for wildcard certificates?

Yes, but only via the DNS-01 challenge since wildcard domains cannot be validated over HTTP. Certbot has plugins for major DNS providers (Cloudflare, Route53, Namecheap). The plugin automatically creates and removes the required _acme-challenge TXT record. Ensure your DNS provider's API token has the necessary permissions.

How do I test that auto-renewal is working?

Run certbot renew --dry-run to simulate renewal without replacing certificates. Check the output for errors. Verify the systemd timer is active: systemctl status certbot.timer. Set up certificate expiry monitoring on all domains — this catches renewal failures independently of Certbot's own logging.

Where does Certbot store its renewal settings?

In one file per certificate under /etc/letsencrypt/renewal/, named after the certificate. It records the authenticator, installer and ACME server used at issuance, and certbot renew replays exactly those settings. To change how a certificate renews, issue it once more with the new method and the same --cert-name, and Certbot rewrites the file.

Can I automate renewal for certificates from a paid CA?

Usually, yes. Most commercial CAs now offer an ACME endpoint, so Certbot, cert-manager and the other clients work with them. You point the client at the CA's ACME directory URL with --server and link it to your CA account with External Account Binding credentials (--eab-kid and --eab-hmac-key in Certbot).

References and further reading

The specifications and primary documentation this article is based on.

Read next

More guides on the same subject.

Even automated renewals can fail — monitor them

Automation failures happen: DNS propagation issues, API rate limits, network timeouts. CertNotify reads the expiry date of the certificate your site actually serves once a day, so a renewal that has stopped working shows up as an email before it reaches users.

Add certificate monitoring free →