How to use a webroot challenge with Certbot

A Certbot webroot challenge lets an existing HTTP site answer ACME validation without stopping the web server or letting Certbot edit the virtual host. It works only when the domain, public port 80, document root, and challenge path all point at the same content.

The webroot authenticator writes a temporary token file below the selected document root. The running web server must then serve that file from the public challenge path for the domain so the certificate authority can retrieve it over HTTP-01 validation.

The example flow uses one document root for both the apex and www hostnames. Replace the names, webroot path, and account email with the real site values, and use a DNS challenge instead when port 80 is blocked, the site is private-only, or a wildcard certificate is required.

Steps to use a Certbot webroot challenge:

  1. Confirm that the domain resolves to the server that serves the HTTP site.
    $ getent ahosts site.example.net
    203.0.113.10   STREAM site.example.net
    203.0.113.10   DGRAM
    203.0.113.10   RAW

    Check every name that will appear on the certificate. If a name has an .AAAA record, that IPv6 address must serve the same challenge path or HTTP-01 validation can fail from validators that choose IPv6.

  2. Confirm the web server's document root for the site.
    $ sudo nginx -T
    nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
    nginx: configuration file /etc/nginx/nginx.conf test is successful
    # configuration file /etc/nginx/sites-enabled/site.example.net:
    server {
        listen 80;
        server_name site.example.net www.site.example.net;
        root /var/www/site.example.net;
    }
    ##### snipped #####

    Use the directory that the active virtual host serves for the requested name. On Apache, this is usually the matching DocumentRoot value instead of the Nginx root directive.

  3. Create the challenge directory inside the webroot.
    $ sudo mkdir -p /var/www/site.example.net/.well-known/acme-challenge

    Certbot can create its own challenge files, but creating the directory first makes hidden-directory and permission problems visible before the ACME request.

  4. Write a short probe file into the challenge directory.
    $ printf 'challenge-ok\n' | sudo tee /var/www/site.example.net/.well-known/acme-challenge/probe-token
    challenge-ok
  5. Confirm that the probe file is reachable over public HTTP.
    $ curl http://site.example.net/.well-known/acme-challenge/probe-token
    challenge-ok

    HTTP-01 starts on port 80. Redirects can work when they preserve the challenge URL and stay on ports 80 or 443, but a redirect to a login page, a different host, or a blocked hidden directory will fail validation.

  6. Confirm that Certbot has the webroot authenticator.
    $ certbot plugins
    Saving debug log to /var/log/letsencrypt/letsencrypt.log
    ##### snipped #####
    * webroot
    Description: Saves the necessary validation files to a
    .well-known/acme-challenge/ directory within the nominated webroot path. A
    separate HTTP server must be running and serving files from the webroot path.
    HTTP challenge only (wildcards not supported).
    Interfaces: Authenticator, Plugin
    ##### snipped #####
  7. Rehearse the certificate request with the staging service.
    $ sudo certbot certonly --webroot -w /var/www/site.example.net -d site.example.net -d www.site.example.net -m admin@example.net --agree-tos --dry-run
    Saving debug log to /var/log/letsencrypt/letsencrypt.log
    Simulating a certificate request for site.example.net and www.site.example.net
    The dry run was successful.

    For domains served from different document roots, place the matching -w option before the -d names that use that webroot.

    –dry-run uses the staging service and does not save a production certificate.

  8. Request the production certificate after the dry run succeeds.
    $ sudo certbot certonly --webroot -w /var/www/site.example.net -d site.example.net -d www.site.example.net -m admin@example.net --agree-tos
    Saving debug log to /var/log/letsencrypt/letsencrypt.log
    Requesting a certificate for site.example.net and www.site.example.net
    Successfully received certificate.
    Certificate is saved at: /etc/letsencrypt/live/site.example.net/fullchain.pem
    Key is saved at:         /etc/letsencrypt/live/site.example.net/privkey.pem

    This command obtains the certificate only. Install /etc/letsencrypt/live/site.example.net/fullchain.pem and /etc/letsencrypt/live/site.example.net/privkey.pem in the web server configuration before expecting HTTPS traffic to use the new certificate.

  9. Confirm that Certbot recorded the certificate with the expected names.
    $ sudo certbot certificates --cert-name site.example.net
    Saving debug log to /var/log/letsencrypt/letsencrypt.log
    Found the following certs:
      Certificate Name: site.example.net
        Domains: site.example.net www.site.example.net
        Expiry Date: 2026-09-09 12:34:56+00:00 (VALID: 89 days)
        Certificate Path: /etc/letsencrypt/live/site.example.net/fullchain.pem
        Private Key Path: /etc/letsencrypt/live/site.example.net/privkey.pem
  10. Test renewal with the saved webroot configuration.
    $ sudo certbot renew --cert-name site.example.net --dry-run
    Saving debug log to /var/log/letsencrypt/letsencrypt.log
    Processing /etc/letsencrypt/renewal/site.example.net.conf
    Congratulations, all simulated renewals succeeded:
      /etc/letsencrypt/live/site.example.net/fullchain.pem (success)

    A successful renewal dry run confirms that Certbot can reuse the saved webroot authenticator and challenge path later.

  11. Remove the temporary probe file.
    $ sudo rm /var/www/site.example.net/.well-known/acme-challenge/probe-token

    Certbot removes its own ACME challenge files after validation. Removing the manual probe keeps the challenge directory from serving stale test content.