SSL certificate expired or renewal failed: fix it now
Browsers show “Your connection is not private” the moment a certificate expires, and most visitors leave. Usually you can fix it in minutes: find out which certificate the server is really sending, renew it, and reload the web server. Then find out why automatic renewal failed, because it will fail again.
Do this first.
- Check what the server actually sends with
openssl s_client(below). Sometimes the certificate was renewed but the web server was never reloaded. - Run
sudo certbot renew --dry-runand read the error. Do not run real renewals over and over: failed attempts count against Let’s Encrypt rate limits. - After a successful renewal, reload (not restart) nginx or Apache.
Symptoms
- Browsers show a certificate warning such as NET::ERR_CERT_DATE_INVALID.
- API clients, webhooks and monitoring fail with “certificate has expired”.
- Mail clients warn about the certificate on IMAP or SMTP (the same certificate is often shared).
- Logs from
certbot renewshow “Failed to renew certificate” or a challenge error.
Step 1: check the live certificate
Ask the server what it sends, using the real host name (the -servername option selects the right site on shared IPs):
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates -ext subjectAltName
# Exit status 0 = still valid in 14 days (1209600 seconds), 1 = expires sooner
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
| openssl x509 -noout -checkend 1209600
For a mail server, add -starttls smtp and connect to port 25 or 587. Look at notAfter. Then compare with what Certbot has on disk:
sudo certbot certificates
This lists each certificate, its domains, its expiry date and its file paths. If Certbot shows a fresh date but openssl shows the old one, the certificate was renewed but the web server still has the old one in memory. Skip to step 4.
If the dates match and are in the past, the renewal itself failed. Check that the web server points at the files in /etc/letsencrypt/live/name/ (fullchain.pem and privkey.pem), not at copies. The files in live are links that Certbot updates on every renewal; a copy never changes.
Step 2: test renewal safely
sudo certbot renew --dry-run
A dry run gets test certificates from the Let’s Encrypt staging server and saves nothing, so it does not use up your production limits. Read the last lines of the output and the log in /var/log/letsencrypt/letsencrypt.log. To test one certificate only, add --cert-name example.com.
Step 3: fix the common failures
Port 80 blocked or not answering
The HTTP-01 challenge, used by the webroot, nginx, Apache and standalone methods, can only be done on port 80. Let’s Encrypt follows redirects to port 80 or 443, so an HTTP-to-HTTPS redirect is fine, but the first request must reach you on port 80.
# Is anything listening on port 80?
sudo ss -ltnp | grep ':80 '
# Firewall: Debian/Ubuntu with ufw
sudo ufw status
sudo ufw allow 80/tcp
# Firewall: RHEL, Rocky, AlmaLinux, Fedora
sudo firewall-cmd --list-all
sudo firewall-cmd --permanent --add-service=http && sudo firewall-cmd --reload
Also check any cloud security group, hosting panel firewall or CDN in front of the server. If the domain has an AAAA (IPv6) record, the site must answer on IPv6 as well, because the validation can arrive over IPv6.
Wrong webroot
With --webroot, Certbot writes a file to webroot/.well-known/acme-challenge/, and your web server must serve it over plain HTTP. Test it yourself:
# Find the webroot Certbot uses
sudo grep -E 'webroot|authenticator' /etc/letsencrypt/renewal/example.com.conf
# Put a test file there and fetch it from outside
sudo mkdir -p /var/www/example/.well-known/acme-challenge
echo ok | sudo tee /var/www/example/.well-known/acme-challenge/test
curl -i http://example.com/.well-known/acme-challenge/test
sudo rm /var/www/example/.well-known/acme-challenge/test
A 404 means the path is wrong, or a rule (for example a block on hidden files, a rewrite to index.php, or a forced login) hides it. Fix the server configuration or the path. Certbot’s own docs warn that hand-editing files in /etc/letsencrypt/renewal/ can damage them; back the file up first, or re-run certbot certonly with the correct -w path for that certificate.
DNS problems
- The A or AAAA record points to an old server or a load balancer that does not forward the challenge. Check with
dig +short A example.comanddig +short AAAA example.com. - A CAA record blocks the CA. Check with
dig +short CAA example.com. If any CAA record exists, one must allowletsencrypt.org, for exampleexample.com. CAA 0 issue "letsencrypt.org". - Wildcard certificates need the DNS-01 challenge: a TXT record at
_acme-challenge.example.com. If your DNS API credentials expired or the DNS provider changed, renewal fails. Check the plugin’s credentials file.
Rate limits
As of November 2026, Let’s Encrypt allows 50 new certificates per registered domain per 7 days, 5 certificates for the exact same set of names per 7 days, and 5 failed validations per name per account per hour. Renewals are exempt from the per-domain limit, and renewals coordinated by ACME Renewal Information (ARI) are exempt from all limits. The error message includes a “retry after” time in UTC. Wait it out; you cannot ask for an override of the duplicate or failed-validation limits. Use --dry-run until the test passes, then renew once.
Step 4: renew and reload
sudo certbot renew --cert-name example.com
# Test the config, then reload (no dropped connections)
sudo nginx -t && sudo systemctl reload nginx
# Apache on Debian/Ubuntu
sudo apachectl configtest && sudo systemctl reload apache2
# Apache on RHEL family
sudo apachectl configtest && sudo systemctl reload httpd
Add a deploy hook so future renewals reload the server automatically. Any executable script in /etc/letsencrypt/renewal-hooks/deploy/ runs once after each successful renewal:
printf '#!/bin/sh\nsystemctl reload nginx\n' | sudo tee /etc/letsencrypt/renewal-hooks/deploy/reload-nginx
sudo chmod 755 /etc/letsencrypt/renewal-hooks/deploy/reload-nginx
Do not forget other services that use the same files, such as Postfix, Dovecot or HAProxy.
Step 5: check the renewal timer
Certbot relies on a scheduled job that runs certbot renew twice a day. Depending on how it was installed, that is a systemd timer (certbot.timer from Debian/Ubuntu packages, certbot-renew.timer from EPEL/Fedora, snap.certbot.renew.timer for the snap) or a cron entry.
systemctl list-timers --all | grep -i certbot
grep -rs certbot /etc/crontab /etc/cron.d/
# Enable the packaged timer if it is off (Debian/Ubuntu name shown)
sudo systemctl enable --now certbot.timer
sudo journalctl -u certbot.service --since "-7 days"
Shorter lifetimes are coming
Let’s Encrypt certificates are still valid for 90 days by default, but this is changing. Certbot 4.0 and later renew when less than a third of the lifetime is left; older versions renewed 30 days before expiry. Any script with a fixed “every 60 days” schedule will break.
Let’s Encrypt also stopped sending expiry reminder emails on June 4, 2025, so you need your own monitoring. Industry rules cap all public certificates at 47 days from March 15, 2029.
Confirm it is fixed
openssl s_clientshows a newnotAfterdate for every host name and port you serve.sudo certbot renew --dry-runsucceeds for all certificates.- The renewal timer or cron job is active and its last run succeeded.
- A deploy hook reloads every service that uses the certificate.
Prevent it next time
- Monitor expiry from outside: alert when fewer than 14 days are left. The
-checkendtest above works in any monitoring script. - Keep Certbot current (distribution package or snap) so its renewal timing suits shorter lifetimes.
- Run
certbot renew --dry-runafter any firewall, DNS, CDN or web server change. - Write down which certificates exist and which services use them.
When to get help
Ask your hosting provider if you do not control the firewall, load balancer or DNS. If a CDN or hosting panel issues the certificate for you, renew it there, not with Certbot. If crt.sh shows certificates for your domain that you did not request, treat it as a possible hijack: see Domain or DNS hijacked.
Sources
- User Guide (certificates, renew, hooks, webroot), Certbot documentation
- Rate Limits, Let’s Encrypt
- Challenge Types, Let’s Encrypt
- Certificate Authority Authorization (CAA), Let’s Encrypt
- Decreasing Certificate Lifetimes to 45 Days, Let’s Encrypt
- Certificate Lifetime Rationale and Plans, Let’s Encrypt
- Shorter Certificate Lifetimes and Rate Limits, Let’s Encrypt
- Expiration Notification Service Has Ended, Let’s Encrypt