# How Caddy TLS Certificate Management Works: Automated HTTPS Architecture Explained

> Discover how Caddy TLS certificate management automates HTTPS using the certmagic library. Explore its declarative configuration for provisioning, policies, and on-demand issuance.

- Repository: [Caddy/caddy](https://github.com/caddyserver/caddy)
- Tags: architecture
- Published: 2026-03-03

---

**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`](https://github.com/caddyserver/caddy/blob/main/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`](https://github.com/caddyserver/caddy/blob/main/modules/caddytls/tls.go) performs several critical setup steps:

- Creates the shared `certCache` that 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`](https://github.com/caddyserver/caddy/blob/main/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:

1. **Placeholder expansion** – Resolves dynamic values in configuration
2. **DNS provider setup** – Configures external DNS providers for DNS-01 challenges
3. **certmagic.Config creation** – Builds a template and instantiates `certmagic.New(certCache, template)`
4. **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`](https://github.com/caddyserver/caddy/blob/main/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`](https://github.com/caddyserver/caddy/blob/main/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.go`](https://github.com/caddyserver/caddy/blob/main/modules/caddytls/session_ticket.go) for Session Ticket Ephemeral Keys (STEK)
- **Encrypted ClientHello (ECH)** – Support implemented in [`modules/caddytls/ech.go`](https://github.com/caddyserver/caddy/blob/main/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:

```caddyfile
{
    # 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:

```caddyfile
{
    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:

```caddyfile
{
    # 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:

```go
// 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.TLS` app in [`modules/caddytls/tls.go`](https://github.com/caddyserver/caddy/blob/main/modules/caddytls/tls.go), which provides the core provisioning and lifecycle management.
- **Automation policies** defined in [`modules/caddytls/automation.go`](https://github.com/caddyserver/caddy/blob/main/modules/caddytls/automation.go) map 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`](https://github.com/caddyserver/caddy/blob/main/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`](https://github.com/caddyserver/caddy/blob/main/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`](https://github.com/caddyserver/caddy/blob/main/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`](https://github.com/caddyserver/caddy/blob/main/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.