How Caddy TLS Certificate Management Works: Automated HTTPS Architecture Explained
Caddy's TLS certificate management delegates ACME operations to the certmagic library while providing a declarative configuration layer through the tls.TLS app that handles provisioning, automation policies, and on-demand certificate issuance.
Caddy is the only web server that enables HTTPS by default, and this capability stems from its sophisticated TLS certificate management system. According to the caddyserver/caddy source code, the entire TLS subsystem is built around the tls.TLS app defined in modules/caddytls/tls.go, which orchestrates certificate provisioning, renewal, and runtime operations through a tight integration with the certmagic library.
Core Architecture of the TLS App
The TLS app serves as the central coordinator for all certificate-related operations in Caddy. When Caddy starts, the TLS.Provision method initializes the certificate management infrastructure by creating a shared certmagic cache (certCache) and loading static certificate sources.
Provisioning Phase
During the provisioning phase, TLS.Provision in modules/caddytls/tls.go performs several critical setup steps:
- Creates the shared
certCachethat persists across the application lifecycle - Loads static certificate loaders (such as PEM files specified in configuration)
- Parses the special
"automate"loader to identify which hostnames require automatic certificate issuance - Prepares the foundation for dynamic certificate acquisition
Automation Configuration
If your configuration includes an automation block, Caddy constructs an AutomationConfig (defined in modules/caddytls/automation.go). This configuration structure contains multiple AutomationPolicy objects that define:
- Subject domains or wildcard patterns
- Issuer modules (ACME, ZeroSSL, internal CA)
- Challenge solver configurations
- Optional on-demand TLS settings
Certificate Lifecycle and Automation Policies
Each AutomationPolicy acts as a rule set that determines how Caddy manages certificates for specific domains. The policy system allows granular control over which Certificate Authority (CA) to use and which validation methods to employ.
Policy Provisioning and certmagic Integration
When AutomationPolicy.Provision executes, it performs several transformation steps:
- Placeholder expansion – Resolves dynamic values in configuration
- DNS provider setup – Configures external DNS providers for DNS-01 challenges
- certmagic.Config creation – Builds a template and instantiates
certmagic.New(certCache, template) - Manager registration – Attaches custom certificate managers or issuers as needed
The ACME Issuer Implementation
The default issuer for public certificates is implemented in modules/caddytls/acmeissuer.go. When provisioned, the ACME issuer receives configuration parameters including:
- CA URL (Let's Encrypt, ZeroSSL, or custom)
- Contact email address
- External Account Binding (EAB) credentials
- Preferred challenge types (HTTP-01, TLS-ALPN-01, DNS-01)
During startup (TLS.Start), the system calls Manage which groups hostnames by their matching automation policy and invokes certmagic.Config.ManageAsync for each group. Certmagic then handles ACME account creation, key reuse, challenge solving, certificate issuance, renewal scheduling, OCSP stapling, and storage cleanup.
On-Demand TLS for Dynamic Certificate Issuance
Caddy supports on-demand TLS for scenarios where the complete list of domains is unknown at startup or too large to manage statically. When a policy has on_demand: true or an external manager is configured, Certmagic creates an OnDemandConfig that triggers during TLS handshakes.
During the handshake process:
- Certmagic checks the in-memory cache via
HasCertificateForSubject - If no certificate exists, it queries the permission module (if configured) to verify domain issuance authorization
- Upon approval, the system obtains the certificate on-the-fly or serves an existing cached certificate
This mechanism enables hosting platforms to serve HTTPS for arbitrary customer domains without pre-configuration.
Runtime Certificate Operations
The TLS app provides several runtime helpers that integrate with Caddy's HTTP handling pipeline.
Challenge Handling and DNS Providers
When ACME challenges arrive, HandleHTTPChallenge in modules/caddytls/tls.go routes incoming HTTP-01 requests to the appropriate issuer. For DNS-01 challenges, Caddy uses the global DNS module (tls.DNSRaw) or policy-specific DNS providers configured in the automation policy.
Certificate Storage and Cache Management
The shared certCache maintains certificates in memory for fast retrieval during TLS handshakes. The system also manages:
- Session tickets – Via
modules/caddytls/session_ticket.gofor Session Ticket Ephemeral Keys (STEK) - Encrypted ClientHello (ECH) – Support implemented in
modules/caddytls/ech.go - Storage cleanup – Periodic maintenance of the certificate storage backend
Configuration Examples
Basic Automatic HTTPS Configuration
This Caddyfile enables automatic HTTPS for all sites using default Let's Encrypt issuance:
{
# optional global automation tweaks
tls {
automation {
policies {
# every host gets a cert from Let's Encrypt (ACME)
subjects ["*"]
issuers [{ "module": "acme" }]
on_demand false
}
}
}
}
On-Demand TLS with Permission Endpoint
For dynamic domain support with authorization checks:
{
tls {
automation {
on_demand {
ask "http://localhost:2019/allow"
}
policies {
subjects ["*"]
issuers [{ "module": "acme" }]
on_demand true
}
}
}
}
DNS-01 Challenge with Cloudflare
When you need wildcard certificates or internal domains:
{
# global DNS provider used by all TLS policies that need DNS-01
dns cloudflare {
api_token {$CLOUDFLARE_TOKEN}
}
tls {
automation {
policies {
subjects ["example.com", "www.example.com"]
issuers [{ "module": "acme" }]
dns { "provider": "cloudflare" }
}
}
}
}
Programmatic TLS Management
You can also interact with the TLS app directly from Go code:
// Using the TLS app programmatically
import (
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/modules/caddytls"
)
func enableTLS(app *caddytls.TLS) error {
// add a custom automation policy at runtime
policy := &caddytls.AutomationPolicy{
SubjectsRaw: []string{"myapp.internal"},
IssuersRaw: []json.RawMessage{json.RawMessage(`{"module":"internal"}`)},
}
if err := app.AddAutomationPolicy(policy); err != nil {
return err
}
// start managing the new name
return app.Manage(map[string]struct{}{"myapp.internal": {}})
}
Summary
- Caddy's TLS certificate management is implemented through the
tls.TLSapp inmodules/caddytls/tls.go, which provides the core provisioning and lifecycle management. - Automation policies defined in
modules/caddytls/automation.gomap domains to specific issuers and challenge configurations. - The system delegates ACME operations to the certmagic library, handling account creation, challenge solving, and renewal automatically.
- On-demand TLS enables dynamic certificate issuance during TLS handshakes, with optional permission callbacks for domain authorization.
- Caddy supports HTTP-01, TLS-ALPN-01, and DNS-01 challenges, with DNS providers configurable globally or per-policy in
modules/caddytls/acmeissuer.go.
Frequently Asked Questions
How does Caddy automatically obtain TLS certificates?
Caddy automatically obtains TLS certificates through its integration with the certmagic library. When configured with an ACME issuer (the default in modules/caddytls/acmeissuer.go), Caddy creates ACME accounts, solves validation challenges (HTTP-01, TLS-ALPN-01, or DNS-01), and requests certificates from CAs like Let's Encrypt or ZeroSSL. The TLS.Start method in modules/caddytls/tls.go initiates this process by calling certmagic.Config.ManageAsync for each configured hostname.
What is the difference between static and on-demand TLS in Caddy?
Static TLS uses certificates loaded at startup from files or obtained immediately for known hostnames configured in automation policies. On-demand TLS, configured by setting on_demand: true in an automation policy, defers certificate acquisition until the first TLS handshake for a domain occurs. As implemented in the TLS app, on-demand mode checks the permission module (if configured) before invoking certmagic to obtain certificates dynamically, making it ideal for multi-tenant platforms.
Which challenge types does Caddy support for ACME validation?
According to modules/caddytls/acmeissuer.go, Caddy supports three ACME challenge types: HTTP-01 (served automatically on port 80), TLS-ALPN-01 (handled during TLS handshakes), and DNS-01 (requires a configured DNS provider). The HTTP-01 challenges are routed through HandleHTTPChallenge in the TLS app, while DNS-01 challenges use the global DNS module or policy-specific providers configured in the automation block.
How does Caddy handle certificate renewal?
Caddy delegates renewal scheduling to certmagic, which tracks certificate expiration dates and initiates renewal automatically before expiry. The ManageAsync function monitors certificates in the shared certCache and triggers re-issuance through the configured issuer (typically ACME). Renewal includes fresh OCSP stapling and storage updates, all handled without downtime or manual intervention.
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 →