temporalio/temporal · error

failed to decode PEM certificate data

Error message

failed to decode PEM certificate data

What it means

parseCert decodes PEM blocks from a cert file/data and builds an x509.Certificate. After iterating all PEM blocks, if no CERTIFICATE-typed block yielded non-empty DER bytes, it returns "failed to decode PEM certificate data". This means the input contained no usable certificate, though some parsing may have partially succeeded. It guards x509.ParseCertificate from being called with empty input.

Source

Thrown at common/rpc/encryption/local_store_cert_provider.go:484

}

// logic borrowed from tls.X509KeyPair()
func parseCert(bytes []byte) (*x509.Certificate, error) {

	var certBytes [][]byte
	for {
		var certDERBlock *pem.Block
		certDERBlock, bytes = pem.Decode(bytes)
		if certDERBlock == nil {
			break
		}
		if certDERBlock.Type == "CERTIFICATE" {
			certBytes = append(certBytes, certDERBlock.Bytes)
		}
	}

	if len(certBytes) == 0 || len(certBytes[0]) == 0 {
		return nil, fmt.Errorf("failed to decode PEM certificate data")
	}
	return x509.ParseCertificate(certBytes[0])
}

func appendError(aggregatedErr error, err error) error {
	if aggregatedErr == nil {
		return err
	}
	if err == nil {
		return aggregatedErr
	}
	return fmt.Errorf("%v, %w", aggregatedErr, err)
}

func (s *localStoreCertProvider) refreshCerts() {

	for {
		select {

View on GitHub (pinned to bde624efd1)

Solutions

  1. Verify the referenced file/data actually contains a PEM block starting with '-----BEGIN CERTIFICATE-----'.
  2. Check you did not swap cert and key paths/data in configuration (key passed where cert expected).
  3. Re-export or re-create the certificate; confirm it is non-empty and PEM-encoded (openssl x509 -in cert.pem -text).
  4. If you have raw DER bytes, wrap them with encoding/pem before passing them in.

Example fix

// before
certData := "MIIDczCCA..." // raw DER base64, no PEM armor
// after
pemData := "-----BEGIN CERTIFICATE-----\n" + certData + "\n-----END CERTIFICATE-----"
Defensive patterns

Strategy: validation

Validate before calling

func hasPEMCert(data []byte) bool {
    for block, rest := pem.Decode(data); block != nil; block, rest = pem.Decode(rest) {
        if block.Type == "CERTIFICATE" && len(block.Bytes) > 0 {
            return true
        }
    }
    return false
}

Type guard

if data == nil || len(data) == 0 || !hasPEMCert(data) { return errors.New("no PEM CERTIFICATE block found") }

Try / catch

if _, err := parseCert(data); err != nil {
    if strings.Contains(err.Error(), "failed to decode PEM certificate data") {
        // fall back to an alternate cert source or fail fast with a clear config error
    }
}

Prevention

When it happens

Trigger: Calling parseCert (via buildCAPool or FetchServerCertificate/FetchClientCAs in localStoreCertProvider) with PEM data that contains zero CERTIFICATE blocks — e.g. only PRIVATE KEY blocks, garbage text, an empty file, or base64 blob passed where PEM is expected.

Common situations: Mounting a Kubernetes secret with the wrong key (tls.key instead of tls.crt); an empty cert file on disk after a failed secret sync; passing raw DER bytes instead of PEM-encoded text; a config pointing at a configmap key that holds a private key rather than a certificate.

Understand the failure class

Related errors


AI-assisted analysis of temporalio/temporal@bde624efd1 (2026-09-01). Data as JSON: /api/errors/b166bb9187aef904. Report an issue: GitHub.