Certbot stores the webroot path it used during initial certificate issuance. If you later move Moodle's document root — for example, when separating the codebase from the data directory — Let's Encrypt HTTP challenge requests hit a 404 and auto-renewal fails silently until the certificate expires.

How Certbot’s webroot validation works

The HTTP-01 challenge works by placing a temporary token file at:

/.well-known/acme-challenge/<token>

Let’s Encrypt then fetches that file over HTTP to prove you control the domain. Certbot writes the token to a directory on disk and the web server serves it. The directory it writes to is recorded when the certificate is first issued and stored in the renewal configuration file.

If the web server’s document root has changed since then, the file is written to the old path, the web server cannot find it, and Let’s Encrypt gets a 404. The renewal fails.

Diagnosing the problem

Run a dry-run renewal to see the error without modifying anything:

certbot renew --dry-run

A failing renewal will show output like:

Attempting to renew cert (yourdomain.com) via certbot...
Challenge failed for domain yourdomain.com
http-01 challenge for yourdomain.com
Cleaning up challenges
Failed to renew certificate yourdomain.com with error:
Some challenges have failed.

Check the current webroot path Certbot has on record:

cat /etc/letsencrypt/renewal/yourdomain.com.conf

Look for the webroot_path value under [webroot_map]:

[renewalparams]
authenticator = webroot

[webroot_map]
yourdomain.com = /home/oldsitepath/public_html

Compare this against your Apache or Nginx virtual host configuration to find the current document root. If they differ, that is the problem.

Fixing it

Edit the renewal config file directly:

vim /etc/letsencrypt/renewal/yourdomain.com.conf

Update webroot_path and the path in [webroot_map] to the current document root:

[webroot_map]
yourdomain.com = /home/newsitepath/public_html

Save, then test:

certbot renew --dry-run

If the dry-run succeeds, the next scheduled renewal will work correctly.

Alternative: re-run certificate issuance

If you prefer not to edit the config file manually, you can re-run Certbot’s webroot mode, pointing it at the new path. This updates the stored configuration as a side effect:

certbot certonly --webroot \
  -w /home/newsitepath/public_html \
  -d yourdomain.com \
  --force-renewal

Use --force-renewal only in this recovery scenario — it counts against Let’s Encrypt’s rate limits.

Checking that the challenge path is web-accessible

Before the dry-run, verify the web server can actually serve from /.well-known/acme-challenge/. On Apache, the default WordPress or Moodle .htaccess sometimes redirects all requests to index.php, which blocks the challenge path. Check for a rule like this in your .htaccess:

RewriteRule ^ index.php [L]

If present, add an exception before it:

RewriteRule ^\.well-known - [L]

On Nginx, verify there is no try_files or return directive that catches all requests before the challenge path can be served.

Automating renewal checks

Certbot installs a systemd timer or cron job for auto-renewal, but failures are only logged — no alert is sent by default. Add a simple check to your monitoring:

certbot certificates 2>/dev/null | grep -E "Domains:|Expiry Date:|VALID"

Or use ssl-cert-check to get an alert before the certificate reaches a critical expiry window.

Solin handles Moodle server migrations, infrastructure, and SSL management.

Contact us