Let’s Encrypt’s HTTP-01 challenge works by fetching a token from http://yourdomain/.well-known/acme-challenge/<token>. That one path must stay reachable over plain HTTP, even though you redirect everything else to HTTPS.
Port-80 block that serves the challenge and redirects the rest:
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
# ACME challenge: served over HTTP from a shared webroot.
location ^~ /.well-known/acme-challenge/ {
root /var/www/certbot;
default_type "text/plain";
}
# Everything else -> HTTPS.
location / {
return 301 https://$host$request_uri;
}
}
The ^~ modifier matters: it tells Nginx that once this prefix matches, don’t fall through to the redirect — otherwise the challenge request would get 301’d and validation fails.
Issue the certificate in webroot mode (writes the token into that same directory):
sudo mkdir -p /var/www/certbot
sudo certbot certonly --webroot -w /var/www/certbot \
-d example.com -d www.example.com
Then point your HTTPS block at the issued files (see the TLS recipe):
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
Renewal:
- certbot installs a systemd timer (or cron job) that runs
certbot renewtwice a day; it only renews certs within 30 days of expiry. - Renewal reuses the same webroot, so it works unattended as long as the port-80 challenge block stays in place.
- Make Nginx pick up renewed certs automatically with a deploy hook:
sudo certbot renew --deploy-hook "nginx -s reload"
sudo nginx -t && sudo nginx -s reload