caddyserver/caddy · error

conflicting config for client authentication trust CA

Error message

conflicting config for client authentication trust CA

What it means

ClientAuthentication.provision rejects configurations that specify both the 'ca' module (CARaw, e.g. an internal PKI CA provider) and one of trusted_ca_certs / trusted_ca_certs_file for the same connection policy. Both are ways to define the client trust CA, and Caddy refuses to guess which one wins.

Source

Thrown at modules/caddytls/connpolicy.go:780

	for block, rest := pem.Decode(certDataPEM); block != nil; block, rest = pem.Decode(rest) {
		if block.Type != "CERTIFICATE" {
			return nil, fmt.Errorf("no CERTIFICATE pem block found in %s", filename)
		}
		ders = append(
			ders,
			base64.StdEncoding.EncodeToString(block.Bytes),
		)
	}
	// if we decoded nothing, return an error
	if len(ders) == 0 {
		return nil, fmt.Errorf("no CERTIFICATE pem block found in %s", filename)
	}
	return ders, nil
}

func (clientauth *ClientAuthentication) provision(ctx caddy.Context) error {
	if len(clientauth.CARaw) > 0 && (len(clientauth.TrustedCACerts) > 0 || len(clientauth.TrustedCACertPEMFiles) > 0) {
		return fmt.Errorf("conflicting config for client authentication trust CA")
	}

	// convert all named file paths to inline
	if len(clientauth.TrustedCACertPEMFiles) > 0 {
		for _, fpath := range clientauth.TrustedCACertPEMFiles {
			ders, err := convertPEMFilesToDER(fpath)
			if err != nil {
				return err
			}
			clientauth.TrustedCACerts = append(clientauth.TrustedCACerts, ders...)
		}
	}

	// if we have TrustedCACerts explicitly set, create an 'inline' CA and return
	if len(clientauth.TrustedCACerts) > 0 {
		caPool := InlineCAPool{
			TrustedCACerts: clientauth.TrustedCACerts,
		}

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Pick ONE trust source: either the 'ca' module or the static cert list/files, and remove the other
  2. If you merged two config fragments, diff your connection policy and delete the duplicate trust setting
  3. Validate the config before deploy: caddy validate --config Caddyfile
  4. If you genuinely need multiple trust anchors, put them all in one trust_ca bundle file and drop the 'ca' module

Example fix

# before
client_auth {
  mode require_and_verify
  ca internal
  trusted_ca_cert_file /etc/caddy/ca.pem
}

# after
client_auth {
  mode require_and_verify
  trusted_ca_cert_file /etc/caddy/ca.pem
}
Defensive patterns

Strategy: validation

Validate before calling

// Reject conflicting client-auth trust config before provisioning
type connPolicyLike struct {
	ClientAuthentication *struct {
		CA                    json.RawMessage `json:"ca,omitempty"`
		TrustedCACerts        []string        `json:"trusted_ca_certs,omitempty"`
		TrustedCACertPEMFiles []string        `json:"trusted_ca_cert_pem_files,omitempty"`
	}
}
func checkNoTrustConflict(p connPolicyLike) error {
	ca := p.ClientAuthentication
	if ca == nil {
		return nil
	}
	if len(ca.CA) > 0 && (len(ca.TrustedCACerts) > 0 || len(ca.TrustedCACertPEMFiles) > 0) {
		return fmt.Errorf("either 'ca' module or trusted_ca_certs(_pem_files), not both")
	}
	return nil
}

Prevention

When it happens

Trigger: In JSON: setting both "client_authentication": {"ca": {...}} and "trusted_ca_certs" or "trusted_ca_cert PEM files" on the same policy. In Caddyfile: mixing the 'ca' subdirective with 'trusted_ca_cert' / 'trusted_ca_cert_file' inside a client_auth block.

Common situations: Migrating from static CA pinning to the internal CA module (or vice versa) and leaving the old directive behind; copying example configs that use both styles; layered config includes that each add one option.

Understand the failure class

Related errors


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