temporalio/temporal · error · ErrTLSConfig
unable to read client certificate file
Error message
unable to read client certificate file
What it means
parseClientCert returns this error when CertFile is set but os.ReadFile fails to read the client certificate PEM file. The underlying os error is chained into the message and the whole thing is wrapped with ErrTLSConfig, so mTLS setup fails fast at config load.
Source
Thrown at common/auth/tls_config_helper.go:195
}
if block.Type != "CERTIFICATE" || len(block.Headers) != 0 {
continue
}
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)
}View on GitHub (pinned to bde624efd1)
Solutions
- Check the chained os error: fix the path (ENOENT) or permissions (EACCES) accordingly.
- Use an absolute path and confirm the cert is actually mounted/installed at that location in the deployment.
- Correct the CertFile value in config or, if the cert is meant to be inline, remove CertFile and set base64 CertData.
- Ensure rotation processes replace the file atomically so it never disappears mid-read.
Example fix
// before tls: certFile: "/etc/temporal/certs/client.pem" # file not mounted in container // after (configmap/secret mount added, e.g. k8s): // volumeMounts: // - name: temporal-certs // mountPath: /etc/temporal/certs tls: certFile: "/etc/temporal/certs/client.pem"
Defensive patterns
Strategy: validation
Validate before calling
func checkCertFileReadable(path string) error {
info, err := os.Stat(path)
if err != nil {
return fmt.Errorf("certFile not accessible: %w", err)
}
if info.IsDir() {
return fmt.Errorf("certFile is a directory: %s", path)
}
f, err := os.Open(path)
if err != nil {
return fmt.Errorf("certFile not readable: %w", err)
}
return f.Close()
} Try / catch
tlsCfg, err := auth.NewTLSConfig(cfg)
if err != nil {
if errors.Is(err, auth.ErrTLSConfig) {
// message includes chained os error: ENOENT vs EACCES
logger.Error("client cert file could not be read", tag.Key, err)
}
return err
} Prevention
- Mount client certs as secret volumes at fixed absolute paths and document them
- Grant read access to the service's runtime user on cert files
- Make cert rotation atomic (write temp file + rename) so the path is never missing
- Stat the cert path in a pre-start check or readiness probe
When it happens
Trigger: NewTLSConfig -> parseClientCert with CertFile pointing to a missing, deleted, or unreadable file (wrong path, wrong mount, permission denied, file is a directory).
Common situations: Typo in cert path; cert secret not mounted into the pod; permission issues for a non-root service user; relative path whose working directory differs in container vs local dev; file removed after a failed cert rotation.
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.
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- unable to read client ca file
- unable to decode client certificate
- 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/124b3e18fa4abf37.
Report an issue: GitHub.