temporalio/temporal · error · ErrTLSConfig

cert or key is missing

Error message

cert or key is missing

What it means

This error is returned by validateTemporalTls when a certificate is provided without a matching private key, or vice versa: certProvided != keyProvided. A usable mTLS identity requires both, so the library rejects the partial config and wraps it with ErrTLSConfig.

Source

Thrown at common/auth/tls_config_helper.go:131

		tlsConfig.Certificates = []tls.Certificate{*clientCert}
	}

	return tlsConfig, nil
}

func validateTemporalTls(temporalTls *TLS) error {
	if temporalTls.CertData != "" && temporalTls.CertFile != "" {
		return fmt.Errorf("%w: %s", ErrTLSConfig, "only one of certData or certFile properties should be specified")
	}

	if temporalTls.KeyData != "" && temporalTls.KeyFile != "" {
		return fmt.Errorf("%w: %s", ErrTLSConfig, "only one of keyData or keyFile properties should be specified")
	}

	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)

View on GitHub (pinned to bde624efd1)

Solutions

  1. Add the missing counterpart: set both cert (CertData or CertFile) and key (KeyData or KeyFile).
  2. If mTLS identity is not needed, remove the cert/key fields entirely rather than leaving only one.
  3. Verify secret/file mounts so both halves of the pair are delivered to the process.

Example fix

// before
tls:
  certFile: "/etc/temporal/certs/client.pem"
// after
tls:
  certFile: "/etc/temporal/certs/client.pem"
  keyFile: "/etc/temporal/certs/client.key"
Defensive patterns

Strategy: validation

Validate before calling

func validateTLSPair(t *auth.TLS) error {
	cert := t.CertData != "" || t.CertFile != ""
	key := t.KeyData != "" || t.KeyFile != ""
	if cert != key {
		return fmt.Errorf("TLS cert and key must both be provided together")
	}
	return nil
}

Type guard

func hasCompleteIdentity(t *auth.TLS) bool {
	cert := t.CertData != "" || t.CertFile != ""
	key := t.KeyData != "" || t.KeyFile != ""
	return cert == key
}

Prevention

When it happens

Trigger: Calling NewTLSConfig where one side is set — e.g. CertFile set but neither KeyData nor KeyFile, or KeyData set with no CertData/CertFile.

Common situations: Config templating where the key is injected separately from the cert (and one injection fails); someone commenting out the key field; rotating certs and replacing only one half of the pair; mounting only the cert secret in Kubernetes.

Related errors


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