Fix Let's Encrypt renewal failing behind an HTTPS redirect
Updated 5 November 2026 · By Brent Alexis
Short answer: Let's Encrypt renewal fails behind an HTTPS redirect when the redirect sends the renewal check to a place that can't answer it, usually your app. With certbot --webroot, give the port 80 site an exception for /.well-known/acme-challenge/ that serves files from the folder you passed to certbot -w, and redirect everything else. Then prove it with sudo certbot renew --dry-run.
Reproduced in the lab with Debian 12's packages (Nginx 1.22.1, Apache 2.4.68, Certbot 2.1.0) and ProxyForge's own output. Let's Encrypt can't be reached from a lab, so Pebble, Let's Encrypt's own test certificate authority, did the checks the same way, on port 80: 29 checks. Let's Encrypt's redirect rules come from its documentation. How it's tested
- The error
- Why it fails (the redirect isn't the problem)
- Fix on Nginx
- Fix on Apache
- The folder must match certbot -w
- Check it before the next renewal
The error
Certbot prints these lines when the check fails (the Detail: line comes from the certificate authority and is left out here):
Certbot failed to authenticate some domains (authenticator: webroot). The Certificate Authority reported these problems:
Domain: example.com
Type: unauthorized
Hint: The Certificate Authority failed to download the temporary challenge files created by Certbot. Ensure that the listed domains serve their content from the provided --webroot-path/-w and that files created there can be downloaded from the internet.
Failed to renew certificate example.com with error: Some challenges have failed.
It usually shows up weeks after setup. The first certificate was issued while the site was still plain HTTP, then an HTTPS redirect went in. Certbot only renews when the certificate gets close to expiry (30 days before, by default), so that's the first time the check runs through the redirect. If the renewal email from Let's Encrypt never reached you, the first sign is a browser warning when the certificate expires.
Why it fails (the redirect isn't the problem)
To renew, Certbot writes a small file into /.well-known/acme-challenge/ inside the folder you gave it with -w, and Let's Encrypt downloads it from http://your-domain/.well-known/acme-challenge/... on port 80. Let's Encrypt follows redirects, up to 10, to HTTP or HTTPS on ports 80 and 443, and it doesn't check the certificate on an HTTPS redirect (source: Let's Encrypt challenge types, checked 5 November 2026).
In the lab, a redirect on its own never broke renewal: when the HTTPS site served the same folder, the check followed the redirect and renewal worked. It failed when the redirect led into an app, with Nginx or Apache proxying everything to it, because the app has no such file and answers 404.
| Setup | Renewal in the lab |
|---|---|
| Port 80 redirects everything; the HTTPS site proxies everything to the app | Fails on Nginx and Apache |
| Port 80 redirects everything; the HTTPS site serves the same folder Certbot writes to | Works (the redirect is followed) |
An exception location on port 80, but return 301 written directly in the server block | Fails |
Exception on port 80 for /.well-known/acme-challenge/, everything else redirected (ProxyForge's output) | Works on Nginx and Apache |
Exception serves /var/www/html, but Certbot was run with -w /var/www/example.com | Fails |
Fix on Nginx
Answer the check on port 80 and redirect everything else. This is the port 80 block from ProxyForge's Config Generator with HTTPS ticked:
server {
listen 80;
server_name example.com www.example.com;
# Let’s Encrypt renewals (certbot --webroot): answer the check over plain HTTP from the
# folder you passed to certbot -w. Change the path below if you used a different one.
location /.well-known/acme-challenge/ {
root /var/www/html;
}
# Everything else goes to HTTPS
location / {
return 301 https://$host$request_uri;
}
}
The return must sit inside location /. In the lab, with the exception location in place but return 301 written directly in the server block, the check was still redirected and renewal failed. A return in the server block runs before Nginx looks at any location.
Then test and reload:
sudo nginx -t && sudo systemctl reload nginx
Fix on Apache
A plain Redirect permanent / https://example.com/ on port 80 sends the check along with everything else, and if the HTTPS virtual host proxies everything to an app, the app answers 404. In the lab that's exactly what happened, and renewal failed. This is the port 80 virtual host from the Config Generator (Apache, virtual host, HTTPS ticked):
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
# Let’s Encrypt renewals (certbot --webroot): answer the check over plain HTTP from the
# folder you passed to certbot -w. Change the path below if you used a different one.
DocumentRoot /var/www/html
<Directory /var/www/html/.well-known/acme-challenge>
Require all granted
</Directory>
# Everything else goes to HTTPS
RewriteEngine On
RewriteCond %{REQUEST_URI} !^/\.well-known/acme-challenge/
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
</VirtualHost>
It needs mod_rewrite (sudo a2enmod rewrite on Debian and Ubuntu). If you'd rather keep the Redirect, the other fix that worked in the lab is to keep the check out of the proxy on the HTTPS side, so Apache serves it from the folder Certbot writes to:
DocumentRoot /var/www/html
ProxyPass /.well-known/acme-challenge/ !
ProxyPass / http://app:8080/
The ! line must come before ProxyPass /. Test with sudo apachectl configtest, then reload.
The folder must match certbot -w
The exception has to serve the same folder Certbot writes to. In the lab, with the exception serving /var/www/html and Certbot run with -w /var/www/example.com, the check failed even though nothing was redirected. Certbot remembers the folder from the first run. To see it:
sudo grep -A2 webroot_map /etc/letsencrypt/renewal/example.com.conf
The path after example.com = is the one your root (Nginx) or DocumentRoot (Apache) must point to. ProxyForge's output uses /var/www/html; change it if yours is different.
Check it before the next renewal
Put a test file where Certbot writes its files and ask for it over plain HTTP:
sudo mkdir -p /var/www/html/.well-known/acme-challenge
echo ok | sudo tee /var/www/html/.well-known/acme-challenge/pf-test
curl -sIL http://example.com/.well-known/acme-challenge/pf-test | grep -iE '^(HTTP|location)'
A single 200 line means port 80 answers the check itself. In the lab, with ProxyForge's output, it printed:
HTTP/1.1 200 OK
A redirect followed by a 404 is the broken setup. From the lab (the lab's curl also had -k, because its certificate isn't publicly trusted, and the 404 came from the lab's test app):
HTTP/1.1 301 Moved Permanently
Location: https://example.com/.well-known/acme-challenge/pf-test
HTTP/1.1 404 File not found
Then run a full test renewal. It doesn't replace your certificate, and in the lab it failed on the broken setup and passed on the fixed one:
sudo certbot renew --dry-run
By default, --dry-run checks against Let's Encrypt's staging server, so it tests the real path from the internet to your server. Delete the test file afterwards. If an Nginx change seems to do nothing, check the reload applied: see nginx -s reload said OK, but my change didn't apply.
Get the Certbot command and HTTPS config →
How to use ProxyForge · FAQ: common errors · Guides · What's new · About