caddyserver/caddy · error

expanding TestCA endpoint

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.

Solutions

  1. Define the variable in the environment the Caddy service runs in and restart
  2. Use the literal staging URL (https://acme-staging-v02.api.letsencrypt.org/directory) instead of a placeholder for this rarely-changing value
  3. Fix placeholder syntax to exactly {env.VAR_NAME}
  4. 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

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


AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15). Data as JSON: /api/errors/1c0d9d3b32bb1b94. Report an issue: GitHub.

Appendix: 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)