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, client_certificate_pem (or TF_HTTP_CLIENT_CERTIFICATE_PEM) is non-empty while client_private_key_pem (or TF_HTTP_CLIENT_PRIVATE_KEY_PEM) is empty. mTLS requires BOTH a certificate and its matching private key; providing only the certificate is an incomplete, unusable configuration.

Solutions

  1. Set client_private_key_pem to the PEM private key matching the certificate.
  2. If you do not need mTLS, remove client_certificate_pem entirely.
  3. Verify both TF_HTTP_CLIENT_CERTIFICATE_PEM and TF_HTTP_CLIENT_PRIVATE_KEY_PEM are exported together.

Example fix

// before
client_certificate_pem = file("client.crt")
// client_private_key_pem missing
// after
client_certificate_pem = file("client.crt")
client_private_key_pem = file("client.key")
Defensive patterns

Strategy: validation

Validate before calling

# Pre-flight: cert and key must both be set (or both unset)
cert="${TF_HTTP_CLIENT_CERTIFICATE_PEM:-}"
key="${TF_HTTP_CLIENT_PRIVATE_KEY_PEM:-}"
if [ -n "$cert" ] && [ -z "$key" ]; then
  echo "ERROR: client_certificate_pem set but client_private_key_pem is empty"; exit 1
fi

Prevention

When it happens

Trigger: Certificate supplied via the backend block or env var, but the matching private key was omitted or its env var was not exported.

Common situations: Key lives in a different secret not wired into the pipeline; the key line was dropped during copy-paste; cert and key stored in separate stores and only one was referenced.

Understand the failure class

Related errors


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

Appendix: 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 d32a084675)