Skip to content
Published on

[DevOps] Let's Encrypt and Certbot Complete Guide: Free TLS Certificates

Authors

1. TLS/SSL Certificate Fundamentals

1.1 Why TLS Certificates Are Needed

TLS (Transport Layer Security) certificates provide three core security elements for web communication.

  • Encryption: Encrypts data between client and server to prevent eavesdropping
  • Authentication: Proves the server is the real owner of the domain
  • Integrity: Guarantees data was not tampered with during transmission

1.2 Certificate Chain Structure

┌───────────────────────┐
│   Root CA Certificate │  <- Trust anchor embedded in browsers/OS
│  (Self-Signed)        │
└───────────┬───────────┘
            │ Signs
┌───────────▼───────────┐
│ Intermediate CA Cert  │  <- Intermediate CA certificate
│ (Signed by Root CA)   │
└───────────┬───────────┘
            │ Signs
┌───────────▼───────────┐
│  Server Certificate   │  <- Domain certificate (Leaf)
│ (Signed by Inter. CA) │
└───────────────────────┘

1.3 Public/Private Key Pairs

  • Private Key: Held only by the server, must never be leaked
  • CSR (Certificate Signing Request): Public key + domain info, submitted to CA
  • Certificate: CA-signed public key + domain info

2. What Is Let's Encrypt

2.1 Overview

Let's Encrypt is a free, automated, open Certificate Authority (CA) operated by ISRG (Internet Security Research Group).

Key features:

  • Free: Issues Domain Validation (DV) certificates at no cost
  • Automated: Fully automates certificate issuance/renewal via ACME protocol
  • Open: Uses open-source protocols and tools
  • Validity: 90 days (short cycle enhances security)
  • Trusted: Trusted by all major browsers and operating systems

2.2 Certificate Type Comparison

TypeValidation LevelIssuance TimeCostLet's Encrypt
DV (Domain Validation)Domain ownershipMinutesFree to lowSupported
OV (Organization Validation)OrganizationDaysPaidNot supported
EV (Extended Validation)ExtendedWeeksExpensiveNot supported

3. ACME Protocol Mechanics

ACME (Automatic Certificate Management Environment) is a protocol that automates certificate issuance.

3.1 Overall Flow

┌────────┐                          ┌──────────────┐
│ Certbot│                          │ Let's Encrypt│
│(Client)│                          │   (CA/ACME)  │
└───┬────┘                          └──────┬───────┘
    │  1. Account Registration              │
    │──────────────────────────────────────>│
    │  2. Account Created                   │
    │<──────────────────────────────────────│
    │                                       │
    │  3. Order (domain list)               │
    │──────────────────────────────────────>│
    │  4. Authorizations + Challenges       │
    │<──────────────────────────────────────│
    │                                       │
    │  5. Respond to Challenge              │
    │  (HTTP-01 / DNS-01 / TLS-ALPN-01)    │
    │──────────────────────────────────────>│
    │                                       │
    │  6. Challenge Validated               │
    │<──────────────────────────────────────│
    │                                       │
    │  7. Finalize (send CSR)               │
    │──────────────────────────────────────>│
    │  8. Certificate issued                │
    │<──────────────────────────────────────│

3.2 HTTP-01 Challenge

The most common challenge type. Validates that a specific file is accessible over port 80.

Let's Encrypt -> http://yourdomain.com/.well-known/acme-challenge/TOKEN_VALUE

Process:
1. Certbot places a token file on the web server
2. Let's Encrypt accesses the file via HTTP
3. If content matches expected value, domain ownership is confirmed

Advantages:

  • Simplest and most common
  • No additional DNS configuration needed
  • Easy to set up on most web servers

Limitations:

  • Port 80 must be externally accessible
  • Cannot issue wildcard certificates
  • Configuration can be complex behind load balancers

3.3 DNS-01 Challenge

Proves domain ownership by creating a DNS TXT record. Required for wildcard certificates.

Let's Encrypt -> DNS lookup: _acme-challenge.yourdomain.com TXT

Process:
1. Certbot calculates a token
2. Set the token value in _acme-challenge.yourdomain.com TXT record
3. Let's Encrypt queries DNS to verify the value
4. If it matches, domain ownership is confirmed

Advantages:

  • Can issue wildcard certificates
  • Works without a web server
  • Port 80 does not need to be open
  • One certificate can be used across multiple servers

Limitations:

  • DNS Provider API needed (for automation)
  • Possible DNS propagation delay
  • Manual setup can be tedious

3.4 TLS-ALPN-01 Challenge

Validates through the ALPN (Application-Layer Protocol Negotiation) extension during TLS connection on port 443.

3.5 Challenge Comparison

FeatureHTTP-01DNS-01TLS-ALPN-01
Port80None (DNS)443
WildcardNoYesNo
Web Server RequiredYesNoYes
Automation DifficultyLowMedium (DNS API needed)High
DNS Setup RequiredNoYesNo
Primary UseGeneral web serversWildcards, internal serversSpecial environments

4. Certbot Installation

4.1 Ubuntu / Debian

# Install via snap (recommended)
sudo snap install core
sudo snap refresh core
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot

4.2 CentOS / RHEL / Rocky Linux

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

4.3 macOS

brew install certbot

4.4 Docker

docker run -it --rm \
  -v /etc/letsencrypt:/etc/letsencrypt \
  -v /var/lib/letsencrypt:/var/lib/letsencrypt \
  certbot/certbot certonly --help

5. Certificate Issuance Methods

5.1 Standalone Mode

Certbot runs its own web server to handle the HTTP-01 challenge. The existing web server must be stopped.

sudo systemctl stop nginx

sudo certbot certonly --standalone \
  -d example.com \
  -d www.example.com \
  --agree-tos \
  --email admin@example.com \
  --non-interactive

sudo systemctl start nginx

5.2 Webroot Mode

Issues certificates without stopping the existing web server. The web server must serve a specific directory.

Nginx configuration:

server {
    listen 80;
    server_name example.com www.example.com;

    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    location / {
        return 301 https://$host$request_uri;
    }
}
sudo mkdir -p /var/www/certbot

sudo certbot certonly --webroot \
  -w /var/www/certbot \
  -d example.com \
  -d www.example.com \
  --agree-tos \
  --email admin@example.com

5.3 Nginx Plugin

Certbot automatically modifies the Nginx configuration.

sudo certbot --nginx \
  -d example.com \
  -d www.example.com \
  --agree-tos \
  --email admin@example.com

5.4 DNS Plugin (Wildcard Certificates)

Wildcard certificates require the DNS-01 challenge.

Cloudflare DNS Plugin:

sudo snap install certbot-dns-cloudflare

cat > /etc/letsencrypt/cloudflare.ini << 'CFEOF'
dns_cloudflare_api_token = YOUR_CLOUDFLARE_API_TOKEN
CFEOF
sudo chmod 600 /etc/letsencrypt/cloudflare.ini

sudo certbot certonly \
  --dns-cloudflare \
  --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
  -d "example.com" \
  -d "*.example.com" \
  --agree-tos \
  --email admin@example.com

AWS Route 53 DNS Plugin:

sudo snap install certbot-dns-route53

sudo certbot certonly \
  --dns-route53 \
  -d "example.com" \
  -d "*.example.com" \
  --agree-tos \
  --email admin@example.com

6. Issued File Structure

/etc/letsencrypt/live/example.com/
  cert.pem       # Server certificate only
  chain.pem      # Intermediate CA certificate chain
  fullchain.pem  # cert.pem + chain.pem (used by servers)
  privkey.pem    # Private key
FileContentsUsage
cert.pemServer certificateStandalone use (rarely used)
chain.pemIntermediate CA certFor OCSP Stapling
fullchain.pemServer cert + Intermediate CANginx ssl_certificate
privkey.pemPrivate keyNginx ssl_certificate_key

Apply to Nginx:

server {
    listen 443 ssl;
    server_name example.com;

    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
}

7. Automatic Renewal Setup

7.1 Renewal Test

sudo certbot renew --dry-run
# /etc/systemd/system/certbot-renew.timer
[Unit]
Description=Certbot renewal timer

[Timer]
OnCalendar=*-*-* 00,12:00:00
RandomizedDelaySec=3600
Persistent=true

[Install]
WantedBy=timers.target
# /etc/systemd/system/certbot-renew.service
[Unit]
Description=Certbot renewal service

[Service]
Type=oneshot
ExecStart=/usr/bin/certbot renew --quiet --deploy-hook "systemctl reload nginx"
sudo systemctl daemon-reload
sudo systemctl enable --now certbot-renew.timer

7.3 Crontab

# crontab -e
0 2,14 * * * certbot renew --quiet --deploy-hook "systemctl reload nginx"

7.4 Renewal Hooks

# Deploy hook (runs only when renewal succeeds)
sudo mkdir -p /etc/letsencrypt/renewal-hooks/deploy
cat > /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh << 'HOOKEOF'
#!/bin/bash
systemctl reload nginx
echo "Certificate renewed and nginx reloaded at $(date)" >> /var/log/certbot-deploy.log
HOOKEOF
chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh

8. Rate Limits

Let's Encrypt applies the following rate limits to prevent abuse.

LimitValueDescription
Certificates per Registered Domain50/week50 per domain per week
Duplicate Certificate5/week5 for identical domain sets
Failed Validations5/hour/account/hostnameValidation failure limit
New Orders300/3 hoursNew order limit
Accounts per IP10/3 hoursAccount creation per IP

Use the Staging Environment:

# Use staging server for testing (much more lenient rate limits)
sudo certbot certonly --standalone \
  --staging \
  -d example.com \
  --agree-tos \
  --email admin@example.com

9. Alternative Tools

9.1 acme.sh

A pure shell script ACME client.

curl https://get.acme.sh | sh

acme.sh --issue -d example.com -w /var/www/html

# Wildcard with DNS API
acme.sh --issue \
  -d example.com \
  -d "*.example.com" \
  --dns dns_cf \
  --dnssleep 120

# Install to Nginx
acme.sh --install-cert -d example.com \
  --key-file       /etc/nginx/ssl/example.com.key \
  --fullchain-file /etc/nginx/ssl/example.com.fullchain.pem \
  --reloadcmd     "systemctl reload nginx"

9.2 Caddy (Automatic HTTPS)

Caddy handles HTTPS automatically within the web server itself.

# Caddyfile
example.com {
    root * /var/www/html
    file_server
}
# This alone auto-issues and renews Let's Encrypt certificates!

9.3 cert-manager (Kubernetes)

Automatically manages certificates in Kubernetes environments.

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: admin@example.com
    privateKeySecretRef:
      name: letsencrypt-prod-key
    solvers:
      - http01:
          ingress:
            class: nginx
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-app
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  tls:
    - hosts:
        - app.example.com
      secretName: app-tls-secret
  rules:
    - host: app.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: my-app
                port:
                  number: 80
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: app-certificate
  namespace: default
spec:
  secretName: app-tls-secret
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer
  dnsNames:
    - app.example.com
    - api.example.com
  renewBefore: 720h # Renew 30 days before expiry

10. Troubleshooting

10.1 Common Errors and Solutions

Challenge failure:

# Verify HTTP-01 challenge file access
curl -v http://yourdomain.com/.well-known/acme-challenge/test

# Check firewall (port 80)
sudo ufw status
sudo iptables -L -n | grep 80

# Check DNS
dig +short yourdomain.com

Certificate renewal failure:

sudo certbot certificates
sudo certbot renew --dry-run -v
sudo cat /var/log/letsencrypt/letsencrypt.log

Certificate info check:

# View certificate contents
openssl x509 -in /etc/letsencrypt/live/example.com/cert.pem -text -noout

# Check expiration date
openssl x509 -in /etc/letsencrypt/live/example.com/cert.pem -enddate -noout

# Check remote server certificate
openssl s_client -connect example.com:443 -servername example.com < /dev/null 2>/dev/null | \
  openssl x509 -text -noout

11. Security Best Practices

  1. Protect private keys: Set privkey.pem permissions to 600, readable only by root
  2. Monitor automatic renewal: Set up alerts for renewal failures (email, Slack, etc.)
  3. Staging first: Always test new configurations in the staging environment
  4. Apply HSTS: Configure HSTS headers after certificate issuance
  5. Certificate Transparency monitoring: Monitor domain certificate issuance at crt.sh
  6. Backup: Regularly back up the /etc/letsencrypt directory

12. Conclusion

Let's Encrypt and Certbot have become the standard for free TLS certificates. Key takeaways:

  • HTTP-01 challenge is simplest, but wildcards require DNS-01
  • Certbot Webroot or Nginx plugin is most practical
  • Automatic renewal via systemd timer or crontab
  • cert-manager is the standard in Kubernetes environments
  • Understand rate limits and use staging server for testing
  • After certificate issuance, always optimize Nginx TLS security settings