Let’s Encrypt #

Let’s Encrypt is a nonprofit Certificate Authority (CA) providing free, automatic, and open TLS certificates for everyone. Launched in November 2015 by the Internet Security Research Group (ISRG), Let’s Encrypt has revolutionized the web by issuing over three billion certificates and becoming the most widely used CA in the world. Its role has been crucial in driving global HTTPS adoption from around 40% in 2015 to over 80% today.

Caddy is designed with Let’s Encrypt as its built-in default CA. This integration is native and runs directly without requiring external libraries or helper programs. For developers and system administrators, this means no more worrying about the certificate lifecycle. Just list the domain name in the Caddyfile, and Caddy handles the entire process — request, verification, storage, and renewal of Let’s Encrypt certificates automatically in the background.


How Let’s Encrypt Automation Works in Caddy #

Caddy handles the entire Let’s Encrypt certificate lifecycle on its own without manual intervention from you. This process is managed by Caddy’s highly reliable internal certificate management engine.

When Caddy first starts or when a new domain is added:

1. ACME Account Key Creation:
   Caddy creates a new cryptographic key pair to represent your account at Let's Encrypt
   (this only happens once and is stored in Caddy's persistent storage).

2. Certificate Request:
   Caddy contacts Let's Encrypt's ACME endpoint and creates a new order for
   each unique domain name listed in the Caddyfile configuration.

3. Challenge Completion:
   Caddy performs the HTTP-01 challenge (default) or DNS-01 (if configured). For HTTP-01,
   Caddy automatically serves the challenge token on port 80.

4. Download & Installation:
   After verification by Let's Encrypt succeeds, Caddy downloads the DER-format certificate,
   converts it, stores it in the data folder, and installs it on the active TLS engine.

Continuously (Background Daemon Process):
5. Periodic Checks:
   Caddy checks all certificates in storage every ~10 minutes.

6. Renewal Initiation:
   If the certificate validity remaining is <= 30 days, Caddy starts a background renewal.

7. Zero-Downtime Renewal:
   Once a new certificate is obtained, Caddy immediately uses it for new connections without
   requiring a restart or service reload that could disrupt users.

Minimal Configuration (Zero Configuration) #

One of Caddy’s biggest advantages is that no complex TLS configuration is needed to get a production Let’s Encrypt certificate. For public domains whose DNS records already point to your server’s IP, the Caddyfile only needs lines like these:

# Minimal Caddyfile configuration
example.com {
    root * /var/www/html
    file_server
}

The automatic mechanism behind the scenes when Caddy loads this configuration:

  • Hostname Analysis: Caddy scans the config and detects that example.com is a valid public domain name.
  • Automatic TLS Granting: Caddy enables Automatic HTTPS for that domain.
  • ACME Initiation: Caddy contacts the Let’s Encrypt server, answers the HTTP-01 challenge on port 80 internally, receives the certificate, and activates port 443 with modern TLS configuration.
  • HTTP to HTTPS Redirect: Caddy automatically creates an HTTP server on port 80 to permanently redirect (301 redirect) all insecure traffic to HTTPS, except for the /.well-known/acme-challenge/ path used by ACME.

Although Caddy can run without an email address for the ACME account (using an anonymous account), setting an email address in the Caddyfile global options is strongly recommended.

# Global option to set the ACME email
{
    email [email protected]
}

example.com {
    file_server
}

Why Is This Email Important? #

  • Renewal Failure Notifications: If your Caddy server keeps failing certificate renewals (for example, due to firewall rules accidentally blocking port 80) and less than 20 days of validity remain, Let’s Encrypt sends a warning email to that address.
  • Policy Change Notices: Let’s Encrypt uses the contact email to notify users about major API changes, certificate revocations due to security issues, or terms of service updates.
  • ACME Account Recovery: Your ACME account key is associated with this email, aiding recovery if Caddy’s storage folder is accidentally deleted.

Let’s Encrypt Chain of Trust Architecture #

To ensure certificates issued by Let’s Encrypt are trusted by billions of devices worldwide, Let’s Encrypt uses a well-structured chain of trust architecture.

flowchart TD
    Browser["Trusted Root CA in Browser/Operating System"] --> ISRG["ISRG Root X1 (Let's Encrypt Root CA)"]
    
    ISRG --> R["R10 / R11 (Intermediate CAs)"]
    ISRG --> E["E5 / E6 (Intermediate CAs)"]
    
    R --> Leaf["Our Web Certificate (Leaf Certificate)"]
    E --> Leaf

    style Browser stroke:#0288d1,stroke-width:2px
    style Leaf stroke:#43a047,stroke-width:2px

Let’s Encrypt Chain of Trust Components: #

  1. ISRG Root X1: Let’s Encrypt’s main root certificate. It is now directly trusted by almost all operating systems, modern web browsers, and smart devices.
  2. Intermediate CAs (R10, R11, E5, E6): Let’s Encrypt doesn’t issue web certificates directly from the ISRG Root X1 key for security reasons. They use intermediate certificates. The R chain (like R10 and R11) uses the RSA algorithm, while the E chain (like E5 and E6) uses the faster, more efficient ECDSA algorithm.
  3. Leaf Certificate: The final certificate issued for your domain name with a 90-day validity period.

The Cross-Signing History with DST Root CA X3 #

In its early days, Let’s Encrypt wasn’t trusted by older operating systems. Therefore, ISRG Root X1 was cross-signed by the very old DST Root CA X3 belonging to IdenTrust.

This cross-signing scheme fully ended in September 2024. Today, Let’s Encrypt operates independently using the pure ISRG Root X1 chain. Caddy automatically downloads the most compatible and secure certificate chain to present to users’ browsers during the TLS handshake.


Caddy’s Certificate Storage Structure #

Caddy stores the ACME account key, domain private keys, and certificates in a securely structured storage location. By default, this storage folder location depends on the operating system where Caddy runs:

  • Linux (Systemd): /var/lib/caddy/.local/share/caddy/
  • Linux (Regular user): ~/.local/share/caddy/
  • macOS: ~/Library/Application Support/Caddy/
  • Windows: %APPDATA%\Caddy\

Here’s a visualization of Caddy’s storage directory tree for Let’s Encrypt:

caddy/
  ├── certificates/
  │   └── acme-v02.api.letsencrypt.org-directory/
  │       └── example.com/
  │           ├── example.com.crt       # TLS certificate (X.509 PEM format)
  │           ├── example.com.json      # Certificate metadata (expiry date, etc.)
  │           └── example.com.key       # Domain private key (VERY SECRET)
  └── acme/
      └── acme-v02.api.letsencrypt.org-directory/
          └── users/
              └── [email protected]/
                  ├── registration.json # ACME account registration
                  └── user.key          # ACME account private key

Each domain name has its own subdirectory containing the certificate file (.crt), private key (.key), and supporting metadata file (.json).

[!CAUTION] Never grant read or write access to this storage folder to unauthorized users. Domain private keys (.key) and account keys (user.key) are highly confidential files. If these keys leak, outsiders can intercept TLS data or impersonate your server. Set strict permissions on this folder (like chmod 700 for the folder and chmod 600 for files).


Shared Storage for Caddy Clusters #

In large-scale deployment scenarios with several Caddy instances running behind a load balancer, using local disk storage on each server creates problems:

  • Each Caddy instance would request separate Let’s Encrypt certificates for the same domain, quickly draining the rate limit.
  • The Caddy instance receiving the ACME verification challenge may differ from the instance that made the initial request.

To solve this, Caddy has the ability to use Shared Storage. Caddy supports various external storage modules compiled with xcaddy:

# Example Caddyfile configuration with Redis Shared Storage
{
    storage redis {
        host     "192.168.1.50"
        port     6379
        db       0
        password "secret-redis-password"
        timeout  5s
    }
}

example.com {
    reverse_proxy localhost:8080
}

How Caddy Shared Storage Works: #

  1. Key Synchronization (Distributed Locking): When one Caddy instance wants to renew a certificate for example.com, it creates a distributed lock in Redis.
  2. Certificate Sharing: The first Caddy instance completes the ACME challenge and downloads the new certificate, then stores it directly in Redis.
  3. Shared Usage: Other Caddy instances detect the new certificate in Redis, download it to their local memory, and use it instantly. This guarantees Let’s Encrypt quota efficiency and eliminates process redundancy.

Multiple Domains and Subject Alternative Names (SAN) #

Caddy is very flexible in managing many domains. You can define domains separately in each site block or combine them in one block.

{
    email [email protected]
}

# Block 1: Single Domain
example.com {
    file_server
}

# Block 2: Separate Subdomain
api.example.com {
    reverse_proxy localhost:8080
}

# Block 3: Combined Multiple Domains (SAN Certificate)
# Caddy requests one certificate covering both domains below
app.example.com, staging.example.com {
    reverse_proxy localhost:3000
}

When several domains are combined with commas as in Block 3, Caddy requests a SAN (Subject Alternative Names) certificate. This certificate has one Common Name in the subject section but includes additional domains in the SAN extension:

Certificate Subject:
  CN = app.example.com

Subject Alternative Name (SAN) Extension:
  DNS Name = app.example.com
  DNS Name = staging.example.com

Using SAN certificates is great for reducing the number of ACME handshakes and speeding up deployment time for services running on several related subdomains.


Monitoring and Verifying Certificate Status #

To ensure Let’s Encrypt certificates work correctly in production, you should know several methods to periodically check their status.

1. Query via the Caddy Admin API #

Caddy provides a local administrative API endpoint listening on port 2019 by default. You can fetch the active TLS configuration in JSON format:

# Fetch the currently running TLS configuration
curl -s http://localhost:2019/config/apps/tls/ | jq .

# Check the list of ACME automation policies
curl -s http://localhost:2019/config/apps/tls/automation/policies/ | jq .

2. Inspection with OpenSSL #

You can check the SSL certificate served by Caddy externally using the openssl command-line tool:

# Fetch complete certificate details from outside
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
  | openssl x509 -noout -text

# Only check the certificate validity period (expiry dates)
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
  | openssl x509 -noout -dates
# Ideal output:
# notBefore=Jun 16 00:00:00 2026 GMT
# notAfter=Sep 14 00:00:00 2026 GMT

Comparative Analysis: Certbot vs Caddy #

For users migrating from traditional setups (like Apache/Nginx + Certbot), here’s an architectural comparison between the Certbot approach and Caddy’s internal TLS management:

FeatureCertbot ApproachCaddy Approach
System IntegrationSeparate external application (Python). Needs extra cron or systemd timer configuration.Fused as a core part of the web server (Go). No external dependencies.
Configuration UpdatesCertbot auto-modifies Nginx/Apache config files (can break file structure).Caddy manages certificates in memory and internal storage without changing the Caddyfile.
Failure HandlingUsually fails silently if the cron script doesn’t run; only noticed when the web goes down.Caddy monitors periodically and automatically retries with a backoff algorithm on failure.
CA RedundancyMust be reconfigured manually to switch CAs.Has automatic multi-CA fallback (like switching to ZeroSSL if Let’s Encrypt is down).

Summary #

  • Let’s Encrypt — Caddy’s built-in default Certificate Authority, providing free, automatic, and universally trusted TLS certificates.
  • Minimal Configuration — Caddy automatically enables HTTPS, HTTP-to-HTTPS redirects, and completes ACME challenges for every public domain without extra TLS configuration.
  • Contact Email — Setting an email address in the Caddyfile global options is strongly recommended as an emergency notification channel for certificate expiration status.
  • Storage Structure — Certificates and private keys are stored in a structured way in Caddy’s local data folder (e.g., /var/lib/caddy/ on Linux) with strict security permissions.
  • Shared Storage — For multi-server cluster deployments, use a distributed storage module (like Redis) for certificate synchronization and to avoid Let’s Encrypt rate limits.
  • SAN Certificates — Caddy automatically packages multiple domains in one configuration block into a single certificate using the Subject Alternative Name (SAN) extension.

← Previous: ACME Concepts   Next: ZeroSSL →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact