Build a TLS automation.policies array whose final entry is an
unscoped (catch-all) policy that uses Caddy's internal issuer.
Why this exists. Without an unscoped catch-all, any hostname not
matched by a more-specific policy hits Caddy's default behavior:
obtain a Let's Encrypt cert via HTTP-01 / TLS-ALPN-01. For Caddy
instances behind a tunnel/edge that already terminates TLS, or
running on a private network with no inbound port 80/443, that's
always wrong: the ACME challenge can't be solved, every renewal job
fails, and the log fills with "obtaining certificate: ... context canceled" and "no solvers available for remaining challenges".
The fix is a terminal policy with no subjects field that uses the
internal (self-signed) issuer. Caddy's own schema describes
AutomationPolicy.subjects as a filter, not a command; omitting it
means "match anything not already matched."
Adding ACME-DNS or other plugin-backed issuers: don't pass them
here. Construct a typed plugin policy via the plugin's schema (under
src/plugins/<plugin-name>/) and merge it into the policy array
before this catch-all in the calling code. This builder intentionally
exposes no unknown-shaped passthrough.
Build a TLS
automation.policiesarray whose final entry is an unscoped (catch-all) policy that uses Caddy'sinternalissuer.Why this exists. Without an unscoped catch-all, any hostname not matched by a more-specific policy hits Caddy's default behavior: obtain a Let's Encrypt cert via HTTP-01 / TLS-ALPN-01. For Caddy instances behind a tunnel/edge that already terminates TLS, or running on a private network with no inbound port 80/443, that's always wrong: the ACME challenge can't be solved, every renewal job fails, and the log fills with
"obtaining certificate: ... context canceled"and"no solvers available for remaining challenges".The fix is a terminal policy with no
subjectsfield that uses the internal (self-signed) issuer. Caddy's own schema describesAutomationPolicy.subjectsas a filter, not a command; omitting it means "match anything not already matched."Adding ACME-DNS or other plugin-backed issuers: don't pass them here. Construct a typed plugin policy via the plugin's schema (under
src/plugins/<plugin-name>/) and merge it into the policy array before this catch-all in the calling code. This builder intentionally exposes nounknown-shaped passthrough.Policy order produced:
subjects: ["*.localhost", "localhost"]→ internalsubjects) → internal