caddyserver/caddy · error

global options are ambiguous: local_certs is confusing when

Error message

global options are ambiguous: local_certs is confusing when combined with cert_issuer, because local_certs is also a specific kind of issuer

What it means

newBaseAutomationPolicyFromDefaults rejects the global options combination of issuers (from 'cert_issuer', possibly multiple) together with 'local_certs'. local_certs is itself sugar for the internal issuer, so specifying both makes the intended issuer list ambiguous; the adapter refuses to guess.

Source

Thrown at caddyconfig/httpcaddyfile/tlsapp.go:937

	globalPreferredChains := options["preferred_chains"]
	hasGlobalACMEDefaults := globalACMECA != nil || globalACMECARoot != nil || globalACMEDNS || globalACMEEAB != nil || globalPreferredChains != nil

	// if there are no global options related to automation policies
	// set, then we can just return right away
	if !hasGlobalAutomationOpts && !hasGlobalACMEDefaults {
		if always {
			return new(caddytls.AutomationPolicy), nil
		}
		return nil, nil
	}

	ap := new(caddytls.AutomationPolicy)
	if hasKeyType {
		ap.KeyType = keyType.(string)
	}

	if hasIssuers && hasLocalCerts {
		return nil, fmt.Errorf("global options are ambiguous: local_certs is confusing when combined with cert_issuer, because local_certs is also a specific kind of issuer")
	}

	if hasIssuers {
		ap.Issuers = issuers.([]certmagic.Issuer)
	} else if hasLocalCerts {
		ap.Issuers = []certmagic.Issuer{new(caddytls.InternalIssuer)}
	}

	if hasGlobalACMEDefaults {
		for i := range ap.Issuers {
			if err := fillInGlobalACMEDefaults(ap.Issuers[i], options); err != nil {
				return nil, fmt.Errorf("filling in global issuer defaults for issuer %d: %v", i, err)
			}
		}
	}

	if hasOCSPStapling {
		ocspConfig := ocspStapling.(certmagic.OCSPConfig)

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Remove 'local_certs' if you want the explicit cert_issuer to apply.
  2. Or remove 'cert_issuer' if you want internal self-signed certs everywhere.
  3. To mix internal and public per site, drop both globals and use per-site 'tls internal' / 'tls issuer acme'.

Example fix

# before
{
  local_certs
  cert_issuer acme
}

# after
{
  cert_issuer acme
}
Defensive patterns

Strategy: validation

Validate before calling

grep -nE 'local_certs|cert_issuer' Caddyfile  # both present => ambiguity

Prevention

When it happens

Trigger: A global options block containing both 'local_certs' and 'cert_issuer <module> ...' (e.g. cert_issuer acme).

Common situations: Migrating a config from self-signed internal certs to ACME and leaving local_certs behind; copy-pasting a cert_issuer block into a template that already had local_certs.

Related errors


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