temporalio/temporal · error · ErrTLSConfig
unable to read client ca file
Error message
unable to read client ca file
What it means
parseCAs returns this error when CaFile is set but os.ReadFile fails to read the CA PEM file. The original os error is chained in, so the message includes the underlying reason (missing file, permissions, etc.) and everything is wrapped with ErrTLSConfig.
Source
Thrown at common/auth/tls_config_helper.go:146
certProvided := temporalTls.CertData != "" || temporalTls.CertFile != ""
keyProvided := temporalTls.KeyData != "" || temporalTls.KeyFile != ""
if certProvided != keyProvided {
return fmt.Errorf("%w: %s", ErrTLSConfig, "cert or key is missing")
}
if temporalTls.CaData != "" && temporalTls.CaFile != "" {
return fmt.Errorf("%w: %s", ErrTLSConfig, "only one of caData or caFile properties should be specified")
}
return nil
}
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)View on GitHub (pinned to bde624efd1)
Solutions
- Verify the path in CaFile exists and is readable by the service user (ls -l; fix permissions/ownership).
- Correct the path in config — use an absolute path and confirm the mount location inside the container.
- Check the chained os error in the message: ENOENT means wrong path, EACCES means permissions.
- If the CA is intended inline, remove CaFile and set base64-encoded CaData instead.
Example fix
// before tls: caFile: "/etc/temporal/certs/ca.cr t" // after tls: caFile: "/etc/temporal/certs/ca.pem"
Defensive patterns
Strategy: validation
Validate before calling
func checkCaFileReadable(path string) error {
info, err := os.Stat(path)
if err != nil {
return fmt.Errorf("caFile not accessible: %w", err)
}
if info.IsDir() {
return fmt.Errorf("caFile is a directory: %s", path)
}
f, err := os.Open(path)
if err != nil {
return fmt.Errorf("caFile not readable: %w", err)
}
return f.Close()
} Try / catch
tlsCfg, err := auth.NewTLSConfig(cfg)
if err != nil {
if errors.Is(err, auth.ErrTLSConfig) {
logger.Error("TLS configuration invalid", tag.Key, err) // inspect chained os error
return err
}
return err
} Prevention
- Use absolute CA paths and verify they exist inside the actual container image/mounts
- Check file ownership/permissions for the service's non-root user
- Add a readiness check that stats configured cert/CA paths before serving
- Prefer mounting the CA as a Kubernetes secret volume at a documented, fixed path
When it happens
Trigger: NewTLSConfig -> parseCAs with CaFile pointing to a path that does not exist, is a directory, or is unreadable by the process (permission denied).
Common situations: Typo in the CA path; config mounted into the container at a different path than referenced; file permissions after mounting a Kubernetes secret (non-root user cannot read); relative path resolved against a different working directory than expected in the service.
Understand the failure class
Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.
Related errors
- 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
- cert or key is missing
AI-assisted analysis of temporalio/temporal@bde624efd1 (2026-09-01).
Data as JSON: /api/errors/22a73cdeb37130a4.
Report an issue: GitHub.