caddyserver/caddy · error
expanding TestCA endpoint '%s': %v
Error message
expanding TestCA endpoint '%s': %v
What it means
Returned by ACMEIssuer.Provision (modules/caddytls/acmeissuer.go:156) when the test/staging CA endpoint field (acme_ca_test, JSON "test_ca") contains a placeholder the replacer cannot resolve. The expansion runs with errorOnUnset, so a missing environment variable or bad placeholder syntax here stops issuer provisioning even if the production CA is fine.
Source
Thrown at modules/caddytls/acmeissuer.go:156
return fmt.Errorf("expanding email address '%s': %v", iss.Email, err)
}
iss.Email = email
}
// expand CA endpoint, if non-empty
if iss.CA != "" {
ca, err := repl.ReplaceOrErr(iss.CA, true, true)
if err != nil {
return fmt.Errorf("expanding CA endpoint '%s': %v", iss.CA, err)
}
iss.CA = ca
}
// expand TestCA endpoint, if non-empty
if iss.TestCA != "" {
testca, err := repl.ReplaceOrErr(iss.TestCA, true, true)
if err != nil {
return fmt.Errorf("expanding TestCA endpoint '%s': %v", iss.TestCA, err)
}
iss.TestCA = testca
}
// expand EAB credentials, if non-empty
if iss.ExternalAccount != nil {
if iss.ExternalAccount.KeyID != "" {
keyID, err := repl.ReplaceOrErr(iss.ExternalAccount.KeyID, true, true)
if err != nil {
return fmt.Errorf("expanding EAB key ID '%s': %v", iss.ExternalAccount.KeyID, err)
}
iss.ExternalAccount.KeyID = keyID
}
if iss.ExternalAccount.MACKey != "" {
macKey, err := repl.ReplaceOrErr(iss.ExternalAccount.MACKey, true, true)
if err != nil {
return fmt.Errorf("expanding EAB MAC key (redacted): %v", err)
}View on GitHub (pinned to 50e54ee279)
Solutions
- Define the variable in the environment the Caddy service runs in and restart
- Use the literal staging URL (https://acme-staging-v02.api.letsencrypt.org/directory) instead of a placeholder for this rarely-changing value
- Fix placeholder syntax to exactly {env.VAR_NAME}
- Remove acme_ca_test if staging is not actually needed
Example fix
# before
{
acme_ca_test {env.STAGING_ENDPOINT} # unset on this host
}
# after
{
acme_ca_test https://acme-staging-v02.api.letsencrypt.org/directory
} Defensive patterns
Strategy: validation
Validate before calling
if strings.Contains(cfgText, "{env.STAGING_CA}") && os.Getenv("STAGING_CA") == "" {
return errors.New("STAGING_CA referenced by acme_ca_test but not set")
} Try / catch
if err := issuer.Provision(ctx); err != nil {
if strings.Contains(err.Error(), "expanding TestCA endpoint") {
// set the env var or replace with the literal staging URL
}
return err
} Prevention
- Hardcode the well-known LE staging URL instead of an env var - it never changes
- Split dev/staging configs into snippets included only where the env exists
- Validate configs per-environment, not once globally
When it happens
Trigger: Setting acme_ca_test {env.STAGING_CA} where STAGING_CA is unset in the Caddy process, or misspelled/unclosed placeholder syntax in the test endpoint value.
Common situations: Using Let's Encrypt staging in dev via an env var that only exists in CI; enabling debug/test mode configs on machines without the same environment; typos like {env.LE_STAGING}}.
Related errors
- expanding email address '%s': %v
- expanding CA endpoint '%s': %v
- expanding EAB key ID '%s': %v
- expanding EAB MAC key (redacted): %v
- expanding account key PEM '%s': %v
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/1c0d9d3b32bb1b94.
Report an issue: GitHub.