Let’s Encrypt with Certbot – Automated Renewal

Let’s Encrypt changed how people think about SSL. Before it existed, getting a certificate meant paying $50–$200/year and going through manual validation. Now I can have a valid, trusted certificate on a new server in under two minutes. The certificates are free, the process is automated, and renewal happens without any manual intervention once you set it up.

Certbot is the official Let’s Encrypt client, maintained by the EFF. It handles certificate issuance and renewal, and it has plugins for Nginx, Apache, and standalone mode.

Installing Certbot

On Ubuntu 20.04+:

sudo apt update
sudo apt install certbot python3-certbot-nginx

For Apache instead of Nginx:

sudo apt install certbot python3-certbot-apache

On RHEL/AlmaLinux/Rocky Linux, enable EPEL first:

sudo dnf install epel-release
sudo dnf install certbot python3-certbot-nginx

Check the version:

certbot --version

Obtaining Your First Certificate: Nginx

The simplest approach with Nginx is the --nginx plugin, which modifies your Nginx config automatically:

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

Certbot will:

  1. Verify domain ownership by placing a file in your web root
  2. Issue the certificate from Let’s Encrypt
  3. Modify your Nginx config to use the new certificate and add HTTPS settings
  4. Optionally set up an HTTP→HTTPS redirect

I usually do this interactively the first time to see what it does. The prompts ask for an email (used for expiry notices) and whether to redirect HTTP traffic.

After completion, certificates land in /etc/letsencrypt/live/example.com/ with these files:

  • cert.pem — your domain certificate
  • chain.pem — intermediate certificates
  • fullchain.pem — cert + chain combined (this is what Nginx’s ssl_certificate should point to)
  • privkey.pem — private key

These are actually symlinks to the current version in /etc/letsencrypt/archive/. The symlinks update automatically on renewal, so your Nginx config keeps working after renewals without any path changes.

Standalone Mode: When Nginx Isn’t Running

If you’re issuing a certificate before your web server is configured, or on a server without a web server, use standalone mode:

sudo certbot certonly --standalone -d example.com -d www.example.com

Certbot spins up a temporary web server on port 80 for domain validation, then stops it. No existing web server needed. But port 80 must be available and reachable from the internet — so stop any existing web server first.

Webroot Mode: Without Touching Server Config

If you want the certificate without Certbot modifying your web server config, use webroot mode:

sudo certbot certonly --webroot -w /var/www/example.com -d example.com -d www.example.com

Certbot places validation files in /.well-known/acme-challenge/ within your document root and serves them through your existing web server. Your server must serve that path over HTTP without redirecting to HTTPS (or Certbot won’t be able to validate).

If you have multiple domains with different document roots:

sudo certbot certonly --webroot \
  -w /var/www/example.com -d example.com -d www.example.com \
  -w /var/www/other.com -d other.com

DNS Challenge: For Wildcard Certificates

Let’s Encrypt supports wildcard certificates (*.example.com) but only through DNS-01 challenge validation. Instead of serving a file over HTTP, you create a DNS TXT record to prove domain ownership.

sudo certbot certonly --manual --preferred-challenges dns \
  -d example.com -d "*.example.com"

This is manual by default — Certbot will show you the TXT record to create and wait for you to confirm. The record looks like:

_acme-challenge.example.com  TXT  "some-random-string"

Create it in your DNS provider, wait a minute for propagation, then press Enter in Certbot.

For automated wildcard renewal, you need a DNS provider plugin. Certbot has plugins for Cloudflare, Route53, DigitalOcean, and others:

sudo apt install python3-certbot-dns-cloudflare

# Configure credentials
sudo nano /etc/letsencrypt/cloudflare.ini
# Contents: dns_cloudflare_api_token = your_token_here
sudo chmod 600 /etc/letsencrypt/cloudflare.ini

# Issue wildcard cert
sudo certbot certonly \
  --dns-cloudflare \
  --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
  -d "*.example.com" -d example.com

This way, renewal is fully automatic — the Certbot renewal process creates the DNS record, waits, validates, and cleans up without any manual steps.

Automated Renewal

Let’s Encrypt certificates expire after 90 days. Certbot installs a systemd timer (or cron job) that runs renewal checks twice daily. Check if it’s active:

systemctl status certbot.timer

To test renewal without actually renewing (useful for verifying the process works):

sudo certbot renew --dry-run

If this succeeds, your auto-renewal is configured correctly. If it fails, you’ll see the error in the output. Fix it before your actual certificate expires.

The actual renewal command (runs automatically but can also be triggered manually):

sudo certbot renew

Certbot only renews certificates within 30 days of expiry by default. Running certbot renew on a fresh certificate does nothing — it just reports that renewal isn’t needed yet.

Post-Renewal Hooks

After a certificate renews, your web server needs to reload to pick up the new files. Certbot supports hooks for this:

# Reload Nginx after renewal
echo '#!/bin/bash\nsystemctl reload nginx' | sudo tee /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh

Or for Apache:

echo '#!/bin/bash\nsystemctl reload apache2' | sudo tee /etc/letsencrypt/renewal-hooks/deploy/reload-apache.sh
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-apache.sh

The deploy directory contains hooks that run after every successful renewal. There are also pre (runs before renewal attempt) and post (runs after the attempt, whether successful or not) hook directories.

When using the --nginx or --apache plugins, Certbot sets up reload hooks automatically. For manual configurations or webroot mode, you need to add them yourself.

Rate Limits to Know About

Let’s Encrypt is free but has rate limits:

  • 50 certificates per registered domain per week
  • 5 duplicate certificates for the same set of domains per week
  • 5 failed validation attempts per domain per hour

The last one matters most during testing. If you’re experimenting with configuration and repeatedly failing validation, you’ll hit the limit quickly. Always use --dry-run or the staging environment when testing:

# Use staging environment (issues untrusted test certificates)
sudo certbot --nginx -d example.com --staging

Staging certificates show as untrusted in browsers but don’t count against rate limits. Switch to the production environment once your setup works.

Revoking and Deleting a Certificate

If a private key is compromised, revoke the certificate immediately:

sudo certbot revoke --cert-path /etc/letsencrypt/live/example.com/cert.pem

To delete a certificate (stop managing it without revoking):

sudo certbot delete --cert-name example.com

This removes it from Certbot’s management and deletes the files, but doesn’t revoke it. The certificate stays valid until it expires.

Listing All Managed Certificates

sudo certbot certificates

This shows every certificate Certbot manages, including expiry dates, associated domains, and paths. I run this on any server I’m taking over to understand what certificates are in play before making changes.

Let’s Encrypt and Certbot have genuinely made SSL a default rather than an optional extra. The 90-day renewal cycle forces automation, which is actually better than annual renewals — annual certs often expire because someone forgot. With Certbot running on a timer and a well-tested renewal hook, SSL maintenance essentially disappears from your todo list.

Scroll to Top