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?
| Feature | Let's Encrypt | Paid DV Certificates |
|---|---|---|
| Cost | Free | $10-$100/year |
| Automation | Built-in ACME | Often manual |
| Validity | 90 days | 1-2 years |
| Issuance Speed | Seconds | Minutes to hours |
| Wildcard Support | Yes (DNS-01) | Yes |
| Browser Trust | All major browsers | All major browsers |
| Support | Community | Paid 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 Type | Value | Reset Period |
|---|---|---|
| Certificates per Registered Domain | 50 | Per week (rolling) |
| Duplicate Certificates | 5 | Per week |
| Failed Validations | 5 per hostname | Per hour |
| New Orders | 300 per account | Per 3 hours |
| Accounts per IP | 10 | Per 3 hours |
| Pending Authorizations | 300 | Per 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:
- Go to Cloudflare Dashboard → My Profile → API Tokens
- Create Token → Edit zone DNS template
- Zone Resources: Include → Specific zone → your domain
- 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:
| Provider | Plugin Package | Command Flag |
|---|---|---|
| DigitalOcean | certbot-dns-digitalocean | --dns-digitalocean |
| Google Cloud | certbot-dns-google | --dns-google |
| Linode | certbot-dns-linode | --dns-linode |
| OVH | certbot-dns-ovh | --dns-ovh |
| RFC 2136 | certbot-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.
Systemd Timer (Recommended)
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
-
Secure private keys
chmod 600 /etc/letsencrypt/live/*/privkey.pem -
Use API tokens, not global API keys for DNS plugins
-
Monitor renewal - Set up alerts for expiring certificates
-
Test changes in staging first
Automation
-
Use systemd timer or cron - Never rely on manual renewal
-
Implement post-renewal hooks - Reload web servers automatically
-
Test renewal regularly
certbot renew --dry-run -
Monitor logs
tail -f /var/log/letsencrypt/letsencrypt.log
Multiple Servers
For load-balanced environments:
-
Centralized certificate management - Use one server to obtain certificates, distribute to others
-
Shared storage - Mount /etc/letsencrypt on shared filesystem (NFS, EFS)
-
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-runregularly
For broader certificate management topics, see our TLS Certificate Complete Guide. For automation strategies beyond Let's Encrypt, see SSL/TLS Certificate Automation & Renewal.
Related Tools
- X.509 Certificate Decoder - Analyze your Let's Encrypt certificates
- Certificate CSR Generator - Generate CSRs (useful for non-Let's Encrypt CAs)
- Certificate Transparency Lookup - Verify Let's Encrypt logged your certificate