caddyserver/caddy · error
expanding email address '%s': %v
Error message
expanding email address '%s': %v
What it means
Returned by ACMEIssuer.Provision (modules/caddytls/acmeissuer.go:138) when the configured email address contains a placeholder and the Caddy replacer fails to expand it. ReplaceOrErr is called with errorOnUnset=true, so an unknown/unresolvable placeholder or a malformed placeholder expression is an error rather than an empty string. Fails at config provisioning, before any ACME traffic.
Source
Thrown at modules/caddytls/acmeissuer.go:138
// CaddyModule returns the Caddy module information.
func (ACMEIssuer) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
ID: "tls.issuance.acme",
New: func() caddy.Module { return new(ACMEIssuer) },
}
}
// Provision sets up iss.
func (iss *ACMEIssuer) Provision(ctx caddy.Context) error {
iss.logger = ctx.Logger()
repl := caddy.NewReplacer()
// expand email address, if non-empty
if iss.Email != "" {
email, err := repl.ReplaceOrErr(iss.Email, true, true)
if err != nil {
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)View on GitHub (pinned to 50e54ee279)
Solutions
- Set the missing variable in the environment Caddy actually runs in (systemctl edit caddy -> Environment=..., or the container env) and restart
- Fix the placeholder syntax: exactly {env.VAR_NAME} with matching braces
- If the value is not secret-dependent, hardcode the email or use the ACME_EMAIL/admin email defaults and drop the placeholder
- Test with caddy adapt + caddy validate --adapter caddyfile to catch it before reload
Example fix
# before
example.com {
tls {
email {env.ACME_MAIL} # typo: variable is ACME_EMAIL
}
}
# after
example.com {
tls {
email {env.ACME_EMAIL}
}
}
# and: systemctl edit caddy -> [Service] Environment=ACME_EMAIL=me@example.com Defensive patterns
Strategy: validation
Validate before calling
// expose placeholder problems before Caddy loads the config
import "os"
func envPlaceholdersSet(cfg string) error {
re := regexp.MustCompile(`\{env\.([A-Za-z0-9_]+)}`)
for _, m := range re.FindAllStringSubmatch(cfg, -1) {
if os.Getenv(m[1]) == "" {
return fmt.Errorf("placeholder {env.%s} is not set", m[1])
}
}
return nil
} Try / catch
if err := issuer.Provision(ctx); err != nil {
if strings.Contains(err.Error(), "expanding email address") {
// unset env var or bad placeholder: fix environment, reload config
}
return err
} Prevention
- Run caddy validate with the production environment (EnvironmentFile) as part of deploy
- Centralize env vars in one EnvironmentFile with documented keys
- Prefer {env.VAR} consistently and grep configs for undefined vars in CI
- Avoid placeholders for values you can set directly (admin email global option)
When it happens
Trigger: Setting email to a placeholder like {env.ACME_EMAIL} in the tls/ACME issuer config when the environment variable is not set in Caddy's process, or using placeholder syntax the replacer cannot resolve (typo, unsupported key, unclosed braces).
Common situations: Env var name mismatch (ACME_EMAIL vs CERT_EMAIL); .env files loaded by a wrapper but not by the systemd unit running Caddy; container env vars set in one service but not the one running Caddy; typos like {env.EMAIL}}.
Related errors
- expanding CA endpoint '%s': %v
- expanding TestCA endpoint '%s': %v
- expanding EAB key ID '%s': %v
- expanding EAB MAC key (redacted): %v
- invalid key type %s: %s
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/05467b6e3eda9003.
Report an issue: GitHub.