temporalio/temporal · error
failed to load client ca: %v
Error message
failed to load client ca: %v
What it means
newClientTLSConfig fetches the optional server root CA used by the client (clientProvider.FetchServerRootCAsForClient(isWorker)) for connections to servers whose certs are not in the system trust store. A failure is wrapped as "failed to load client ca: %v". Raised for internode and frontend client TLS config construction.
Source
Thrown at common/rpc/encryption/local_store_tls_provider.go:376
}
return auth.NewTLSConfigWithCertsAndCAs(
clientAuthType,
[]tls.Certificate{*serverCert},
clientCaPool,
logger), nil
}
func newClientTLSConfig(
clientProvider CertProvider,
serverName string,
isAuthRequired bool,
isWorker bool,
enableHostVerification bool,
) (*tls.Config, error) {
// Optional ServerCA for client if not already trusted by host
serverCa, err := clientProvider.FetchServerRootCAsForClient(isWorker)
if err != nil {
return nil, fmt.Errorf("failed to load client ca: %v", err)
}
var getCert tlsCertFetcher
// mTLS enabled, present certificate
if isAuthRequired {
getCert = func() (*tls.Certificate, error) {
cert, err := clientProvider.FetchClientCertificate(isWorker)
if err != nil {
return nil, err
}
if cert == nil {
return nil, fmt.Errorf("client auth required, but no certificate provided")
}
return cert, nil
}
}View on GitHub (pinned to bde624efd1)
Solutions
- Fix the wrapped root cause: ensure the client's server CA file exists, is readable, and is valid PEM.
- If the server cert is already trusted by the host's system roots, remove the redundant server CA config.
- Re-copy the CA bundle after rotation; verify with openssl x509 -in ca.pem -noout -text.
- Check worker-vs-non-worker config split: internode and frontend worker settings each have their own CA paths.
Example fix
// before
clientTLS:
internode:
serverName: tls-server
rootCADataFile: /etc/certs/ca.pem # not mounted in worker pod
// after
clientTLS:
internode:
serverName: tls-server
rootCADataFile: /etc/temporal/tls/internode-ca.pem # mounted secret Defensive patterns
Strategy: validation
Validate before calling
if rootCAPath != "" {
b, err := os.ReadFile(rootCAPath)
if err != nil { return fmt.Errorf("client root CA unreadable: %w", err) }
if _, err := x509.SystemCertPool(); err == nil {
pool := x509.NewCertPool()
if !pool.AppendCertsFromPEM(b) { return errors.New("root CA PEM not parseable") }
}
} Try / catch
tlsCfg, err := newClientTLSConfig(...)
if err != nil && strings.Contains(err.Error(), "failed to load client ca") {
return fmt.Errorf("cannot connect over TLS: %w", err)
} Prevention
- Mount the server CA bundle in every client/worker pod that needs it.
- Re-copy the CA after server cert rotation.
- Drop explicit CA config when the server uses publicly trusted certs.
When it happens
Trigger: Building a client TLS config (GetInternodeClientConfig / GetFrontendClientConfig / anonymous worker config) where the configured server CA file/data for clients is missing, unreadable, or unparseable.
Common situations: Worker or CLI configured with server CA path that does not exist in its container; self-signed server certs with a stale/mistyped CA path; permission issues after secret mount; typo between serverName and CA file layout.
Related errors
- failed to fetch client CAs: %v
- URI is invalid
- URI scheme does not match the archiver
- history was mutated
- get archived history request is invalid
AI-assisted analysis of temporalio/temporal@bde624efd1 (2026-09-01).
Data as JSON: /api/errors/37e41eb55bbf2ca0.
Report an issue: GitHub.