caddyserver/caddy · error

unable to add %s to trust pool: %v

Error message

unable to add %s to trust pool: %v

What it means

Returned by ACMEIssuer.Provision (modules/caddytls/acmeissuer.go:240) when the PEM file was read successfully but x509.CertPool.AppendCertsFromPEM returned false - the content contains no parseable certificate blocks. Note the wrapped err at this point is nil (it comes from the earlier successful ReadFile), so the trailing %v prints '<nil>'; the real information is the file path itself.

Source

Thrown at modules/caddytls/acmeissuer.go:240

				PropagationDelay:   time.Duration(iss.Challenges.DNS.PropagationDelay),
				PropagationTimeout: time.Duration(iss.Challenges.DNS.PropagationTimeout),
				Resolvers:          iss.Challenges.DNS.Resolvers,
				OverrideDomain:     iss.Challenges.DNS.OverrideDomain,
				Logger:             iss.logger.Named("dns_manager"),
			},
		}
	}

	// add any custom CAs to trust store
	if len(iss.TrustedRootsPEMFiles) > 0 {
		iss.rootPool = x509.NewCertPool()
		for _, pemFile := range iss.TrustedRootsPEMFiles {
			pemData, err := os.ReadFile(pemFile)
			if err != nil {
				return fmt.Errorf("loading trusted root CA's PEM file: %s: %v", pemFile, err)
			}
			if !iss.rootPool.AppendCertsFromPEM(pemData) {
				return fmt.Errorf("unable to add %s to trust pool: %v", pemFile, err)
			}
		}
	}

	var err error
	iss.template, err = iss.makeIssuerTemplate(ctx)
	if err != nil {
		return err
	}

	return nil
}

func (iss *ACMEIssuer) makeIssuerTemplate(ctx caddy.Context) (certmagic.ACMEIssuer, error) {
	template := certmagic.ACMEIssuer{
		CA:                iss.CA,
		TestCA:            iss.TestCA,
		Email:             iss.Email,

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Verify the file is PEM text with certificate blocks: grep -c 'BEGIN CERTIFICATE' file (should be >=1) and openssl x509 -in file -noout
  2. If it is DER, convert: openssl x509 -inform DER -in file -out file.pem
  3. If it is a key/CSR, replace it with the CA's certificate PEM
  4. Re-download/restore the file and confirm its size matches the source

Example fix

# before: DER-encoded root
 trusted_roots /etc/caddy/roots/root-ca.der

# after: convert to PEM and use it
openssl x509 -inform DER -in root-ca.der -out root-ca.pem
# config:
 trusted_roots /etc/caddy/roots/root-ca.pem
Defensive patterns

Strategy: validation

Validate before calling

# verify each trust file actually contains PEM certificates
for f in /etc/caddy/roots/*.pem; do
  grep -q 'BEGIN CERTIFICATE' "$f" || { echo "$f has no PEM cert block"; exit 1; }
  openssl x509 -in "$f" -noout || exit 1
done

Type guard

// Go guard: file must yield at least one PEM certificate block
func hasPEMCertificates(data []byte) bool {
	rest := data
	for {
		var block *pem.Block
		block, rest = pem.Decode(rest)
		if block == nil {
			return false
		}
		if block.Type == "CERTIFICATE" {
			return true
		}
	}
}

Try / catch

if err := issuer.Provision(ctx); err != nil {
    if strings.Contains(err.Error(), "unable to add") && strings.HasSuffix(err.Error(), "<nil>") {
        // note: wrapped err is nil here - the file parsed but had no cert blocks;
        // convert DER->PEM or replace key/CSR with the certificate
    }
    return err
}

Prevention

When it happens

Trigger: The trusted-roots file contains a private key instead of a certificate, DER-encoded (binary) rather than PEM data, a chain where only the key/CSR blocks are present, or PEM with mangled headers/base64 (truncated download, Windows line-ending corruption in rare cases, or an HTML error page saved as .pem).

Common situations: Saving the wrong half of a CA pair (key instead of cert); openssl x509 -outform DER used by mistake; truncated files after failed scp/curl; concatenating files without trailing newlines between blocks; fetching from a URL that returned an error page.

Related errors


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