temporalio/temporal · error

failed to load CA files

Error message

failed to load CA files: %v

What it means

registerTLSConfig in the MySQL session package reads the configured CA PEM file (cfg.TLS.CaFile) to build the root certificate pool for the connection; if os.ReadFile fails, the error is wrapped as "failed to load CA files". The MySQL client cannot establish verified TLS without this file.

Solutions

  1. Verify the path in TLS.CaFile exists and is readable by the Temporal process user (ls -l / check permissions).
  2. Use an absolute path in containerized deployments and ensure the file is mounted (e.g., via a Kubernetes secret volume).
  3. Fix ownership/permissions (chmod/chown) so the service account can read the PEM file.
  4. If TLS verification is not required for the environment, remove the CaFile setting — but prefer fixing the mount for security.

Example fix

// before (config)
tls:
  caFile: certs/ca.pem
// after
tls:
  caFile: /etc/temporal/certs/ca.pem  # absolute path, verified mounted & readable
Defensive patterns

Strategy: validation

Validate before calling

caPath := cfg.TLS.CaFile
if fi, err := os.Stat(caPath); err != nil || fi.Size() == 0 {
    return fmt.Errorf("CA file %q missing or empty", caPath)
}
if f, err := os.Open(caPath); err != nil {
    return fmt.Errorf("CA file %q not readable: %v", caPath, err)
} else { f.Close() }

Try / catch

if err := connectDB(cfg); err != nil {
    if strings.Contains(err.Error(), "failed to load CA files") {
        // fix caFile path/mount/permissions before reconnect
    }
    return err
}

Prevention

When it happens

Trigger: createConnection -> registerTLSConfig with a non-empty TLS.CaFile that points to a nonexistent, unreadable, or otherwise un-openable file path.

Common situations: Typo in the CA file path in config; file not mounted into a container (missing volume mount); permission denied for the service user; relative path used with a different working directory in a container.

Related errors


AI-assisted analysis of temporalio/temporal@bde624efd1 (2026-09-01). Data as JSON: /api/errors/edbc018c97d2d444. Report an issue: GitHub.

Appendix: source

Thrown at common/persistence/sql/sqlplugin/mysql/session/session.go:216

		return key, value
	default:
		return inkey, invalue
	}
}

func registerTLSConfig(cfg *config.SQL) error {
	if cfg.TLS == nil || !cfg.TLS.Enabled {
		return nil
	}

	// TODO: create a way to set MinVersion and CipherSuites via cfg.
	tlsConfig := auth.NewTLSConfigForServer(cfg.TLS.ServerName, cfg.TLS.EnableHostVerification)

	if cfg.TLS.CaFile != "" {
		rootCertPool := x509.NewCertPool()
		pem, err := os.ReadFile(cfg.TLS.CaFile)
		if err != nil {
			return fmt.Errorf("failed to load CA files: %v", err)
		}
		if ok := rootCertPool.AppendCertsFromPEM(pem); !ok {
			return fmt.Errorf("failed to append CA file")
		}
		tlsConfig.RootCAs = rootCertPool
	}

	if cfg.TLS.CertFile != "" && cfg.TLS.KeyFile != "" {
		clientCert := make([]tls.Certificate, 0, 1)
		certs, err := tls.LoadX509KeyPair(
			cfg.TLS.CertFile,
			cfg.TLS.KeyFile,
		)
		if err != nil {
			return fmt.Errorf("failed to load tls x509 key pair: %v", err)
		}
		clientCert = append(clientCert, certs)
		tlsConfig.Certificates = clientCert

View on GitHub (pinned to bde624efd1)