Module 7 · 7.3
English
https with Let's Encrypt
Module 7: Domain, web server and https · Reading time: 12 minutes
Learning objective: After this lesson you can use certbot to request a free certificate from Let's Encrypt for nginx, redirect http to https, check automatic renewal and fix the most common errors in a request.
Key concepts: certificate, certificate authority (CA), ACME, Let's Encrypt, domain validation, HTTP-01, DNS-01, TLS-ALPN-01, certbot, renewal, certbot renew --dry-run, lifetime, ARI, rate limits, staging, CAA, redirecting to https
Why https, and what a certificate proves
Without https, everything between browser and server travels across the network unencrypted: passwords, forms, cookies. Anyone along the way (a public wifi network, a hacked router) can read along or change something. That is why browsers mark http sites as "not secure", and some features only work over https. For a vault such as Vaultwarden (lesson 9.4), https is even required.
https uses TLS, and for that your server needs a certificate: proof, signed by a certificate authority (CA) that browsers trust, that this key belongs to this name. An ordinary certificate only proves that the applicant controls the domain name (domain validation). It says nothing about who you are or whether you can be trusted.
Let's Encrypt is a free CA that only issues domain-validated certificates, fully automatically via the ACME protocol. A program on your server (the ACME client) requests the certificate, proves that it controls the name, installs it and renews it in time. This lesson uses certbot, the client from the EFF. Caddy (lesson 7.4) has an ACME client built in.
How do you prove that you control the name?
Let's Encrypt has three kinds of challenges:
| Challenge | How | Port | Wildcard (*.example.com) |
|---|---|---|---|
| HTTP-01 | place a file under http://<naam>/.well-known/acme-challenge/ | 80 | no |
| DNS-01 | set a TXT record _acme-challenge.<naam> | none | yes |
| TLS-ALPN-01 | present a special certificate during the TLS handshake | 443 | no |
For an ordinary VPS with a website, HTTP-01 is the simplest. For it to work:
- the name must point to your server with A (and AAAA) records (lesson 7.1);
- port 80 must be open to the whole world, even if your site otherwise only uses https. Let's Encrypt validates only via port 80 and from several places on the internet; you cannot add those addresses to your firewall;
- your server must give the same answer over IPv4 and IPv6. If the name has an AAAA record, Let's Encrypt tries IPv6 first. Only if IPv6 gives no connection at all does it fall back to IPv4.
You need DNS-01 for a wildcard certificate, or if the server cannot be reached from outside. The ACME client then has to be able to change your DNS, usually via an API key from your DNS provider. That key can change your entire DNS: keep it as carefully as a password.
Installing certbot
The makers of certbot recommend the snap, because it always has the latest version. On Debian and Ubuntu you can also simply install it from the package repository:
anna@web1:~$ sudo apt install certbot python3-certbot-nginxThe version from the distribution is older but receives security updates via apt (lessons 4.1 and 4.2), and that fits the rest of this course. Choose one of the two, not both: two installations get in each other's way when renewing.
Requesting a certificate
With the site from lesson 7.2 already working on port 80:
anna@web1:~$ sudo certbot --nginx -d example.com -d www.example.com
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Enter email address or hit Enter to skip.
(Enter 'c' to cancel):
...
Requesting a certificate for example.com and www.example.com
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/example.com/fullchain.pem
Key is saved at: /etc/letsencrypt/live/example.com/privkey.pem
This certificate expires on 2027-02-07.
These files will be updated when the certificate renews.
Certbot has set up a scheduled task to automatically renew this certificate in the background.
Deploying certificate
Successfully deployed certificate for example.com to /etc/nginx/sites-enabled/example.com
Successfully deployed certificate for www.example.com to /etc/nginx/sites-enabled/example.com
Congratulations! You have successfully enabled HTTPS on https://example.com and https://www.example.com(The date and details are an example.) The --nginx plugin does three things: it temporarily sets up the challenge, fetches the certificate, and adjusts your server block with a listen 443 ssl block and a redirect from http to https. If you would rather do that yourself, use certbot certonly --nginx and add the lines to your configuration yourself.
A few things to watch out for:
- Request all names that belong to the site in one go (
-dper name). Every name must point to the server, otherwise the whole request fails. - The private key (
privkey.pem) is stored in/etc/letsencrypt/live/and is readable only by root. Do not copy it to other places and never put it in a backup that others can read. - The email address is optional. Since June 2025, Let's Encrypt no longer sends emails when a certificate is about to expire. So you have to monitor yourself that renewal succeeds (lesson 8.3).
Redirecting http to https
After certbot, your configuration looks roughly like this (tidied up; certbot adds its own comments # managed by Certbot and sometimes if lines):
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
root /var/www/example.com;
...
}The first block redirects everything over http to the same name and the same path over https, with a permanent redirect (301). The challenge file for renewal still works: Let's Encrypt follows that redirect. The second block is the real site. If you also want to send www to the bare name (or the other way round), do that in a separate block, so visitors see one fixed name.
Renewal: automatic, and checked
A certificate from Let's Encrypt is currently valid for 90 days. That is getting shorter: Let's Encrypt has announced that its default profile will issue 64-day certificates from 10 February 2027 and 45-day certificates from 16 February 2028. Renewing by hand is therefore not an option; it has to be automatic.
During installation, certbot sets up a systemd timer (or a cron entry) that checks twice a day whether a certificate needs renewing. Check that it is there, and test renewal with a trial request:
anna@web1:~$ systemctl list-timers 'certbot*'
NEXT LEFT LAST PASSED UNIT ACTIVATES
Mon 2026-11-09 22:41:00 CET 9h left - - certbot.timer certbot.service
anna@web1:~$ sudo certbot renew --dry-run
...
Congratulations, all simulated renewals succeeded:
/etc/letsencrypt/live/example.com/fullchain.pem (success)After renewal, nginx also has to load the new certificate. With the nginx plugin, certbot handles that itself; if you use certonly, set up a deploy hook that reloads nginx, for example an executable script in /etc/letsencrypt/renewal-hooks/deploy/ containing systemctl reload nginx.
Good ACME clients use ARI (ACME Renewal Information): Let's Encrypt then tells the client itself when a certificate should be renewed, for example earlier if something is wrong with a batch of certificates. So keep your client up to date.
If a request fails
| Message (shortened) | Cause | Solution |
|---|---|---|
Timeout during connect (likely firewall problem) | port 80 closed, or the name points to a different address | firewall (lesson 5.2), A/AAAA (lesson 7.1) |
Invalid response from http://.../.well-known/acme-challenge/...: 404 | a different server or server block answers, often via IPv6 | listen [::]:80, check the AAAA record |
CAA record for example.com prevents issuance | a CAA record only allows a different authority | adjust CAA (lesson 7.1) |
too many certificates already issued | a rate limit | wait; test with staging |
Let's Encrypt has rate limits to prevent abuse. The most important ones: at most 50 new certificates per registered domain per 7 days, at most 5 certificates for exactly the same set of names per 7 days, and at most 5 failed validations per name per hour. If you are experimenting and keep requesting again and again, you will soon run into them. So test with the staging environment, which has much more generous limits but issues certificates that browsers do not trust:
anna@web1:~$ sudo certbot certonly --nginx --staging -d test.example.comOnce it works there, request the real certificate.
On AlmaLinux and Rocky Linux
There, certbot comes from EPEL (dnf install certbot python3-certbot-nginx) or as a snap. The plugin and the commands are the same. Remember firewalld (--add-service=http --add-service=https) and SELinux if certificates are stored in an unusual location.
In practice
With a hosting package, the control panel takes care of the certificate for you (at WebOké and at other hosting companies with DirectAdmin, cPanel or Plesk). On a VPS you do it yourself, with certbot, Caddy or the ACME client that comes with your software. That applies with every provider.
Summary
- A certificate from Let's Encrypt proves that you control the name; certbot requests, installs and renews it via ACME.
- HTTP-01 needs port 80 open, to everyone, and the same answer over IPv4 and IPv6; DNS-01 is for wildcards.
certbot --nginx -d ... -d ...takes care of the certificate and the redirect to https; request all names of a site in one go.- Certificates are now valid for 90 days and are getting shorter; check the timer and
certbot renew --dry-run, and monitor the expiry date yourself. - Test with
--stagingto avoid rate limits; watch out for CAA.
Quiz
Exercise
Exercise Z7.3: https with a valid certificate. Request a certificate for your site (with certbot or Caddy), redirect http to https and test renewal with certbot renew --dry-run (with Caddy: check in the logs that the certificate was fetched). Enter the name of your site. The program makes a TLS connection to port 443 and checks whether the certificate is valid, is signed by a trusted authority and covers your name, and then requests http://<naam>/ to see whether you redirect to https. You also fill in when your certificate expires and how renewal is set up on your server.
Further reading
- Activating, renewing, and troubleshooting SSL certificates
- Free DNS course 5.2: CAA: who's allowed to issue certificates?
- Let's Encrypt, Challenge Types: https://letsencrypt.org/docs/challenge-types/
- Let's Encrypt, Rate Limits: https://letsencrypt.org/docs/rate-limits/
- Let's Encrypt, Decreasing Certificate Lifetimes to 45 Days: https://letsencrypt.org/2025/12/02/from-90-to-45
- Certbot instructions (nginx): https://certbot.eff.org/instructions?ws=nginx&os=snap
