caddyserver/caddy · error

expanding email address

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.

Solutions

  1. Set the missing variable in the environment Caddy actually runs in (systemctl edit caddy -> Environment=..., or the container env) and restart
  2. Fix the placeholder syntax: exactly {env.VAR_NAME} with matching braces
  3. If the value is not secret-dependent, hardcode the email or use the ACME_EMAIL/admin email defaults and drop the placeholder
  4. 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

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


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

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