{"record":{"id":"1c0d9d3b32bb1b94","repo":"caddyserver/caddy","slug":"expanding-testca-endpoint-s-v","errorCode":null,"errorMessage":"expanding TestCA endpoint '%s': %v","messagePattern":"expanding TestCA endpoint '(.+?)': (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"modules/caddytls/acmeissuer.go","lineNumber":156,"sourceCode":"\t\t\treturn fmt.Errorf(\"expanding email address '%s': %v\", iss.Email, err)\n\t\t}\n\t\tiss.Email = email\n\t}\n\n\t// expand CA endpoint, if non-empty\n\tif iss.CA != \"\" {\n\t\tca, err := repl.ReplaceOrErr(iss.CA, true, true)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"expanding CA endpoint '%s': %v\", iss.CA, err)\n\t\t}\n\t\tiss.CA = ca\n\t}\n\n\t// expand TestCA endpoint, if non-empty\n\tif iss.TestCA != \"\" {\n\t\ttestca, err := repl.ReplaceOrErr(iss.TestCA, true, true)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"expanding TestCA endpoint '%s': %v\", iss.TestCA, err)\n\t\t}\n\t\tiss.TestCA = testca\n\t}\n\n\t// expand EAB credentials, if non-empty\n\tif iss.ExternalAccount != nil {\n\t\tif iss.ExternalAccount.KeyID != \"\" {\n\t\t\tkeyID, err := repl.ReplaceOrErr(iss.ExternalAccount.KeyID, true, true)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"expanding EAB key ID '%s': %v\", iss.ExternalAccount.KeyID, err)\n\t\t\t}\n\t\t\tiss.ExternalAccount.KeyID = keyID\n\t\t}\n\t\tif iss.ExternalAccount.MACKey != \"\" {\n\t\t\tmacKey, err := repl.ReplaceOrErr(iss.ExternalAccount.MACKey, true, true)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"expanding EAB MAC key (redacted): %v\", err)\n\t\t\t}","sourceCodeStart":138,"sourceCodeEnd":174,"githubUrl":"https://github.com/caddyserver/caddy/blob/50e54ee279aa1e504fe218ca49ab6ae16c100410/modules/caddytls/acmeissuer.go#L138-L174","documentation":"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.","triggerScenarios":"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.","commonSituations":"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}}.","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"],"exampleFix":"# before\n{\n  acme_ca_test {env.STAGING_ENDPOINT}   # unset on this host\n}\n\n# after\n{\n  acme_ca_test https://acme-staging-v02.api.letsencrypt.org/directory\n}","handlingStrategy":"validation","validationCode":"if strings.Contains(cfgText, \"{env.STAGING_CA}\") && os.Getenv(\"STAGING_CA\") == \"\" {\n    return errors.New(\"STAGING_CA referenced by acme_ca_test but not set\")\n}","typeGuard":null,"tryCatchPattern":"if err := issuer.Provision(ctx); err != nil {\n    if strings.Contains(err.Error(), \"expanding TestCA endpoint\") {\n        // set the env var or replace with the literal staging URL\n    }\n    return err\n}","preventionTips":["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"],"tags":["tls","acme","placeholders","staging","environment"],"backgroundTag":null,"analyzedSha":"50e54ee279aa1e504fe218ca49ab6ae16c100410","analyzedAt":"2026-08-15T09:20:21.641Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}