temporalio/temporal · error · ErrTLSConfig
unable to decode client certificate
Error message
unable to decode client certificate
What it means
parseClientCert returns this error when CertData is set but base64.StdEncoding.DecodeString fails — the value is not valid base64. The decode error is chained and wrapped with ErrTLSConfig so the caller knows their inline client certificate is malformed before any TLS handshake occurs.
Source
Thrown at common/auth/tls_config_helper.go:200
certBytes := block.Bytes
return x509.ParseCertificates(certBytes)
}
return nil, nil
}
func parseClientCert(temporalTls *TLS) (*tls.Certificate, error) {
var certBytes []byte
var keyBytes []byte
var err error
if temporalTls.CertFile != "" {
certBytes, err = os.ReadFile(temporalTls.CertFile)
if err != nil {
return nil, fmt.Errorf("%w: %s (%w)", ErrTLSConfig, "unable to read client certificate file", err)
}
} else if temporalTls.CertData != "" {
certBytes, err = base64.StdEncoding.DecodeString(temporalTls.CertData)
if err != nil {
return nil, fmt.Errorf("%w: %s (%w)", ErrTLSConfig, "unable to decode client certificate", err)
}
}
if temporalTls.KeyFile != "" {
keyBytes, err = os.ReadFile(temporalTls.KeyFile)
if err != nil {
return nil, fmt.Errorf("%w: %s (%w)", ErrTLSConfig, "unable to read client certificate private key file", err)
}
} else if temporalTls.KeyData != "" {
keyBytes, err = base64.StdEncoding.DecodeString(temporalTls.KeyData)
if err != nil {
return nil, fmt.Errorf("%w: %s (%w)", ErrTLSConfig, "unable to decode client certificate private key", err)
}
}
if len(certBytes) > 0 {
clientCert, err := tls.X509KeyPair(certBytes, keyBytes)
if err != nil {View on GitHub (pinned to bde624efd1)
Solutions
- Regenerate the value with base64 -w0 client.pem and paste that exact single-line string into CertData.
- Confirm it decodes once to PEM text starting with -----BEGIN CERTIFICATE----- (not base64 of base64).
- Remove newlines/whitespace introduced by YAML block scalars or templating; use a folded/single-line string.
- Switch to CertFile to avoid inline encoding issues entirely.
Example fix
// before tls: certData: "-----BEGIN CERTIFICATE-----\nMIID..." # raw PEM // after tls: certData: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUQ..." # base64 -w0 client.pem
Defensive patterns
Strategy: validation
Validate before calling
func checkCertDataIsBase64(certData string) error {
decoded, err := base64.StdEncoding.DecodeString(certData)
if err != nil {
return fmt.Errorf("certData is not valid base64: %w", err)
}
if !strings.Contains(string(decoded), "BEGIN CERTIFICATE") {
return fmt.Errorf("certData does not decode to PEM certificate")
}
return nil
} Prevention
- Encode inline certs with base64 -w0 client.pem and store as a single-line string
- Avoid YAML block scalars for base64 values, or strip inserted newlines
- Verify locally before deploy: echo '<value>' | base64 -d | openssl x509 -noout -subject
- Prefer certFile mounts over inline data when the format fights you
When it happens
Trigger: NewTLSConfig -> parseClientCert with CertData containing raw PEM text, truncated base64, or a string corrupted by whitespace/newlines/escaping during config templating.
Common situations: Pasting the PEM cert directly instead of base64-encoding it; kubectl/secret tooling that already wrapped or altered the base64; YAML multiline strings inserting newlines into the value; double-encoding leaving invalid characters.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- unable to decode client ca data
- unable to read client certificate file
- only one of certData or certFile properties should be specif
- only one of certData or certFile properties should be specif
- only one of keyData or keyFile properties should be specifie
AI-assisted analysis of temporalio/temporal@bde624efd1 (2026-09-01).
Data as JSON: /api/errors/3c1f9433a980fdfd.
Report an issue: GitHub.