HTTP-01 Challenge
When you request a certificate, Let's Encrypt needs to verify that you control the specific domain or subdomain you're requesting. The HTTP-01 Challenge verifies this by making an HTTP request on port 80.
For example: if you request a certificate for api.domain.com, Let's Encrypt validates only that subdomain, not the entire domain. To cover multiple subdomains, you need to request a certificate for each one or use wildcard via DNS-01 Challenge which we'll see later.
DNS prerequisite: Only the A/CNAME record pointing to the server's IP (already existing if the site works). No additional records need to be created.
Server prerequisites:
- Port 80 publicly accessible
- Web server responding to HTTP requests
How it works:
- You run Certbot requesting a certificate
- Let's Encrypt generates a unique token and sends it to Certbot
- Certbot automatically creates the file at
/.well-known/acme-challenge/TOKEN - Let's Encrypt makes an HTTP request to verify the token
- If validated, certificate is issued
- Certbot automatically removes the token file
Certbot is the official ACME client developed by the Electronic Frontier Foundation (EFF) in partnership with Let's Encrypt. It automates the entire process of obtaining and renewing certificates.
Do I need to create an account? No! Certbot automatically creates an account on first run. It only asks for an email (optional) for expiration notices. Credentials are stored in /etc/letsencrypt/accounts/.
Since ACME is an open protocol, there are other compatible clients: acme.sh (shell script), Caddy (web server), Traefik (reverse proxy), cert-manager (Kubernetes).
Certbot is a tool, not a service. You install it once and it manages multiple certificates for different domains on the same server. After the first run, Certbot automatically configures a systemd timer (or cron job) that checks for renewals 2x daily. If a certificate is close to expiration, it renews automatically.
Important: You don't need to create any files manually. Certbot handles the entire process.
Certbot is a completely separate application from your actual application. Architecture:
Installing Certbotβ
# Ubuntu/Debian
sudo apt update
sudo apt install certbot
# Nginx plugin if needed
sudo apt install python3-certbot-nginx
# Apache plugin if needed
sudo apt install python3-certbot-apache
# CentOS/RHEL/Rocky + plugin
sudo dnf install certbot python3-certbot-nginx
Plugins are not mandatory, but if you want to use the --nginx or --apache commands, which we'll see later, it will make things much easier as they automatically configure these web servers. Without a plugin, use certonly and configure manually.
| Mode | Plugin | What it does |
|---|---|---|
--nginx | Requires plugin | Obtains certificate and configures Nginx |
--apache | Requires plugin | Obtains certificate and configures Apache |
certonly | Not required | Only obtains certificate (manual configuration) |
Obtaining a Certificateβ
# With Nginx (recommended - configures automatically)
sudo certbot --nginx -d subdomain.domain.com
# With Apache
sudo certbot --apache -d subdomain.domain.com
# Certificate only (without configuring server)
sudo certbot certonly --webroot -w /var/www/html -d subdomain.domain.com
Certbot automatically: receives the token, creates the file at /.well-known/acme-challenge/, waits for validation, removes the file, and installs the certificate creating the timer for renewal on the system.
Useful Commandsβ
# Check the automatic renewal timer
systemctl list-timers | grep certbot
# List all managed certificates
certbot certificates
# Renew all certificates (automatically executed by timer)
certbot renew
Where Certificates Are Storedβ
After successful validation, Certbot stores certificates in /etc/letsencrypt/:
/etc/letsencrypt/
βββ live/
β βββ domain.com/
β βββ cert.pem # Server certificate
β βββ chain.pem # Intermediate chain
β βββ fullchain.pem # cert.pem + chain.pem (use this on server)
β βββ privkey.pem # Private key (protect!)
βββ archive/ # Previous certificate history
βββ renewal/ # Renewal configurations
βββ accounts/ # ACME account credentials
In server configuration, use:
ssl_certificate: point tofullchain.pemssl_certificate_key: point toprivkey.pem
# Check installed certificates
sudo certbot certificates
# Expected output:
# Certificate Name: domain.com
# Domains: domain.com
# Expiry Date: 2024-04-15 (VALID: 89 days)
# Certificate Path: /etc/letsencrypt/live/domain.com/fullchain.pem
# Private Key Path: /etc/letsencrypt/live/domain.com/privkey.pem
Manual Renewal (legacy systems)β
On systems without systemd timer, add a cron job:
# Edit crontab
sudo crontab -e
# Add line for renewal 2x daily
0 0,12 * * * certbot renew --quiet --post-hook "systemctl reload nginx"
Renewal flow:
- Timer/cron executes
certbot renew - Certbot checks installed certificates
- Identifies certificates with less than 30 days validity
- Starts renewal process (repeats HTTP-01 validation)
- Replaces old certificate with new one
- Executes post-hook to reload web server
Security Risks and Considerationsβ
1. Rate Limitsβ
Since Let's Encrypt is free, there are limits to prevent abuse:
| Limit | Meaning | Example |
|---|---|---|
| 50 certificates/domain/week | Maximum of 50 certificates for subdomains of your domain | api.site.com, www.site.com, etc. combined |
| 5 duplicates/week | Same certificate (exact domains) can only be issued 5x | Prevents accidental loops in scripts |
| 10 accounts/IP/3h | New ACME account creation limited | Only affects first installation |
In practice: These limits rarely affect normal use. Problems arise with:
- Scripts with bugs requesting certificates in a loop
- Test/development environments not using staging
Solution: Use --test-cert (staging) for tests - has much higher limits:
# Test environment (doesn't generate valid certificate, but tests entire process)
certbot certonly --test-cert --nginx -d test.domain.com
# Production (real certificate)
certbot certonly --nginx -d prod.domain.com
2. What if Renewal Fails?β
If the automatic timer fails to renew, the certificate expires and your site loses HTTPS. This can happen due to:
| Common cause | What happened |
|---|---|
| Port 80 blocked | Firewall changed, Let's Encrypt can't validate |
| Server stopped | Nginx/Apache wasn't running at renewal time |
| Domain changed | DNS no longer points to the server |
| Disk full | Can't save new certificate |
How to verify everything is ok:
# View certificate status and expiration dates
sudo certbot certificates
# Test if renewal will work (without actually renewing)
sudo certbot renew --dry-run
Simple monitoring: Add an email alert if renewal fails:
# Add to crontab (sudo crontab -e)
0 8 * * * certbot renew --dry-run --quiet || echo "ALERT: SSL renewal failed!" | mail -s "Certbot Failed" [email protected]
This runs daily at 8am and only sends an email if there's a problem.
3. Protecting Your Domain with CAA Recordsβ
Anyone can try to obtain a certificate for your domain. If an attacker gains temporary access to your server or DNS, they could obtain a valid certificate.
To address this situation we have CAA (Certificate Authority Authorization) which is a DNS record that says "only THIS certificate authority can issue certificates for my domain".
# Add these DNS records at your provider (Cloudflare, Route53, etc.)
# Only Let's Encrypt can issue certificates for this domain
domain.com. CAA 0 issue "letsencrypt.org"
# Block wildcard issuance (optional)
domain.com. CAA 0 issuewild ";"
# Receive email if someone tries to issue unauthorized certificate
domain.com. CAA 0 iodef "mailto:[email protected]"
Check if CAA is configured:
dig CAA domain.com
4. Protecting the Private Keyβ
The private key (privkey.pem) is the most important secret. Anyone with access can impersonate your site.
# Check permissions (should be 600, only root reads)
ls -la /etc/letsencrypt/live/domain.com/
# Fix if necessary
sudo chmod 600 /etc/letsencrypt/live/domain.com/privkey.pem
sudo chown root:root /etc/letsencrypt/live/domain.com/privkey.pem
Never do:
- Copy keys to git repositories
- Send keys via email/chat
- Give read permission to other users