Home/Blog/Let's Encrypt Complete Guide: Free SSL/TLS Certificates with Certbot & ACME
Security

Let's Encrypt Complete Guide: Free SSL/TLS Certificates with Certbot & ACME

Master Let's Encrypt with this comprehensive guide covering Certbot installation, HTTP-01 and DNS-01 challenges, wildcard certificates, automated renewal, DNS provider integrations, troubleshooting, and rate limits.

By Inventive HQ Team
Let's Encrypt Complete Guide: Free SSL/TLS Certificates with Certbot & ACME

Let's Encrypt Complete Guide: Free SSL/TLS Certificates with Certbot & ACME

Let's Encrypt has revolutionized web security by providing free, automated SSL/TLS certificates. Since launching in 2016, it has secured over 300 million websites and fundamentally changed how we think about HTTPS. This comprehensive guide covers everything from basic Certbot installation to advanced wildcard certificates with DNS provider integrations.

What is Let's Encrypt?

Let's Encrypt is a free, automated, and open Certificate Authority (CA) operated by the nonprofit Internet Security Research Group (ISRG). It issues Domain Validation (DV) certificates at no cost, making HTTPS accessible to everyone.

┌─────────────────────────────────────────────────────────────────────────┐
│                    Let's Encrypt Ecosystem                               │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  YOUR SERVER              ACME PROTOCOL            LET'S ENCRYPT         │
│  ───────────────────────────────────────────────────────────────────────│
│                                                                          │
│  ┌──────────────┐        ┌──────────────┐        ┌──────────────┐       │
│  │ Certbot      │        │ Challenge    │        │ CA Server    │       │
│  │ acme.sh      │───────▶│ HTTP-01 or   │───────▶│ Validates    │       │
│  │ Caddy        │        │ DNS-01       │        │ Issues Cert  │       │
│  └──────────────┘        └──────────────┘        └──────────────┘       │
│         │                                               │                │
│         │◀──────────────────────────────────────────────┘                │
│         │           Certificate (90-day validity)                        │
│         ▼                                                                │
│  ┌──────────────┐                                                        │
│  │ Web Server   │        Auto-renewal every 60 days                      │
│  │ Nginx/Apache │        via systemd timer or cron                       │
│  └──────────────┘                                                        │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘

Why Choose Let's Encrypt?

FeatureLet's EncryptPaid DV Certificates
CostFree$10-$100/year
AutomationBuilt-in ACMEOften manual
Validity90 days1-2 years
Issuance SpeedSecondsMinutes to hours
Wildcard SupportYes (DNS-01)Yes
Browser TrustAll major browsersAll major browsers
SupportCommunityPaid support

Key Benefits:

  • Free forever - No credit card, no trial period, no premium tier
  • Automated - ACME protocol enables hands-off renewal
  • Trusted - Root certificates in all major browser trust stores
  • Secure - Same encryption strength as paid certificates
  • Open - Open source tools, transparent operations

Rate Limits & Quotas

Understanding rate limits is crucial to avoid certificate issuance failures:

Production Rate Limits

Limit TypeValueReset Period
Certificates per Registered Domain50Per week (rolling)
Duplicate Certificates5Per week
Failed Validations5 per hostnamePer hour
New Orders300 per accountPer 3 hours
Accounts per IP10Per 3 hours
Pending Authorizations300Per account

Important Notes on Rate Limits

Renewals don't count against the 50/week limit if the certificate has the exact same set of domain names.

Registered Domain means the base domain (example.com), not subdomains. So certificates for www.example.com, api.example.com, and mail.example.com all count against example.com's limit.

Duplicate Certificates are exact matches - same set of domain names. Changing even one SAN creates a new certificate, not a duplicate.

Staging Environment (Testing)

Always test with staging first:

# Staging has much higher limits - 30,000 certs/week
certbot certonly --staging -d example.com

# Test your entire workflow
certbot renew --staging --dry-run

# When ready, remove --staging for production
certbot certonly -d example.com

The staging environment issues certificates signed by a fake root ("Fake LE Root X1"), which browsers don't trust but are perfect for testing automation.

Certbot Installation

Certbot is the official ACME client from the Electronic Frontier Foundation (EFF).

Ubuntu/Debian

# Update package list
sudo apt update

# Install Certbot
sudo apt install certbot

# For Nginx integration
sudo apt install python3-certbot-nginx

# For Apache integration
sudo apt install python3-certbot-apache

# Verify installation
certbot --version

CentOS/RHEL 8+

# Enable EPEL repository
sudo dnf install epel-release

# Install Certbot
sudo dnf install certbot

# For Nginx
sudo dnf install python3-certbot-nginx

# For Apache
sudo dnf install python3-certbot-apache

macOS (Homebrew)

# Install via Homebrew
brew install certbot

# Verify
certbot --version

Docker

# Run Certbot in Docker
docker run -it --rm \
  -v /etc/letsencrypt:/etc/letsencrypt \
  -v /var/lib/letsencrypt:/var/lib/letsencrypt \
  -v /var/www/html:/var/www/html \
  certbot/certbot certonly --webroot \
  -w /var/www/html \
  -d example.com

# For DNS plugins, use specific images
docker run -it --rm \
  -v /etc/letsencrypt:/etc/letsencrypt \
  certbot/dns-cloudflare certonly \
  --dns-cloudflare \
  -d example.com

HTTP-01 Challenge (Standard)

HTTP-01 is the most common challenge type. Let's Encrypt places a file on your server and verifies it's accessible.

How HTTP-01 Works

┌─────────────────────────────────────────────────────────────────────────┐
│                    HTTP-01 Challenge Flow                                │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  1. Request        2. Challenge         3. Validation      4. Issue     │
│                                                                          │
│  ┌──────────┐     ┌──────────┐        ┌──────────┐      ┌──────────┐   │
│  │ Certbot  │────▶│ LE sends │───────▶│ LE checks│─────▶│ LE issues│   │
│  │ requests │     │ token    │        │ HTTP GET │      │ cert     │   │
│  │ cert     │     │          │        │ /.well-  │      │          │   │
│  └──────────┘     └──────────┘        │ known/   │      └──────────┘   │
│                        │               │ acme-    │                     │
│                        │               │ challenge│                     │
│                        ▼               └──────────┘                     │
│               ┌──────────────────┐           │                          │
│               │ Certbot places   │           │                          │
│               │ file on server   │◀──────────┘                          │
│               │ port 80 required │                                      │
│               └──────────────────┘                                      │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘

Requirements:

  • Port 80 must be accessible from the internet
  • Domain must resolve to your server's IP
  • Server must respond to HTTP requests

Standalone Mode

Certbot runs its own temporary web server. Best when no web server is running:

# Stop any existing web server first
sudo systemctl stop nginx

# Run standalone mode
sudo certbot certonly --standalone -d example.com -d www.example.com

# Start web server again
sudo systemctl start nginx

Use when: No web server installed, testing, or one-time certificate issuance.

Webroot Mode

Certbot places files in an existing web server's document root:

# Specify webroot directory
sudo certbot certonly --webroot \
  -w /var/www/html \
  -d example.com \
  -d www.example.com

# Multiple webroots for different domains
sudo certbot certonly --webroot \
  -w /var/www/example \
  -d example.com \
  -w /var/www/other \
  -d other.com

Nginx configuration to serve ACME challenges:

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

    # ACME challenge location
    location /.well-known/acme-challenge/ {
        root /var/www/html;
    }

    # Redirect everything else to HTTPS
    location / {
        return 301 https://$host$request_uri;
    }
}

Nginx Plugin

Certbot automatically configures Nginx:

# Obtain and install certificate
sudo certbot --nginx -d example.com -d www.example.com

# Obtain certificate only (manual installation)
sudo certbot certonly --nginx -d example.com

# Non-interactive with email
sudo certbot --nginx -d example.com \
  --non-interactive \
  --agree-tos \
  --email [email protected]

The plugin:

  • Modifies Nginx configuration automatically
  • Sets up HTTPS server block
  • Configures redirects (optional)
  • Reloads Nginx after changes

Apache Plugin

# Obtain and install certificate
sudo certbot --apache -d example.com -d www.example.com

# Obtain only
sudo certbot certonly --apache -d example.com

DNS-01 Challenge (Wildcards)

DNS-01 is required for wildcard certificates and works when port 80 isn't available.

Why DNS-01?

┌─────────────────────────────────────────────────────────────────────────┐
│                   DNS-01 vs HTTP-01 Comparison                           │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  HTTP-01                              DNS-01                             │
│  ───────────────────────────────────────────────────────────────────────│
│                                                                          │
│  ✓ Simple setup                       ✓ Wildcard certificates           │
│  ✓ No DNS access needed               ✓ Works behind firewalls          │
│  ✓ Works with any DNS provider        ✓ No port 80 required             │
│                                       ✓ Can issue from different server │
│  ✗ Requires port 80 open                                                │
│  ✗ No wildcard support                ✗ Requires DNS API access         │
│  ✗ Server must be publicly accessible ✗ More complex setup              │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘

Cloudflare Integration

Most popular DNS-01 integration:

# Install Cloudflare plugin
sudo apt install python3-certbot-dns-cloudflare

# Create credentials file
sudo mkdir -p /etc/letsencrypt/cloudflare
sudo nano /etc/letsencrypt/cloudflare/credentials.ini

credentials.ini:

# Cloudflare API token (recommended - limited permissions)
dns_cloudflare_api_token = YOUR_API_TOKEN

# OR legacy API key (full account access - not recommended)
# dns_cloudflare_email = [email protected]
# dns_cloudflare_api_key = YOUR_GLOBAL_API_KEY
# Secure the credentials file
sudo chmod 600 /etc/letsencrypt/cloudflare/credentials.ini

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

Creating a Cloudflare API Token:

  1. Go to Cloudflare Dashboard → My Profile → API Tokens
  2. Create Token → Edit zone DNS template
  3. Zone Resources: Include → Specific zone → your domain
  4. Copy token to credentials.ini

AWS Route53 Integration

# Install Route53 plugin
sudo apt install python3-certbot-dns-route53

# Configure AWS credentials (use IAM role if on EC2)
aws configure
# Or set environment variables:
export AWS_ACCESS_KEY_ID=your_key
export AWS_SECRET_ACCESS_KEY=your_secret

# Request certificate
sudo certbot certonly \
  --dns-route53 \
  -d example.com \
  -d "*.example.com"

Minimum IAM policy:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "route53:ListHostedZones",
        "route53:GetChange"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": "route53:ChangeResourceRecordSets",
      "Resource": "arn:aws:route53:::hostedzone/YOUR_ZONE_ID"
    }
  ]
}

Other DNS Providers

Popular DNS plugins:

ProviderPlugin PackageCommand Flag
DigitalOceancertbot-dns-digitalocean--dns-digitalocean
Google Cloudcertbot-dns-google--dns-google
Linodecertbot-dns-linode--dns-linode
OVHcertbot-dns-ovh--dns-ovh
RFC 2136certbot-dns-rfc2136--dns-rfc2136

Manual DNS Verification

For one-time certificates or unsupported DNS providers:

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

# Certbot will display:
# Please deploy a DNS TXT record under the name:
# _acme-challenge.example.com
# with the following value:
# AbCdEf123456...
#
# Before continuing, verify the TXT record has propagated.

# Add the TXT record via your DNS provider's dashboard
# Verify propagation:
dig -t TXT _acme-challenge.example.com

# Press Enter in Certbot when record is propagated

Note: Manual mode doesn't support automated renewal. Use a DNS plugin for production.

Automated Renewal

Let's Encrypt certificates are valid for 90 days. Automated renewal is essential.

Certbot installs a systemd timer by default on most Linux distributions:

# Check timer status
sudo systemctl status certbot.timer

# View timer schedule
sudo systemctl list-timers | grep certbot

# Timer runs twice daily, renews if within 30 days of expiry

Timer configuration (/lib/systemd/system/certbot.timer):

[Unit]
Description=Run certbot twice daily

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

[Install]
WantedBy=timers.target

Service configuration (/lib/systemd/system/certbot.service):

[Unit]
Description=Certbot
Documentation=https://certbot.eff.org/docs

[Service]
Type=oneshot
ExecStart=/usr/bin/certbot renew --quiet
PrivateTmp=true

Cron Job

Alternative to systemd timer:

# Edit crontab
sudo crontab -e

# Add renewal job (runs at 3 AM and 3 PM daily)
0 3,15 * * * certbot renew --quiet --post-hook "systemctl reload nginx"

Post-Renewal Hooks

Execute commands after successful renewal:

# Global hook (runs after any renewal)
sudo mkdir -p /etc/letsencrypt/renewal-hooks/deploy
sudo nano /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh

reload-nginx.sh:

#!/bin/bash
systemctl reload nginx
# Make executable
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh

# Or specify in renewal config
sudo nano /etc/letsencrypt/renewal/example.com.conf

Add to renewal config:

[renewalparams]
post_hook = systemctl reload nginx

Testing Renewal

Always test before relying on automation:

# Dry run - simulates renewal without making changes
sudo certbot renew --dry-run

# Force renewal (even if not due)
sudo certbot renew --force-renewal

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

Troubleshooting Common Errors

Rate Limit Errors

Error: too many certificates already issued for exact set of domains

# Check current certificates
certbot certificates

# Wait for rate limit reset (1 week rolling window)
# Or use staging environment for testing
certbot certonly --staging -d example.com

Error: too many failed authorizations recently

Wait 1 hour, then fix the underlying validation issue before retrying.

Challenge Failures

HTTP-01 failures:

# Check if port 80 is open
curl -I http://example.com/.well-known/acme-challenge/test

# Verify firewall allows port 80
sudo ufw status
sudo iptables -L -n | grep 80

# Check web server is running
sudo systemctl status nginx

# Verify DNS points to your server
dig +short example.com

DNS-01 failures:

# Verify TXT record exists
dig -t TXT _acme-challenge.example.com

# Check propagation (may take up to 48 hours)
# Use multiple DNS servers
dig @8.8.8.8 -t TXT _acme-challenge.example.com
dig @1.1.1.1 -t TXT _acme-challenge.example.com

# Add propagation delay if needed
certbot certonly --dns-cloudflare \
  --dns-cloudflare-propagation-seconds 60 \
  -d example.com

Permission Issues

# Check Certbot directory permissions
ls -la /etc/letsencrypt/

# Fix permissions
sudo chown -R root:root /etc/letsencrypt/
sudo chmod -R 755 /etc/letsencrypt/
sudo chmod 600 /etc/letsencrypt/live/*/privkey.pem

# Fix webroot permissions for HTTP-01
sudo chown -R www-data:www-data /var/www/html/.well-known/

Renewal Failures

# Check renewal configuration
sudo cat /etc/letsencrypt/renewal/example.com.conf

# View renewal logs
sudo cat /var/log/letsencrypt/letsencrypt.log

# Common fixes:
# 1. Ensure web server is running
# 2. Check DNS hasn't changed
# 3. Verify authenticator matches current setup
# 4. Test with --dry-run
sudo certbot renew --dry-run

Certificate Management

View Installed Certificates

# List all certificates
sudo certbot certificates

# Output shows:
# - Certificate name
# - Domains covered
# - Expiry date
# - Certificate and key paths

Delete a Certificate

# Interactive deletion
sudo certbot delete

# Delete specific certificate
sudo certbot delete --cert-name example.com

Revoke a Certificate

# Revoke (use only if key is compromised)
sudo certbot revoke --cert-path /etc/letsencrypt/live/example.com/cert.pem

# Revoke with reason
sudo certbot revoke \
  --cert-path /etc/letsencrypt/live/example.com/cert.pem \
  --reason keycompromise

# Then delete the certificate files
sudo certbot delete --cert-name example.com

Expand Certificate (Add Domains)

# Add new domains to existing certificate
sudo certbot certonly --expand \
  -d example.com \
  -d www.example.com \
  -d api.example.com  # New domain

Staging Environment

Use staging for all testing to avoid rate limits:

# Request staging certificate
sudo certbot certonly --staging \
  --nginx \
  -d example.com

# Staging certificates are signed by "Fake LE Root X1"
# Browsers will show security warning (expected)

# Test renewal with staging
sudo certbot renew --staging --dry-run

# When ready for production, remove --staging
sudo certbot certonly --nginx -d example.com

Staging rate limits:

  • 30,000 certificates per registered domain per week
  • 30,000 duplicate certificates per week
  • 60 failed validations per hour

Best Practices

Security

  1. Secure private keys

    chmod 600 /etc/letsencrypt/live/*/privkey.pem
    
  2. Use API tokens, not global API keys for DNS plugins

  3. Monitor renewal - Set up alerts for expiring certificates

  4. Test changes in staging first

Automation

  1. Use systemd timer or cron - Never rely on manual renewal

  2. Implement post-renewal hooks - Reload web servers automatically

  3. Test renewal regularly

    certbot renew --dry-run
    
  4. Monitor logs

    tail -f /var/log/letsencrypt/letsencrypt.log
    

Multiple Servers

For load-balanced environments:

  1. Centralized certificate management - Use one server to obtain certificates, distribute to others

  2. Shared storage - Mount /etc/letsencrypt on shared filesystem (NFS, EFS)

  3. Or use DNS-01 challenge - Works without routing traffic to specific server

Quick Reference

Common Certbot Commands

# Obtain certificate (Nginx)
certbot --nginx -d example.com

# Obtain certificate (Apache)
certbot --apache -d example.com

# Obtain certificate (standalone)
certbot certonly --standalone -d example.com

# Obtain wildcard (DNS-01)
certbot certonly --dns-cloudflare -d "*.example.com" -d example.com

# Test renewal
certbot renew --dry-run

# Force renewal
certbot renew --force-renewal

# List certificates
certbot certificates

# Delete certificate
certbot delete --cert-name example.com

# Revoke certificate
certbot revoke --cert-path /path/to/cert.pem

Certificate File Locations

/etc/letsencrypt/
├── live/
│   └── example.com/
│       ├── cert.pem       # Server certificate
│       ├── chain.pem      # Intermediate certificates
│       ├── fullchain.pem  # cert.pem + chain.pem
│       └── privkey.pem    # Private key
├── archive/               # Historical certificates
├── renewal/               # Renewal configurations
└── accounts/              # ACME account keys

Nginx SSL Configuration

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

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

    # Modern SSL configuration
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
    ssl_prefer_server_ciphers off;

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

Conclusion

Let's Encrypt has made HTTPS accessible to everyone. With proper setup, Certbot handles certificate issuance and renewal automatically, letting you focus on building great applications rather than managing certificates.

Key takeaways:

  • Use staging for all testing to avoid rate limits
  • Automate renewal with systemd timers or cron
  • DNS-01 is required for wildcard certificates
  • Monitor certificate expiration and renewal logs
  • Test renewal with --dry-run regularly

For broader certificate management topics, see our TLS Certificate Complete Guide. For automation strategies beyond Let's Encrypt, see SSL/TLS Certificate Automation & Renewal.

Let's turn this knowledge into action

Get a free 30-minute consultation with our experts. We'll help you apply these insights to your specific situation.