hyperledger/fabric · error

client certificate isn't in PEM format

Error message

client certificate isn't in PEM format: %v

What it means

NewBlockPuller validates the orderer's own TLS client certificate before building the cluster block puller. The certificate bytes in the dialer's SecOpts.Certificate must be PEM-encoded; if pem.Decode fails, the configured certificate is malformed and the puller cannot be created. The raw bytes are included in the error to aid diagnosis.

Solutions

  1. Verify General.TLS.Certificate (and General.Cluster.ClientCertificate) point to a valid PEM file beginning with '-----BEGIN CERTIFICATE-----'.
  2. Re-encode: openssl x509 -in cert.crt -out cert.pem and redeploy the orderer with the corrected file.
  3. If injecting via env/Kubernetes config, ensure newlines are preserved (use | multiline YAML or proper file mounts, not flattened env values).
  4. Confirm the private key (TLS.PrivateKey) and cert are a matching pair with openssl x509 / openssl rsa modulus comparison.

Example fix

// before: DER or header-less cert loaded
SecOpts.Certificate = rawDerBytes

// after: proper PEM-encoded certificate
pemBytes, _ := os.ReadFile("orderer.crt") // starts with -----BEGIN CERTIFICATE-----
SecOpts.Certificate = pemBytes
Defensive patterns

Strategy: validation

Validate before calling

func isPEMCert(b []byte) bool {
    block, _ := pem.Decode(b)
    if block == nil || block.Type != "CERTIFICATE" {
        return false
    }
    _, err := x509.ParseCertificate(block.Bytes)
    return err == nil
}
// call before building SecOpts: if !isPEMCert(certBytes) { fail fast }

Type guard

func validPEMCertificate(data []byte) bool {
    der, _ := pem.Decode(data)
    return der != nil
}

Prevention

When it happens

Trigger: Starting an etcdraft chain (NewBlockPuller via EndpointconfigFromSupport flow) when generalTLS or cluster TLS client certificate material in configuration (General.TLS.Certificate / General.Cluster.ClientCertificate) is not valid PEM — e.g., raw DER bytes, base64-only content, or an empty/garbled file.

Common situations: Config map/env var corruption where newlines in the PEM got mangled (e.g., single-line env values), pasting a certificate without the BEGIN/END headers, or generating a cert and forgetting PEM encoding.

Understand the failure class

Related errors


AI-assisted analysis of hyperledger/fabric@2736b63f8f (2026-09-04). Data as JSON: /api/errors/67e10ceecd912981. Report an issue: GitHub.

Appendix: source

Thrown at orderer/consensus/etcdraft/blockpuller.go:88

		vb := cluster.BlockVerifierBuilder(bccsp)
		return cluster.VerifyBlocksBFT(blocks, support.SignatureVerifier(), vb)
	}

	stdDialer := &cluster.StandardDialer{
		Config: baseDialer.Config,
	}
	stdDialer.Config.AsyncConnect = false
	stdDialer.Config.SecOpts.VerifyCertificate = nil

	// Extract the TLS CA certs and endpoints from the configuration,
	endpoints, err := EndpointconfigFromSupport(support, bccsp)
	if err != nil {
		return nil, err
	}

	der, _ := pem.Decode(stdDialer.Config.SecOpts.Certificate)
	if der == nil {
		return nil, errors.Errorf("client certificate isn't in PEM format: %v",
			string(stdDialer.Config.SecOpts.Certificate))
	}

	logger := flogging.MustGetLogger("orderer.common.cluster.puller").With("channel", support.ChannelID())

	myCert, err := x509.ParseCertificate(der.Bytes)
	if err != nil {
		logger.Warnf("Failed parsing my own TLS certificate: %v, therefore we may connect to our own endpoint when pulling blocks", err)
	}

	bp := &cluster.BlockPuller{
		MyOwnTLSCert:        myCert,
		VerifyBlockSequence: verifyBlockSequence,
		Logger:              logger,
		RetryTimeout:        clusterConfig.ReplicationRetryTimeout,
		MaxTotalBufferBytes: clusterConfig.ReplicationBufferSize,
		FetchTimeout:        clusterConfig.ReplicationPullTimeout,
		Endpoints:           endpoints,

View on GitHub (pinned to 2736b63f8f)