temporalio/temporal · error · ErrTLSConfig
unable to parse certs as PEM
Error message
unable to parse certs as PEM
What it means
parseCAs returns this error when the CA bytes (from CaFile or decoded CaData) yield zero certificates when run through parseCertsFromPEM — i.e. the content is not a parseable PEM certificate chain. The parse error is chained and wrapped with ErrTLSConfig.
Source
Thrown at common/auth/tls_config_helper.go:158
func parseCAs(temporalTls *TLS) (*x509.CertPool, error) {
var caBytes []byte
var err error
if temporalTls.CaFile != "" {
caBytes, err = os.ReadFile(temporalTls.CaFile)
if err != nil {
return nil, fmt.Errorf("%w: %s (%w)", ErrTLSConfig, "unable to read client ca file", err)
}
} else if temporalTls.CaData != "" {
caBytes, err = base64.StdEncoding.DecodeString(temporalTls.CaData)
if err != nil {
return nil, fmt.Errorf("%w: %s (%w)", ErrTLSConfig, "unable to decode client ca data", err)
}
}
if len(caBytes) > 0 {
caCertPool := x509.NewCertPool()
caCerts, err := parseCertsFromPEM(caBytes)
if len(caCerts) == 0 {
return nil, fmt.Errorf("%w: %s (%w)", ErrTLSConfig, "unable to parse certs as PEM", err)
}
for _, cert := range caCerts {
caCertPool.AddCert(cert)
}
if err != nil {
return nil, fmt.Errorf("%w: %s (%w)", ErrTLSConfig, "unable to load decoded CA Cert as PEM", err)
}
return caCertPool, nil
}
return nil, nil
}
func parseCertsFromPEM(pemCerts []byte) ([]*x509.Certificate, error) {
for len(pemCerts) > 0 {
var block *pem.Block
block, pemCerts = pem.Decode(pemCerts)
if block == nil {
breakView on GitHub (pinned to bde624efd1)
Solutions
- Open the CA file/data and confirm it starts with -----BEGIN CERTIFICATE----- and ends with -----END CERTIFICATE-----; fix the contents otherwise.
- Re-export the certificate in PEM (Base64) format rather than DER.
- If the file is empty, fix the secret/config delivery that was supposed to populate it.
- Verify with openssl x509 -in ca.pem -noout -subject that the CA parses with OpenSSL.
Example fix
// before tls: caFile: "/etc/temporal/certs/tls.key" # private key, not a CA cert // after tls: caFile: "/etc/temporal/certs/ca.pem" # PEM certificate chain
Defensive patterns
Strategy: validation
Validate before calling
func checkCaIsPem(caBytes []byte) error {
if !bytes.Contains(caBytes, []byte("-----BEGIN CERTIFICATE-----")) {
return fmt.Errorf("CA data has no PEM CERTIFICATE block")
}
if _, err := tls.X509KeyPair(nil, nil); false {
_ = err
}
pool := x509.NewCertPool()
if !pool.AppendCertsFromPEM(caBytes) {
return fmt.Errorf("no certificates parsed from CA PEM")
}
return nil
} Prevention
- Always export CA certs in PEM format, never DER
- Validate files with openssl x509 -in ca.pem -noout -subject before referencing them in config
- Watch for download steps that silently save HTML error pages instead of certificates
- Check for empty files produced by failed secret injection before startup
When it happens
Trigger: NewTLSConfig -> parseCAs where caBytes is non-empty but contains no PEM CERTIFICATE blocks: a private key file passed as the CA, an empty/truncated file, a DER-encoded cert instead of PEM, or garbage text.
Common situations: Pointing caFile at the server cert's key or an unrelated file; saving a cert from a browser in DER format; a CI/CD templating step writing an empty secret; concatenating a chain without PEM headers; downloading a CA and getting an HTML error page instead of the cert.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- unable to load decoded CA Cert as PEM
- only one of certData or certFile properties should be specif
- failed to load decoded CA Cert as PEM
- 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/3a59e3bf445e50b9.
Report an issue: GitHub.