How Caddy Automatic HTTPS Works with ZeroSSL and Let's Encrypt: A Technical Deep Dive
Caddy's automatic HTTPS provisions TLS certificates for every configured hostname through a three-phase process that discovers domains, creates CertMagic automation policies, and uses the ACMEIssuer driver to handle both Let's Encrypt and ZeroSSL (with automatic EAB credential generation) through a unified code path.
Caddy is the only web server that enables HTTPS by default, automatically obtaining and renewing certificates without manual configuration. According to the caddyserver/caddy source code, this automatic HTTPS system supports multiple certificate authorities including Let's Encrypt and ZeroSSL through a modular architecture centered around modules/caddyhttp/autohttps.go and the ACMEIssuer implementation in modules/caddytls/acmeissuer.go.
Phase 1: Hostname Discovery with automaticHTTPSPhase1
When Caddy starts, the HTTP app calls App.automaticHTTPSPhase1 (line 71 in modules/caddyhttp/autohttps.go) to scan the configuration for domains requiring certificates.
- Provision matchers – All routes are provisioned so host matchers (
MatchHost) are decoded and available for inspection. - Collect domain names – For each server, the code builds
serverDomainSet(lines 49-64), skipping names listed inAutoHTTPS.Skip. - Determine certificate requirements – If
AutoHTTPS.DisableCertsis false, every qualified name (validated viacertmagic.SubjectQualifiesForCert) that isn't inSkipCertsis added touniqueDomainsForCerts(lines 98-106). - Register ECH names – The TLS app receives the server names for Encrypted Client Hello support via
app.tlsApp.RegisterServerNames.
At the end of this phase, Caddy maintains two critical maps: uniqueDomainsForCerts (domains needing certificates) and redirDomains (HTTP to HTTPS redirect destinations).
Phase 2: Creating Automation Policies for ACME Issuers
After hostname discovery completes, App.createAutomationPolicies (lines 41-88 in autohttps.go) constructs CertMagic automation policies governing how certificates are obtained.
Base policy creation – If the user doesn't supply custom TLS settings, Caddy creates a catch-all policy (basePolicy) and populates its Issuers slice from caddytls.DefaultIssuersProvisioned (line 95 in modules/caddytls/values.go). By default, this instantiates an ACMEIssuer configured for Let's Encrypt's production directory (https://acme-v02.api.letsencrypt.org/directory).
CA configuration override – When targeting ZeroSSL, users set the CA field on the ACMEIssuer to https://acme.zerossl.com/v2/DV90. The same policy creation logic applies, but the issuer template is modified to handle ZeroSSL's External Account Binding (EAB) requirements.
ZeroSSL EAB Credential Generation
ZeroSSL's ACME endpoint requires External Account Binding (EAB) credentials. Caddy eliminates manual setup by automatically generating these credentials when the CA URL begins with https://acme.zerossl.com/.
In modules/caddytls/acmeissuer.go, the makeIssuerTemplate function (lines 61-70) detects ZeroSSL URLs and injects a custom NewAccountFunc:
if strings.HasPrefix(iss.CA, "https://acme.zerossl.com/") {
template.NewAccountFunc = func(ctx context.Context, acmeIss *certmagic.ACMEIssuer, acct acme.Account) (acme.Account, error) {
if acmeIss.ExternalAccount != nil {
return acct, nil
}
var err error
acmeIss.ExternalAccount, acct, err = iss.generateZeroSSLEABCredentials(ctx, acct)
return acct, err
}
}
The generateZeroSSLEABCredentials function (lines 17-76) automates the credential exchange:
- POSTs the primary contact email to
https://api.zerossl.com/acme/eab-credentials-email - Receives
eab_kidandeab_hmac_keyfrom the ZeroSSL API - Stores these in
acme.EABfor the ACME transaction
This enables automatic HTTPS with ZeroSSL without user-supplied credentials.
Phase 3: Certificate Management with automaticHTTPSPhase2
Once all servers are started, App.automaticHTTPSPhase2 (lines 0-22 in autohttps.go) initiates certificate procurement:
app.logger.Info("enabling automatic TLS certificate management",
zap.Strings("domains", internal.MaxSizeSubjectsListForLog(app.allCertDomains, 1000)),
)
err := app.tlsApp.Manage(app.allCertDomains)
The tlsApp.Manage method invokes CertMagic to:
- Look up the automation policies created during Phase 2
- Execute the ACME protocol (or ZeroSSL API) to obtain or renew certificates
- Store certificates in the configured storage (local disk by default)
Because policies already contain the appropriate issuer configuration, the same code path handles both Let's Encrypt and ZeroSSL ACME endpoints transparently.
ACME vs ZeroSSL API Implementation Differences
Caddy supports three distinct issuer implementations for certificate authorities:
| Issuer Type | Protocol | Configuration | EAB Handling |
|---|---|---|---|
| Let's Encrypt | Standard ACME (RFC 8555) | Default, no config required | Not required |
| ZeroSSL via ACME | Standard ACME with EAB | ca https://acme.zerossl.com/v2/DV90 |
Auto-generated via generateZeroSSLEABCredentials |
| ZeroSSL via API | Proprietary ZeroSSL REST API | tls.issuance.zerossl { api_key ... } |
API key authentication |
The ZeroSSLIssuer in modules/caddytls/zerosslissuer.go provides the proprietary API integration (lines 76-99), but this path requires explicit configuration and manual API key management, bypassing the automatic HTTPS flow.
Caddyfile Configuration Examples
Let's Encrypt (Default)
example.com {
# Automatic HTTPS enabled by default
respond "Hello, world!"
}
Result: Caddy discovers example.com, creates an ACME policy with Let's Encrypt's production endpoint, completes the HTTP-01 or TLS-ALPN-01 challenge, and serves HTTPS.
ZeroSSL via ACME with Automatic EAB
{
tls {
issuance acme {
ca https://acme.zerossl.com/v2/DV90
email admin@example.com # Required for EAB generation
}
}
}
zerossl.example.com {
respond "Secure with ZeroSSL"
}
Result: During Phase 1, Caddy registers zerossl.example.com. During Phase 2, ACMEIssuer detects the ZeroSSL CA URL, calls generateZeroSSLEABCredentials to obtain EAB credentials, and completes the ACME challenge flow identical to Let's Encrypt.
ZeroSSL via Legacy API (Non-ACME)
{
tls {
issuance zerossl {
api_key XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
validity_days 90
}
}
}
api.example.com {
reverse_proxy localhost:8080
}
Result: The ZeroSSLIssuer contacts ZeroSSL's proprietary API directly to request certificates, bypassing ACME entirely. This requires explicit TLS app configuration and manual API key provision, and is not part of the automatic HTTPS discovery flow.
Summary
- Automatic HTTPS operates in three phases: hostname discovery (
automaticHTTPSPhase1), automation policy creation (createAutomationPolicies), and certificate management (automaticHTTPSPhase2). - Both CAs use the same code path: The
ACMEIssuerinmodules/caddytls/acmeissuer.gohandles both Let's Encrypt and ZeroSSL ACME endpoints, differing only in CA URL and EAB requirements. - ZeroSSL EAB is automatic: When the CA URL matches
https://acme.zerossl.com/*, Caddy automatically generates EAB credentials viagenerateZeroSSLEABCredentialswithout user intervention. - Legacy API available: A separate
ZeroSSLIssuerexists for ZeroSSL's proprietary API inmodules/caddytls/zerosslissuer.go, but requires manual configuration and does not participate in automatic HTTPS discovery.
Frequently Asked Questions
What is the difference between ZeroSSL ACME and ZeroSSL API issuers in Caddy?
The ZeroSSL ACME issuer uses standard RFC 8555 ACME protocol through modules/caddytls/acmeissuer.go, automatically generating EAB credentials and supporting the full automatic HTTPS flow. The ZeroSSL API issuer uses ZeroSSL's proprietary REST API through modules/caddytls/zerosslissuer.go, requires manual API key configuration, and operates outside the automatic hostname discovery system.
Does Caddy require manual EAB credentials for ZeroSSL?
No. When using ZeroSSL's ACME endpoint (https://acme.zerossl.com/v2/DV90), Caddy automatically generates the required External Account Binding credentials via the generateZeroSSLEABCredentials function. You only need to provide an email address in the TLS configuration; Caddy handles the API call to ZeroSSL's eab-credentials-email endpoint.
Which source files control Caddy's automatic HTTPS behavior?
The primary files are:
modules/caddyhttp/autohttps.go– ContainsautomaticHTTPSPhase1(hostname discovery),createAutomationPolicies, andautomaticHTTPSPhase2(certificate management)modules/caddytls/acmeissuer.go– ImplementsACMEIssuerwithmakeIssuerTemplateand ZeroSSL EAB generation logicmodules/caddytls/zerosslissuer.go– Implements the proprietary ZeroSSL API issuermodules/caddytls/values.go– DefinesDefaultIssuersProvisionedcontaining default Let's Encrypt configuration
Can I use both Let's Encrypt and ZeroSSL simultaneously?
Yes. You can define multiple automation policies in your Caddy configuration, specifying different issuers for different site blocks. Caddy's createAutomationPolicies function builds separate CertMagic policies for each configured TLS context, allowing some sites to use Let's Encrypt while others use ZeroSSL's ACME endpoint or legacy API.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →