caddyserver/caddy · error

certificate lifetime (%s) should be less than intermediate c

Error message

certificate lifetime (%s) should be less than intermediate certificate lifetime (%s)

What it means

Enforced by the internal ACME server (smallstep ACME CA) at provisioning: a certificate authority's leaf `lifetime` must be strictly shorter than the intermediate certificate's lifetime. It only applies when Caddy manages the intermediate itself (ca.Intermediate == nil, i.e. no external intermediate cert was configured); with an externally supplied intermediate the check is skipped. Default intermediate lifetime is 7 days (defaultIntermediateLifetime = 24h*7 in ca.go).

Source

Thrown at modules/caddypki/acmeserver/acmeserver.go:160

	}

	ash.warnIfPolicyAllowsAll()

	// get a reference to the configured CA
	appModule, err := ctx.App("pki")
	if err != nil {
		return err
	}
	pkiApp := appModule.(*caddypki.PKI)
	ca, err := pkiApp.GetCA(ctx, ash.CA)
	if err != nil {
		return err
	}

	// make sure leaf cert lifetime is less than the intermediate cert lifetime. this check only
	// applies for caddy-managed intermediate certificates
	if ca.Intermediate == nil && ash.Lifetime >= ca.IntermediateLifetime {
		return fmt.Errorf("certificate lifetime (%s) should be less than intermediate certificate lifetime (%s)", time.Duration(ash.Lifetime), time.Duration(ca.IntermediateLifetime))
	}

	database, err := ash.openDatabase()
	if err != nil {
		return err
	}

	authorityConfig := caddypki.AuthorityConfig{
		SignWithRoot: ash.SignWithRoot,
		AuthConfig: &authority.AuthConfig{
			Provisioners: provisioner.List{
				&provisioner.ACME{
					Name:       ash.CA,
					Challenges: ash.Challenges.toSmallstepType(),
					Options: &provisioner.Options{
						X509: ash.Policy.normalizeRules(),
					},
					Type: provisioner.TypeACME.String(),

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Lower the handler's `lifetime` below the intermediate's lifetime — e.g. `lifetime 12h` (default 12h works with the 7d default intermediate)
  2. Or raise the CA's intermediate lifetime first: set `intermediate_lifetime` on the CA (e.g. 720h) in the pki app config, then the leaf lifetime can go up
  3. Or provide your own external intermediate certificate to the CA (via `intermediate` key/cert), which bypasses this check
  4. Verify by validating config: `caddy validate --config Caddyfile`

Example fix

# before
{
  acme_server {
    lifetime 720h
  }
}

# after (option A: shorter leaf)
{
  acme_server {
    lifetime 24h
  }
}
# after (option B: longer intermediate, pki app JSON)
"pki": { "certificate_authorities": { "local": { "intermediate_lifetime": 2160 } } }
Defensive patterns

Strategy: validation

Validate before calling

// Check lifetimes before loading acme_server config:
const defaultIntermediate = 7 * 24 * time.Hour
if intermediateLifetime == 0 { intermediateLifetime = defaultIntermediate }
if externalIntermediate == nil && leafLifetime >= intermediateLifetime {
    return fmt.Errorf("leaf %s must be < intermediate %s", leafLifetime, intermediateLifetime)
}

Prevention

When it happens

Trigger: Configuring the ACME server handler with `lifetime 168h` (or any value >= 7d = 168h) while relying on Caddy's own intermediate certificate: `if ca.Intermediate == nil && ash.Lifetime >= ca.IntermediateLifetime` triggers. Also with intermediate_lifetime set low and lifetime not lowered to match.

Common situations: Raising certificate lifetime on an internal ACME server for device/iot clients that cache certs for weeks; setting lifetime to 720h for a private CA without realizing the stock intermediate lives 7 days; mixing lifetime/intermediate_lifetime units (Caddyfile durations are d/h/m/s).

Understand the failure class

Related errors


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