Caddy can obtain and renew managed certificates without an operator manually implementing ACME. That removes repetitive work, not the prerequisites for issuance or the need to notice failures. A valid certificate today proves neither that the next renewal can complete nor that every TLS hop uses the same certificate. Identify the names, validation route, storage, and termination boundary first.

Give automatic HTTPS a name

Automatic HTTPS activates when Caddy knows a qualifying hostname or address through its configuration, subject to documented exceptions and overrides. A hostname in a Caddyfile site address is one common trigger. The file_server directive serves files; it is not, by itself, a request for a public certificate for an unspecified domain.

The command-line distinction is concrete: bare caddy file-server defaults to port 80. Supplying --domain makes it attempt HTTPS for that hostname and changes the default listener to port 443. Neither command spelling proves that DNS, port forwarding, or certificate authorization is correct. Explicit HTTP configuration and manually loaded certificates also change automatic behavior.

Local development has a different trust boundary. Caddy can use its local certificate authority for internal names. Clients must trust that authority through an appropriate installation process; a certificate working on one machine does not make it publicly trusted. Do not distribute a private CA’s signing key to solve a client trust problem.

Match the challenge to the route

Caddy enables HTTP-01 and TLS-ALPN-01 by default. HTTP-01 validation enters through public port 80, while TLS-ALPN-01 uses port 443 and a special TLS handshake. Caddy can choose between available challenges. A failed HTTP challenge does not imply that DNS validation is the only possible alternative; inspect which methods and routes are actually available.

A proxy or CDN can interfere with either path, but its presence does not automatically make issuance impossible. HTTP challenge traffic may be forwarded correctly even when the CDN terminates ordinary HTTPS. DNS-01 proves control through a TXT record and does not require the requesting server to accept inbound validation traffic. It needs a supported provider integration and appropriately scoped DNS credentials.

When it breaks

Stale A or AAAA records, blocked ports, incorrect forwarding, or a changed CDN rule can break validation after the first issuance succeeded. Check both address families and the actual failed challenge in logs. Repeatedly deleting storage and retrying is a poor diagnostic: it removes reusable certificate state and can increase unnecessary issuance attempts.

Persist Caddy’s data storage across replacement instances and protect it because it contains sensitive key material. Multiple instances need a storage arrangement with the coordination semantics Caddy expects. A directory merely bearing the same name on several machines is not shared storage. Test recovery of that configuration without exposing keys in logs or source control.

Automation can fail while the existing certificate still works. Monitor renewal errors and the expiry of the certificate clients actually receive, leaving enough time for response. A successful origin renewal does not prove the CDN’s public certificate changed. Likewise, CDN TLS does not remove the need to secure and validate a separate HTTPS origin connection.

Use a staging issuer for issuance experiments, and keep its untrusted test certificates out of production. DNS credential rotation needs a controlled validation check. Choose alert lead time around response requirements and certificate lifetime; a fixed thirty-day arithmetic example is not a specification of Caddy’s renewal schedule.

Caddy prerequisites, persistent storage and served-certificate monitoring are separated from a thirty-day arithmetic alert model and its historical evidence limits.
Caddy certificate lifecycle: mechanism and evidence boundaries.

Check it quickly

Save this as expiry_alert.py and run python3 expiry_alert.py. Use assertions enabled, without -O or PYTHONOPTIMIZE. It needs only the standard library and uses a fixed UTC observation time. The synthetic thirty-day threshold belongs to this operator alert, not to Caddy. No domain, certificate authority, network listener, key, or system trust setting is touched.

from datetime import datetime, timedelta, timezone

# An operator-chosen alert threshold, not Caddy's renewal scheduler.
ALERT_BEFORE = timedelta(days=30)
NOW = datetime(2026, 10, 15, 12, tzinfo=timezone.utc)


def expiry_alert(not_after, now):
    for value in (not_after, now):
        if value.tzinfo is None or value.utcoffset() is None:
            raise ValueError("timezone-aware timestamps required")
    remaining = not_after - now
    if remaining <= timedelta(0):
        return "expired"
    if remaining <= ALERT_BEFORE:
        return "warning"
    return "ok"


for name, days, expected in [
    ("fresh", 80, "ok"), ("due", 20, "warning"),
    ("boundary", 30, "warning"), ("expired", -1, "expired"),
]:
    not_after = NOW + timedelta(days=days)
    actual = expiry_alert(not_after, NOW)
    assert actual == expected
    print(f"{name}: remaining={days}d alert={actual}")
assert expiry_alert(NOW, NOW) == "expired"
try:
    expiry_alert(NOW.replace(tzinfo=None), NOW)
except ValueError:
    print("naive timestamp rejected")
else:
    raise AssertionError("missing timezone accepted")
print("PASS: expiry alert boundaries; no certificate issuance tested")

The four printed cases are ok, warning, warning, and expired. Exactly thirty days is included in the warning interval; expiry itself is classified separately. The negative case supplies a naive expiry timestamp; the function also checks the observation time, but that separate branch is not exercised. These assertions verify the alert’s declared boundaries rather than relying on today’s date or a particular certificate lifetime.

Retained exact records cover the unchanged arithmetic fixture; this source review did not rerun it. The exercise does not run Caddy, issue a certificate, or observe renewal. For deployment evidence, verify the served certificate and configured challenge route, then observe successful renewal using the intended persistent storage. Keep that operational evidence separate from this small, repeatable date-arithmetic check.

Further reading

Sources