caddyserver/caddy · error

expanding account key PEM '%s': %v

Error message

expanding account key PEM '%s': %v

What it means

Returned by ACMEIssuer.Provision (modules/caddytls/acmeissuer.go:183) when the ACME account key PEM field (JSON "account_key", a full private key embedded in config) contains a placeholder that fails to expand. Caddy allows the account key to be supplied via placeholders (e.g. {env.ACME_ACCOUNT_KEY} or a file read via {http...}/{file...} style replacements), and an unresolvable one is a hard error.

Source

Thrown at modules/caddytls/acmeissuer.go:183

			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)
			}
			iss.ExternalAccount.MACKey = macKey
		}
	}

	// expand account key, if non-empty
	if iss.AccountKey != "" {
		accountKey, err := repl.ReplaceOrErr(iss.AccountKey, true, true)
		if err != nil {
			return fmt.Errorf("expanding account key PEM '%s': %v", iss.AccountKey, err)
		}
		iss.AccountKey = accountKey
	}

	// expand DNS override domain, if non-empty
	if iss.Challenges != nil && iss.Challenges.DNS != nil && iss.Challenges.DNS.OverrideDomain != "" {
		overrideDomain, err := repl.ReplaceOrErr(iss.Challenges.DNS.OverrideDomain, true, true)
		if err != nil {
			return fmt.Errorf("expanding DNS override domain '%s': %v", iss.Challenges.DNS.OverrideDomain, err)
		}
		iss.Challenges.DNS.OverrideDomain = overrideDomain
	}

	// DNS challenge provider, if not already established
	if iss.Challenges != nil && iss.Challenges.DNS != nil && iss.Challenges.DNS.solver == nil {
		var prov certmagic.DNSProvider
		if iss.Challenges.DNS.ProviderRaw != nil {
			// a challenge provider has been locally configured - use it

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Ensure the env var/secret holding the PEM exists on every node running Caddy and restart/reload
  2. Verify the var contains a valid single-line-escaped or raw PEM that certmagic can decode after expansion; test: printenv ACCOUNT_KEY_PEM | openssl pkey -noout
  3. Fix placeholder syntax to exactly {env.VAR}
  4. If you do not need a pinned account key, remove the field - Caddy/certmagic will generate and persist one in storage

Example fix

// before
"issuer": {
  "module": "acme",
  "account_key": "{env.ACCT_KEY}"   // ACCT_KEY unset
}

// after
"issuer": {
  "module": "acme"
}
// let certmagic manage the account key in storage, or set ACCT_KEY to the PEM in the service env
Defensive patterns

Strategy: validation

Validate before calling

// if pinning an account key, verify the env var holds a parseable PEM first
if v := os.Getenv("ACCT_KEY"); v != "" {
    if _, err := certmagic.PEMDecodePrivateKey([]byte(v)); err != nil {
        return fmt.Errorf("ACCT_KEY is not a valid private key PEM: %v", err)
    }
} else if strings.Contains(cfgText, "{env.ACCT_KEY}") {
    return errors.New("ACCT_KEY referenced but not set")
}

Try / catch

if err := issuer.Provision(ctx); err != nil {
    if strings.Contains(err.Error(), "expanding account key PEM") {
        // account key placeholder unresolved: set env var or drop the field
    }
    return err
}

Prevention

When it happens

Trigger: Setting "account_key": "{env.ACCOUNT_KEY_PEM}" on an ACME issuer where the environment variable is missing, or where the placeholder is misspelled; the error occurs even though the raw PEM is never echoed (the message includes iss.AccountKey, i.e. the unexpanded placeholder text, not a secret value, when the placeholder itself is the configured string).

Common situations: Pinning a pre-created ACME account across restarts/clusters by injecting its key via env or secrets; var present on the old node but not on new nodes during scaling; multi-line PEM stored in env vars mangled by YAML/compose folding.

Related errors


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