Certificate renewal fails and the site goes to expired HTTPS
The renewal ran and the validation failed, so the old certificate stayed until it expired.
What you see
Browsers refuse the site with a date error. The renewal timer has been running for weeks with failures nobody read.
What is actually wrong
The HTTP challenge path is being redirected or blocked, the DNS record has changed, the web root in the renewal configuration no longer matches the site, or a rate limit has been reached after repeated failures.
Codes and articles
Fixes (3)
Repair the HTTP challenge path
HTTP-01 validation. The challenge file has to be reachable over plain HTTP from the internet.
Do a dry run — it tests everything without consuming a rate limit.
sudo certbot renew --dry-runThe dry run against the staging environment is the correct way to debug this. Repeatedly retrying against production is how a domain hits the failed-validation rate limit and is locked out for an hour on top of everything else.
Read the renewal configuration to see what it believes.
sudo cat /etc/letsencrypt/renewal/example.com.confTest that the challenge path is actually served over HTTP with no redirect to HTTPS.
sudo mkdir -p /var/www/html/.well-known/acme-challengeecho ok | sudo tee /var/www/html/.well-known/acme-challenge/testcurl -sIL http://example.com/.well-known/acme-challenge/test | head -12
A redirect to HTTPS is fine as long as it follows through; a redirect to a broken HTTPS is not. Add an explicit exception in nginx so the challenge is always served over plain HTTP.
printf 'location ^~ /.well-known/acme-challenge/ {\n root /var/www/html;\n default_type "text/plain";\n allow all;\n}\n' | sudo tee /etc/nginx/snippets/acme.confsudo nginx -t && sudo systemctl reload nginx
Confirm DNS points here and that port 80 is open — Let's Encrypt validates from the internet, not from the machine.
dig +short example.comcurl -sI http://example.com/ | head -3
sudo certbot renew --dry-run && sudo certbot renew && sudo systemctl reload nginxRepair DNS-01 validation
DNS-01, which is what wildcard certificates require.
Run the dry run and read which record it could not set or find.
sudo certbot renew --dry-run 2>&1 | tail -30Check the API credentials for the DNS plugin are still valid — an expired token is the usual cause and the error is not always clear.
sudo ls -l /etc/letsencrypt/*.inisudo chmod 600 /etc/letsencrypt/*.ini
Confirm the TXT record can be seen from outside after a manual attempt.
dig +short TXT _acme-challenge.example.com @1.1.1.1Querying a public resolver rather than the local one is the point. The record can exist on the authoritative server and not yet be visible where Let's Encrypt will look, which is a propagation delay rather than a failure.
Increase the propagation wait if the provider is slow.
sudo certbot renew --dns-cloudflare-propagation-seconds 60 --dry-runCheck the CAA record permits the issuer — a CAA record naming a different authority blocks issuance entirely.
dig +short CAA example.com
sudo certbot certificatesMake sure renewal is running and being watched
After fixing it. This is what stops it happening again.
Check the timer is enabled and when it last ran.
systemctl list-timers certbot* snap.certbot* --allsudo systemctl status certbot.timer --no-pager
Add a reload hook so a renewed certificate is actually served — a renewal that does not reload nginx leaves the old certificate in memory.
printf '#!/bin/sh\nsystemctl reload nginx\n' | sudo tee /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.shsudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
This is a genuinely common cause of an expired certificate on a machine where renewal has been succeeding for months. The file on disk is current and the running process is serving one from ninety days ago.
Add monitoring that checks the expiry from outside, so a failure is noticed before the browser notices it.
echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null | openssl x509 -noout -enddateRegister an email address with the ACME account so expiry warnings arrive.
sudo certbot update_account --email admin@example.com
sudo certbot certificates | grep -A2 'Certificate Name'Related faults
Where this stops. This write-up was written and checked by hand. It says what each step changes, how to confirm it worked and how to reverse it, and anything destructive is flagged before you reach it. If it does not match what your machine is doing, search the Support Centre for the exact code or message — and when something needs a person, get in touch.