cloudflare/cloudflared · error

error appending custom CA to cert pool

Error message

error appending custom CA to cert pool

What it means

LoadCustomOriginCA reads a user-supplied CA PEM file (via --origin-ca-pool) and adds it to an x509 CertPool. If crypto/x509's AppendCertsFromPEM returns false — the file parsed to zero valid PEM certificates — cloudflared returns this error because the origin CA pool would otherwise silently trust nothing, breaking origin TLS validation.

Source

Thrown at tlsconfig/origin_ca.go:71

	if err != nil {
		return nil, errors.Wrap(err, "could not append Cloudflare Root CAs to cloudflared certificate pool")
	}
	for _, cert := range cfRootCA {
		certPool.AddCert(cert)
	}

	if originCAFilename == "" {
		return certPool, nil
	}

	// nolint: gosec
	customOriginCA, err := os.ReadFile(originCAFilename)
	if err != nil {
		return nil, errors.Wrap(err, fmt.Sprintf("unable to read the file %s", originCAFilename))
	}

	if !certPool.AppendCertsFromPEM(customOriginCA) {
		return nil, fmt.Errorf("error appending custom CA to cert pool")
	}
	return certPool, nil
}

func CreateTunnelConfig(caCert string, serverName string) (*tls.Config, error) {
	tlsConfig := &tls.Config{ServerName: serverName}
	if caCert != "" {
		caCertPEM, err := os.ReadFile(caCert) //nolint:gosec
		if err != nil {
			return nil, fmt.Errorf("read CA certificate %s: %w", caCert, err)
		}

		rootCAPool := x509.NewCertPool()
		if !rootCAPool.AppendCertsFromPEM(caCertPEM) {
			return nil, fmt.Errorf("parse CA certificate %s", caCert)
		}
		tlsConfig.RootCAs = rootCAPool
	}

View on GitHub (pinned to 2253eeeb25)

Solutions

  1. Convert the certificate to PEM format: `openssl x509 -inform DER -in ca.der -out ca.pem` and pass the PEM file.
  2. Verify the file actually contains certificates: `openssl x509 -in ca.pem -noout -subject` should print a subject.
  3. Check you are not passing a private key or CSR file; the pool needs CA certificates.
  4. Confirm the path via --origin-ca-pool is correct and the file is non-empty (`ls -l`, `head -1` should show -----BEGIN CERTIFICATE-----).
  5. Concatenate intermediate + root CAs into one PEM bundle if the chain is split across files.

Example fix

// before (DER file passed directly)
originServerName: "example.com"
originCA: "/etc/ssl/certs/ca.der"
// after (converted to PEM)
# openssl x509 -inform DER -in /etc/ssl/certs/ca.der -out /etc/ssl/certs/ca.pem
originServerName: "example.com"
originCA: "/etc/ssl/certs/ca.pem"
Defensive patterns

Strategy: validation

Validate before calling

// guard before invoking the flag path
pemBytes, err := os.ReadFile(caPath)
if err != nil {
	return err
}
if !bytes.Contains(pemBytes, []byte("-----BEGIN CERTIFICATE-----")) {
	return fmt.Errorf("%s is not a PEM certificate bundle", caPath)
}
block, _ := pem.Decode(pemBytes)
if block == nil || block.Type != "CERTIFICATE" {
	return fmt.Errorf("%s has no PEM CERTIFICATE block", caPath)
}
if _, err := x509.ParseCertificate(block.Bytes); err != nil {
	return fmt.Errorf("%s contains an unparseable certificate: %v", caPath, err)
}

Type guard

func isPEMCertBundle(data []byte) bool {
	block, _ := pem.Decode(data)
	return block != nil && block.Type == "CERTIFICATE"
}

Try / catch

pool, err := tlsconfig.LoadCustomOriginCA(caPath)
if err != nil {
	if strings.Contains(err.Error(), "appending custom CA") {
		return fmt.Errorf("CA pool file %s is not valid PEM: %w", caPath, err)
	}
	return err
}

Prevention

When it happens

Trigger: --origin-ca-pool points to a file whose content is not PEM (DER-encoded cert, private key, HTML error page, empty file), contains certificates in an unsupported format, or the file read succeeded but every PEM block failed to parse as a certificate.

Common situations: Exporting a cert from a browser/Windows store as DER instead of PEM; pointing the flag at a private key or bundle of CSRs; config copied from docs with a placeholder path; a proxy download saving an HTML login page as ca.pem.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


AI-assisted analysis of cloudflare/cloudflared@2253eeeb25 (2026-09-06). Data as JSON: /api/errors/ff658a82c9f40f45. Report an issue: GitHub.