Skip to main content

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:

  1. You run Certbot requesting a certificate
  2. Let's Encrypt generates a unique token and sends it to Certbot
  3. Certbot automatically creates the file at /.well-known/acme-challenge/TOKEN
  4. Let's Encrypt makes an HTTP request to verify the token
  5. If validated, certificate is issued
  6. 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.

ModePluginWhat it does
--nginxRequires pluginObtains certificate and configures Nginx
--apacheRequires pluginObtains certificate and configures Apache
certonlyNot requiredOnly 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 to fullchain.pem
  • ssl_certificate_key: point to privkey.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:

  1. Timer/cron executes certbot renew
  2. Certbot checks installed certificates
  3. Identifies certificates with less than 30 days validity
  4. Starts renewal process (repeats HTTP-01 validation)
  5. Replaces old certificate with new one
  6. 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:

LimitMeaningExample
50 certificates/domain/weekMaximum of 50 certificates for subdomains of your domainapi.site.com, www.site.com, etc. combined
5 duplicates/weekSame certificate (exact domains) can only be issued 5xPrevents accidental loops in scripts
10 accounts/IP/3hNew ACME account creation limitedOnly 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 causeWhat happened
Port 80 blockedFirewall changed, Let's Encrypt can't validate
Server stoppedNginx/Apache wasn't running at renewal time
Domain changedDNS no longer points to the server
Disk fullCan'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