hashicorp/terraform · error

client_certificate_pem is set but client_private_key_pem is

Error message

client_certificate_pem is set but client_private_key_pem is not

What it means

In configureTLS, when client_certificate_pem is non-empty (or TF_HTTP_CLIENT_CERTIFICATE_PEM set) but client_private_key_pem is empty. mTLS requires both halves of the key pair, so supplying only the certificate is treated as a configuration error rather than silently sending an unverifiable cert. Fires at Configure time before any TLS handshake.

Source

Thrown at internal/backend/remote-state/http/backend.go:276

		backendbase.GetAttrDefault(configVal, "skip_cert_verification", cty.False),
	)
	clientCACertificatePem := backendbase.GetAttrEnvDefaultFallback(
		configVal, "client_ca_certificate_pem",
		"TF_HTTP_CLIENT_CA_CERTIFICATE_PEM", cty.StringVal(""),
	).AsString()
	clientCertificatePem := backendbase.GetAttrEnvDefaultFallback(
		configVal, "client_certificate_pem",
		"TF_HTTP_CLIENT_CERTIFICATE_PEM", cty.StringVal(""),
	).AsString()
	clientPrivateKeyPem := backendbase.GetAttrEnvDefaultFallback(
		configVal, "client_private_key_pem",
		"TF_HTTP_CLIENT_PRIVATE_KEY_PEM", cty.StringVal(""),
	).AsString()
	if !skipCertVerification && clientCACertificatePem == "" && clientCertificatePem == "" && clientPrivateKeyPem == "" {
		return nil
	}
	if clientCertificatePem != "" && clientPrivateKeyPem == "" {
		return fmt.Errorf("client_certificate_pem is set but client_private_key_pem is not")
	}
	if clientPrivateKeyPem != "" && clientCertificatePem == "" {
		return fmt.Errorf("client_private_key_pem is set but client_certificate_pem is not")
	}

	// TLS configuration is needed; create an object and configure it
	var tlsConfig tls.Config
	client.HTTPClient.Transport.(*http.Transport).TLSClientConfig = &tlsConfig

	if skipCertVerification {
		// ignores TLS verification
		tlsConfig.InsecureSkipVerify = true
	}
	if clientCACertificatePem != "" {
		// trust servers based on a CA
		tlsConfig.RootCAs = x509.NewCertPool()
		if !tlsConfig.RootCAs.AppendCertsFromPEM([]byte(clientCACertificatePem)) {
			return errors.New("failed to append certs")

View on GitHub (pinned to c9def3e214)

Solutions

  1. Provide a matching client_private_key_pem (PEM-encoded) alongside the certificate.
  2. If mTLS is not actually required, remove client_certificate_pem entirely.
  3. Ensure both env vars (TF_HTTP_CLIENT_CERTIFICATE_PEM and TF_HTTP_CLIENT_PRIVATE_KEY_PEM) are set together in CI.

Example fix

// before
backend "http" {
  address               = "https://state.corp"
  client_certificate_pem = file("client.crt")
}
// after
backend "http" {
  address                = "https://state.corp"
  client_certificate_pem = file("client.crt")
  client_private_key_pem = file("client.key")
}
Defensive patterns

Strategy: validation

Validate before calling

func validateMTLSPair(cert, key string) error {
  if cert != "" && key == "" {
    return fmt.Errorf("client_certificate_pem set without client_private_key_pem")
  }
  return nil
}

Prevention

When it happens

Trigger: Setting client_certificate_pem without client_private_key_pem in the backend block, or exporting only TF_HTTP_CLIENT_CERTIFICATE_PEM. Triggered at `terraform init` once a TLS-related option is present.

Common situations: Operator's secret store issues only the cert; copy-paste drops the key block; key is injected by a separate mechanism that forgot the env var.

Understand the failure class

Related errors


AI-assisted analysis of hashicorp/terraform@c9def3e214 (2026-08-07). Data as JSON: /api/errors/f51fd5d35518efe7. Report an issue: GitHub.