All topics
DevOps · Learning hub

Let's Encrypt notes for developers

Master Let's Encrypt with a curated set of 2 developer notes — core concepts, patterns, and interview prep. Maintained by the DevRecall team.

Save this stack to your DevRecallMore DevOps notes
Let's Encrypt

Certbot, ACME Protocol & Certificate Issuance

Let's Encrypt: Certbot & ACME Let's Encrypt is a free, automated Certificate Authority. Certificates are issued via the ACME protocol — no manual validation or

Let's Encrypt: Certbot & ACME

Let's Encrypt is a free, automated Certificate Authority. Certificates are issued via the ACME protocol — no manual validation or payment. Certificates last 90 days and are designed to be auto-renewed.

How ACME Works

ACME (Automatic Certificate Management Environment — RFC 8555):

1. Client generates a key pair, registers with Let's Encrypt CA
2. Client requests a certificate for domain.com
3. CA issues a challenge — prove you control domain.com:

   HTTP-01 challenge: serve a token at
     http://domain.com/.well-known/acme-challenge/<token>
     Requires: port 80 open, no CDN blocking the path
     Works for: single domains

   DNS-01 challenge: add a TXT record
     _acme-challenge.domain.com = <token>
     Requires: DNS API access (or manual)
     Works for: wildcards (*.domain.com), private servers, no port 80

   TLS-ALPN-01: serve challenge via TLS on port 443
     Requires: port 443 open

4. CA validates the challenge, issues signed certificate
5. Client stores certificate + private key

Rate limits (free tier):
  50 certificates per registered domain per week
  5 duplicate certificates per week
  5 failures per hour (use --staging for testing!)

Certbot Installation

# Ubuntu/Debian
sudo apt install snapd
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot

# macOS (testing/dev only)
brew install certbot

# CentOS/RHEL
sudo dnf install epel-release
sudo dnf install certbot python3-certbot-nginx

Issuing Certificates

# Nginx plugin — auto-configures Nginx, handles HTTP-01 challenge
sudo certbot --nginx -d example.com -d www.example.com

# Apache plugin
sudo certbot --apache -d example.com

# Standalone (temporary HTTP server — stop nginx/apache first)
sudo certbot certonly --standalone -d example.com

# Webroot (nginx/apache keeps running, serves challenge files)
sudo certbot certonly --webroot   --webroot-path /var/www/html   -d example.com -d www.example.com

# DNS-01 (for wildcards — requires DNS plugin or manual)
sudo certbot certonly --manual   --preferred-challenges dns   -d '*.example.com' -d example.com

# TEST FIRST with staging (avoids hitting rate limits)
sudo certbot certonly --staging --nginx -d example.com

# Non-interactive (for scripts/CI)
sudo certbot certonly --nginx   --non-interactive   --agree-tos   --email admin@example.com   -d example.com   -d www.example.com

Certificate Files

# Certificates stored in: /etc/letsencrypt/live/example.com/
ls /etc/letsencrypt/live/example.com/

# cert.pem      — server certificate only (leaf)
# chain.pem     — intermediate certificates
# fullchain.pem — cert.pem + chain.pem (use this for ssl_certificate)
# privkey.pem   — private key (KEEP PRIVATE)

# Check certificate details
sudo openssl x509 -in /etc/letsencrypt/live/example.com/cert.pem -noout -text

# Check expiry
sudo openssl x509 -in /etc/letsencrypt/live/example.com/cert.pem   -noout -dates

# Check which domains are covered
sudo openssl x509 -in /etc/letsencrypt/live/example.com/cert.pem   -noout -ext subjectAltName
Let's Encrypt

Nginx Config, Wildcards & Auto-renewal

Let's Encrypt: Nginx Config, Wildcards & Renewal Nginx Configuration # /etc/nginx/sites-available/example.com server { listen 80; server_name example.com www.ex

Let's Encrypt: Nginx Config, Wildcards & Renewal

Nginx Configuration

# /etc/nginx/sites-available/example.com
server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    http2 on;
    server_name example.com www.example.com;

    # Let's Encrypt certificates
    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;   # generated by certbot
    ssl_dhparam         /etc/letsencrypt/ssl-dhparams.pem;         # generated by certbot

    # HSTS
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;

    root /var/www/example.com/html;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }

    # For reverse proxy (Node.js, Rails, etc.)
    location /api/ {
        proxy_pass http://localhost:3000;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

# Enable site
# sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
# sudo nginx -t && sudo systemctl reload nginx

Wildcard Certificates with DNS-01

# Wildcard requires DNS-01 challenge (HTTP-01 can't validate *.domain.com)
# Use a DNS provider plugin for automation

# Cloudflare DNS plugin
pip install certbot-dns-cloudflare
# or: sudo snap install certbot-dns-cloudflare

# Create Cloudflare credentials file
cat > /etc/letsencrypt/cloudflare.ini << 'EOF'
dns_cloudflare_email = your@email.com
dns_cloudflare_api_key = your_global_api_key
# Or use API token (more secure):
# dns_cloudflare_api_token = your_api_token
EOF
chmod 600 /etc/letsencrypt/cloudflare.ini

# Issue wildcard certificate
sudo certbot certonly   --dns-cloudflare   --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini   -d example.com   -d '*.example.com'

# Other DNS plugins: certbot-dns-route53, certbot-dns-digitalocean,
#                   certbot-dns-google, certbot-dns-linode

# Manual DNS-01 (when no plugin available)
sudo certbot certonly --manual --preferred-challenges dns   -d '*.example.com'
# Follow prompts: add TXT record _acme-challenge.example.com
# Verify record propagated before pressing Enter:
# dig TXT _acme-challenge.example.com @8.8.8.8

Auto-renewal

# Certbot installs a systemd timer or cron job automatically
# Verify it's active
sudo systemctl status certbot.timer
sudo systemctl list-timers | grep certbot

# Or check cron
sudo cat /etc/cron.d/certbot

# Test renewal (dry run — doesn't actually renew)
sudo certbot renew --dry-run

# Force renewal (even if cert isn't expiring soon)
sudo certbot renew --force-renewal

# Renew specific domain only
sudo certbot renew --cert-name example.com

# Hook: reload nginx after renewal
sudo certbot renew   --deploy-hook "systemctl reload nginx"

# Or add to /etc/letsencrypt/renewal/example.com.conf:
# [renewalparams]
# post_hook = systemctl reload nginx

# List all certificates and expiry dates
sudo certbot certificates

Docker & Cloud Alternatives

# Traefik (auto-TLS for Docker/Kubernetes)
# docker-compose.yml
# traefik:
#   image: traefik:v3
#   command:
#     - --providers.docker=true
#     - --entrypoints.web.address=:80
#     - --entrypoints.websecure.address=:443
#     - --certificatesresolvers.le.acme.tlschallenge=true
#     - --certificatesresolvers.le.acme.email=admin@example.com
#     - --certificatesresolvers.le.acme.storage=/letsencrypt/acme.json
#   volumes:
#     - /var/run/docker.sock:/var/run/docker.sock
#     - ./letsencrypt:/letsencrypt
#   ports: ["80:80", "443:443"]

# Caddy (auto-TLS built-in — simplest option)
# Caddyfile:
# example.com {
#     reverse_proxy localhost:3000
# }
# Caddy handles cert issuance and renewal automatically

# Kubernetes cert-manager
# kubectl apply -f https://github.com/cert-manager/cert-manager/releases/latest/download/cert-manager.yaml
# Then create Issuer + Certificate resources

# Cloud-managed (no Certbot needed)
# - AWS ACM (free, auto-renew, integrates with ALB/CloudFront)
# - GCP Certificate Manager (free, auto-renew)
# - Azure App Service Managed Certificates (free)
  • 90-day certificate lifetime forces automation — manual renewal would be unsustainable.

  • Always use --staging for testing to avoid rate limits (5 failures/hour, 50 certs/domain/week).

  • Monitor certificate expiry: set up alerts at 30 days (UptimeRobot, Datadog, StatusCake — many check TLS expiry for free).

  • Backup /etc/letsencrypt/ — contains all certificates and keys. Losing private keys requires re-issuance.

  • Prefer ECDSA keys: certbot --key-type ecdsa uses smaller, faster ECDSA instead of RSA 2048.

Keep your Let's Encrypt knowledge sharp.

Save this stack to your personal DevRecall — add your own notes, track what you're learning, and share what you know with the community.

Get started — free forever