hashicorp/terraform · error

cannot load client certificate

Error message

cannot load client certificate: %w

What it means

tls.X509KeyPair(clientCertificatePem, clientPrivateKeyPem) failed. Both PEM strings were supplied but the pair could not be assembled into a tls.Certificate. The '%w' wraps the underlying crypto/tls error. Causes: invalid PEM encoding, wrong PEM block type (e.g. 'CERTIFICATE REQUEST' instead of 'CERTIFICATE'), a key that does not match the certificate, corrupted/truncated PEM, or an unsupported key type.

Solutions

  1. Regenerate the cert/key pair and verify both load: openssl x509 -in client.crt -noout -text and openssl rsa -in client.key -check.
  2. Confirm the modulus matches: openssl x509 -in client.crt -modulus -noout | openssl md5 vs openssl rsa -in client.key -modulus | openssl md5.
  3. Ensure each value is full PEM including '-----BEGIN CERTIFICATE-----'/'-----BEGIN PRIVATE KEY-----' headers and footers.
  4. If reading via a secret manager, confirm the value is decoded (not base64) and uses LF line endings.
  5. Use file() to load from disk rather than inlining PEM in HCL to avoid escaping issues.

Example fix

// before (inline, often mangled)
client_certificate_pem = "-----BEGIN CERTIFICATE-----\nMIIB...==\n-----END CERTIFICATE-----"
client_private_key_pem  = "-----BEGIN RSA PRIVATE KEY-----\nMIIE...==\n-----END RSA PRIVATE KEY-----"
// after (load full files from disk)
client_certificate_pem = file("${path.module}/client.crt")
client_private_key_pem = file("${path.module}/client.key")
Defensive patterns

Strategy: validation

Validate before calling

# Pre-flight: verify the cert/key pair loads and the moduli match
openssl x509 -in client.crt -noout >/dev/null 2>&1 || { echo 'invalid cert PEM'; exit 1; }
openssl rsa -in client.key -check -noout >/dev/null 2>&1 || openssl pkey -in client.key -check -noout >/dev/null 2>&1 || { echo 'invalid key PEM'; exit 1; }
cmp -s <(openssl x509 -in client.crt -modulus -noout | openssl md5) <(openssl rsa -in client.key -modulus 2>/dev/null | openssl md5) \
  || cmp -s <(openssl x509 -in client.crt -pubkey -noout | openssl md5) <(openssl pkey -in client.key -pubout 2>/dev/null | openssl md5) \
  || { echo 'cert and key do not match'; exit 1; }

Prevention

When it happens

Trigger: Cert and key are both present but are not a matching pair; one is not valid PEM; the 'private key' file actually contains a public key or CSR; PEM bytes were mangled (line-wrapping, charset, escaping) by a secret store or shell.

Common situations: Pasted cert/key truncated; copied the public cert into the key field; cert and key generated for different subjects; CRLF vs LF line endings introduced by Windows; secret manager base64-encoded the value and it was not decoded.

Understand the failure class

Related errors


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

Appendix: source

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

	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")
		}
	}
	if clientCertificatePem != "" && clientPrivateKeyPem != "" {
		// attach a client certificate to the TLS handshake (aka mTLS)
		certificate, err := tls.X509KeyPair([]byte(clientCertificatePem), []byte(clientPrivateKeyPem))
		if err != nil {
			return fmt.Errorf("cannot load client certificate: %w", err)
		}
		tlsConfig.Certificates = []tls.Certificate{certificate}
	}

	return nil
}

func (b *Backend) StateMgr(name string) (statemgr.Full, tfdiags.Diagnostics) {
	var diags tfdiags.Diagnostics

	if name != backend.DefaultStateName {
		return nil, diags.Append(backend.ErrWorkspacesNotSupported)
	}

	sm := &remote.State{Client: b.client}

	if err := sm.RefreshState(); err != nil {
		return nil, diags.Append(err)

View on GitHub (pinned to d32a084675)