Let's Encrypt Certificate: certbot Setup, Renewal, Limits

Updated: 5 min read Editorial team vserverhost
Short answer

To get a Let's Encrypt certificate on Debian or Ubuntu, install certbot with apt install certbot python3-certbot-nginx and run certbot --nginx -d example.com -d www.example.com. The domain must resolve to the server and port 80 must be open. A systemd timer renews it automatically, and certbot renew --dry-run confirms renewal works.

Getting the certificate is the easy part. The trouble comes later: a renewal timer that never ran, validation that fails after a server move, or a container that requested a fresh certificate on every restart until Let’s Encrypt locked it out for a week. This guide covers the one certbot command and everything around it.

What you need before requesting a Let’s Encrypt certificate

Let’s Encrypt has to confirm you control the domain. With the default HTTP-01 challenge, certbot places a file under /.well-known/acme-challenge/ and Let’s Encrypt fetches it over port 80. Three things must be true:

  • The domain resolves to your server through an A record, and if there’s an AAAA record, the server actually answers on IPv6.
  • Port 80 is open, even if the site will only serve HTTPS.
  • The web server answers requests for exactly this name.
dig +short example.com A
dig +short example.com AAAA
curl -I http://example.com

If curl gets a response from your server, you’re ready.

Issue the certificate with certbot and nginx

On Debian 12 and Ubuntu 24.04, certbot is in the standard repositories:

apt update
apt install certbot python3-certbot-nginx

Test against staging first. It issues certificates that browsers don’t trust, but its limits are far more generous:

certbot --nginx --test-cert -d example.com -d www.example.com

Then the real one:

certbot --nginx -d example.com -d www.example.com

certbot asks whether to replace the staging certificate and adds ssl_certificate and ssl_certificate_key to your server block. Files live in /etc/letsencrypt/live/example.com/:

FileContents
fullchain.pemCertificate plus intermediate, for ssl_certificate
privkey.pemPrivate key, for ssl_certificate_key
cert.pemCertificate only, rarely needed
chain.pemIntermediates only

If you’d rather certbot didn’t edit your nginx config, use webroot mode and add the paths yourself:

certbot certonly --webroot -w /var/www/example.com \
  -d example.com -d www.example.com \
  --deploy-hook "systemctl reload nginx"

Check that renewal is automated

The Debian package installs a systemd timer that runs certbot renew twice a day. certbot only renews certificates that are close to expiry, roughly 30 days before the end for a 90-day certificate.

systemctl list-timers | grep certbot
certbot renew --dry-run
Congratulations, all simulated renewals succeeded:
  /etc/letsencrypt/live/example.com/fullchain.pem (success)

A snap install uses snap.certbot.renew.timer instead. Having both the apt and the snap version installed at once is a classic source of confusion.

After renewal, nginx has to load the new file. The nginx plugin handles that; in webroot mode the --deploy-hook does. The hook is saved in /etc/letsencrypt/renewal/example.com.conf and runs on every renewal.

Let’s Encrypt no longer sends expiry reminder emails. If renewal fails silently, you find out when browsers start warning. A simple check from cron or your monitoring catches it earlier:

echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
  | openssl x509 -noout -enddate

Certificate lifetime: from 90 days to 45

Let’s Encrypt has announced a staged reduction (Let’s Encrypt: certificate lifetimes):

FromDefault classic profile
Now90 days
10 February 202764 days
16 February 202845 days

Since May 2026 an opt-in tlsserver profile already issues 45-day certificates, and a shortlived profile issues 6-day ones. The practical lesson: fixed schedules like “renew every 60 days from cron” will break. Let the ACME client decide when to renew.

Rate limits that matter in practice

From the official rate limit documentation:

LimitValue
New certificates per registered domain50 per 7 days
Certificates for the exact same set of names5 per 7 days
Failed authorizations per name per account5 per hour
New orders per account300 per 3 hours
New accounts per IP address10 per 3 hours

The duplicate limit catches people who rebuild containers without persisting /etc/letsencrypt or acme.json. Every restart orders a new certificate and after the fifth you wait a week. Renewals coordinated through ARI (ACME Renewal Information) are exempt from all of these limits, according to Let’s Encrypt.

One certificate for many names, or many certificates?

A single Let’s Encrypt certificate can hold up to 100 names, and it’s tempting to put every site on the server into one. The catch: if validation fails for one name, say a domain that lapsed or now points elsewhere, renewal fails for all of them. Every name also sits side by side in public Certificate Transparency logs.

One certificate per site, example.com plus www.example.com, is easier to live with. certbot handles as many as you like. certbot certificates lists them with their expiry dates.

Error messages and what they mean

Timeout during connect (likely firewall problem): port 80 is closed or the IP is wrong. If an IPv6 address appears in the message, it’s almost always an AAAA record without working IPv6.

Invalid response from http://example.com/.well-known/acme-challenge/...: 404: the request arrives but another server block or a redirect catches it. Look at rewrites and any proxy in front.

DNS problem: NXDOMAIN looking up A for www.example.com: there’s no record for www. Create one or drop -d www.example.com.

For *.example.com you need DNS-01, covered in the wildcard certificate guide. If you run Caddy or Traefik instead of nginx, they handle certificates without certbot, as shown in reverse proxy for multiple domains.

When certbot renew --dry-run passes, you’re done. For the next project you can find an available domain at betadomain. Extensions such as .dev (€22.99) and .app (€22.99) are on the HSTS preload list, so browsers only load them over HTTPS and a certificate is mandatory from day one.

Step by step

  1. Check DNS and port 80Confirm with dig +short example.com A and AAAA that the domain resolves to the server, and with curl -I http://example.com that port 80 answers.
  2. Install certbotRun apt install certbot python3-certbot-nginx, or python3-certbot-apache for Apache.
  3. Test against stagingRun certbot --nginx --test-cert -d example.com -d www.example.com to catch errors without touching production rate limits.
  4. Issue the real certificateRun the same command without --test-cert and confirm replacing the staging certificate.
  5. Verify renewalRun certbot renew --dry-run and check with systemctl list-timers that the certbot timer is active.

Found your domain? You can check and register it directly at buy a domain – yearly price from €2.99.

FAQ

How long is a Let's Encrypt certificate valid?

90 days by default today. Let's Encrypt is shortening that in stages: the default profile moves to 64 days on 10 February 2027 and to 45 days on 16 February 2028. With automated renewal you won't notice.

Does a Let's Encrypt certificate cost anything?

No. Let's Encrypt is a non-profit certificate authority and issues domain-validated certificates for free. It doesn't offer organisation-validated or EV certificates.

Why does certbot fail with 'Timeout during connect'?

Let's Encrypt can't reach your server on port 80. Usually it's a firewall, an AAAA record without working IPv6, or an A record still pointing elsewhere. The IP in the error message shows which address was tried.

What happens if I hit a rate limit?

certbot stops with 'too many certificates already issued' and says when you can retry. For the limit of five identical certificates per week, waiting is the only option, which is why you test against staging with --test-cert.