hyperledger/fabric · error
peer.tls.clientKey.file and peer.tls.clientCert.file must bo
Error message
peer.tls.clientKey.file and peer.tls.clientCert.file must both be set or must both be empty
What it means
GetClientCertificate resolves the client key/cert pair used for mutual TLS. If exactly one of peer.tls.clientKey.file and peer.tls.clientCert.file is set (XOR condition), the configuration is invalid and this error is returned; both must be set together or both left empty.
Source
Thrown at core/peer/config.go:469
}
if viper.IsSet("peer.maxSendMsgSize") {
serverConfig.MaxSendMsgSize = int(viper.GetInt32("peer.maxSendMsgSize"))
}
return serverConfig, nil
}
// GetClientCertificate returns the TLS certificate to use for gRPC client
// connections
func GetClientCertificate() (tls.Certificate, error) {
cert := tls.Certificate{}
keyPath := viper.GetString("peer.tls.clientKey.file")
certPath := viper.GetString("peer.tls.clientCert.file")
if keyPath != "" || certPath != "" {
// need both keyPath and certPath to be set
if keyPath == "" || certPath == "" {
return cert, errors.New("peer.tls.clientKey.file and " +
"peer.tls.clientCert.file must both be set or must both be empty")
}
keyPath = config.GetPath("peer.tls.clientKey.file")
certPath = config.GetPath("peer.tls.clientCert.file")
} else {
// use the TLS server keypair
keyPath = viper.GetString("peer.tls.key.file")
certPath = viper.GetString("peer.tls.cert.file")
if keyPath != "" || certPath != "" {
// need both keyPath and certPath to be set
if keyPath == "" || certPath == "" {
return cert, errors.New("peer.tls.key.file and " +
"peer.tls.cert.file must both be set or must both be empty")
}
keyPath = config.GetPath("peer.tls.key.file")
certPath = config.GetPath("peer.tls.cert.file")View on GitHub (pinned to 2736b63f8f)
Solutions
- Set both CORE_PEER_TLS_CLIENTKEY_FILE and CORE_PEER_TLS_CLIENTCERT_FILE (and the corresponding core.yaml keys) to valid paths.
- Or unset/remove both keys if client TLS keypair is not needed.
- Verify both files exist and are non-empty at the configured paths.
- Check docker-compose/K8s env blocks so the pair is always set together.
Example fix
// before (environment) CORE_PEER_TLS_CLIENTKEY_FILE=/etc/hyperledger/fabric/tls/client.key // (clientCert missing) // after CORE_PEER_TLS_CLIENTKEY_FILE=/etc/hyperledger/fabric/tls/client.key CORE_PEER_TLS_CLIENTCERT_FILE=/etc/hyperledger/fabric/tls/client.crt
Defensive patterns
Strategy: validation
Validate before calling
keyPath := viper.GetString("peer.tls.clientKey.file")
certPath := viper.GetString("peer.tls.clientCert.file")
if (keyPath == "") != (certPath == "") {
return errors.New("client key and client cert must both be set or both empty")
} Try / catch
cert, err := GetClientCertificate()
if err != nil && strings.Contains(err.Error(), "clientCert.file must both be set") {
log.Fatalf("set both client key and cert TLS files: %v", err)
} Prevention
- Always set client key/cert as a pair
- Lint paired env vars in deployment configs
- Mount both files in the same secret
When it happens
Trigger: Calling GetClientCertificate when only one of peer.tls.clientKey.file / peer.tls.clientCert.file is non-empty in viper config (core.yaml or CORE_PEER_TLS_CLIENTKEY_FILE / CORE_PEER_TLS_CLIENTCERT_FILE env vars).
Common situations: Setting CORE_PEER_TLS_CLIENTKEY_FILE but forgetting CORE_PEER_TLS_CLIENTCERT_FILE (or vice versa) in docker-compose env; partial secret mount containing only one of the two files; copy-paste config from a non-TLS peer.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- peer.tls.key.file and peer.tls.cert.file must both be set or
- must set either [peer.tls.key.file and peer.tls.cert.file] o
- error loading TLS root certificate (%s)
- cannot load consenter config for orderer type %s: %s
- no TLS certificate sent
AI-assisted analysis of hyperledger/fabric@2736b63f8f (2026-09-04).
Data as JSON: /api/errors/8961bfc7cd32f889.
Report an issue: GitHub.