thanos-io/thanos · error

both client key and certificate must be provided

Error message

both client key and certificate must be provided

What it means

NewClientConfig returns this error when exactly one of cert and key is non-empty. Mutual TLS requires the client certificate and its private key to be supplied together; a lone half is a configuration error.

Solutions

  1. Provide both cert and key paths together, or neither.
  2. Check the corresponding flags/env vars are both set (e.g. TLS_CERT and TLS_KEY).
  3. Mount both files from the secret and pass both paths.
  4. If mTLS is not required, omit both to use an unauthenticated client.

Example fix

// before
cfg, err := tls.NewClientConfig(logger, certPath, "", caPath, serverName, false, "1.2")
// after
if (certPath == "") != (keyPath == "") {
    return errors.New("client cert and key must both be set")
}
cfg, err := tls.NewClientConfig(logger, certPath, keyPath, caPath, serverName, false, "1.2")
Defensive patterns

Strategy: validation

Validate before calling

func havePair(cert, key string) error {
    if (cert == "") != (key == "") {
        return errors.New("client cert and key must be provided together")
    }
    return nil
}

Try / catch

if err := havePair(certPath, keyPath); err != nil {
    return err
}
cfg, err := tls.NewClientConfig(logger, certPath, keyPath, caCert, serverName, skipVerify, ver)

Prevention

When it happens

Trigger: StoreClientTLSCredentials calls NewClientConfig with cert set but key empty (or vice versa) when configuring mTLS.

Common situations: Only the cert file was mounted/secret-shared, the key was omitted for security; one of the two env vars/flags misnamed; TLS config template filled partially; copy-paste dropped the key path line.

Understand the failure class

Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.

Related errors


AI-assisted analysis of thanos-io/thanos@35b8b99117 (2026-09-07). Data as JSON: /api/errors/3db25792a347176a. Report an issue: GitHub.

Appendix: source

Thrown at pkg/tls/options.go:181

			return nil, err
		}
		level.Debug(logger).Log("msg", fmt.Sprintf("setting minimum TLS version to %s", minTLSVersion))
	}
	tlsCfg := &tls.Config{
		RootCAs:    certPool,
		MinVersion: mtlsVersion,
	}

	if serverName != "" {
		tlsCfg.ServerName = serverName
	}

	if skipVerify {
		tlsCfg.InsecureSkipVerify = true
	}

	if (key != "") != (cert != "") {
		return nil, errors.New("both client key and certificate must be provided")
	}

	if cert != "" {
		mngr := &clientTLSManager{
			certPath: cert,
			keyPath:  key,
		}
		tlsCfg.GetClientCertificate = mngr.getClientCertificate

		level.Debug(logger).Log("msg", "TLS client authentication enabled")
	}
	return tlsCfg, nil
}

type clientTLSManager struct {
	certPath string
	keyPath  string

View on GitHub (pinned to 35b8b99117)