cilium/cilium · error

certificate and private key are both required, but only one

Error message

certificate and private key are both required, but only one was provided

What it means

ErrInvalidKeypair in pkg/crypto/certloader/reloader.go is returned when constructing a FileReloader (NewFileReloader, via NewFileReloaderReady) when exactly one of the certificate and private key files is provided but not the other. The certloader requires the pair to be complete; a lone cert or lone key cannot be used to establish TLS.

Source

Thrown at pkg/crypto/certloader/reloader.go:37

type FileReloader struct {
	// caFiles, certFile, and privkeyFile are constants for the FileReloader's
	// lifetime, thus accessing them doesn't require acquiring the mutex.
	caFiles     []string
	certFile    string
	privkeyFile string
	mutex       lock.Mutex
	// fields below should only be accessed with mutex acquired as they may be
	// updated concurrently.
	caCertPool           *x509.CertPool
	caCertPoolGeneration uint // incremented when caCertPool is reloaded
	keypair              *tls.Certificate
	keypairGeneration    uint // incremented when keypair is reloaded
}

var (
	// ErrInvalidKeypair is returned when either the certificate or its
	// corresponding private key is missing.
	ErrInvalidKeypair = errors.New("certificate and private key are both required, but only one was provided")
)

// NewFileReloaderReady create and returns a FileReloader using the given file.
// The files are already loaded when this function returns, thus the returned
// FileReloader is readily usable.
func NewFileReloaderReady(caFiles []string, certFile, privkeyFile string) (*FileReloader, error) {
	r, err := NewFileReloader(caFiles, certFile, privkeyFile)
	if err != nil {
		return nil, err
	}

	// load the files for the first time.
	if _, _, err := r.Reload(); err != nil {
		return nil, err
	}

	return r, nil
}

View on GitHub (pinned to ac7b90affa)

Solutions

  1. Provide both certFile and privkeyFile (e.g. --tls-cert + --tls-key or the corresponding Helm values)
  2. Verify the mounted Kubernetes Secret contains both tls.crt and tls.key entries
  3. Fix the file path typo causing one of the pair to be missing
  4. If you intend no TLS, clear both flags rather than only one

Example fix

// before
reloader, err := certloader.NewFileReloader(nil, "/certs/tls.crt", "")
// after
reloader, err := certloader.NewFileReloader(nil, "/certs/tls.crt", "/certs/tls.key")
Defensive patterns

Strategy: validation

Validate before calling

func validateKeypair(certFile, privkeyFile string) error {
    if (certFile == "") != (privkeyFile == "") {
        return certloader.ErrInvalidKeypair
    }
    if certFile != "" {
        if _, err := os.Stat(certFile); err != nil { return err }
        if _, err := os.Stat(privkeyFile); err != nil { return err }
    }
    return nil
}

Type guard

func keypairComplete(certFile, privkeyFile string) bool { return (certFile == "") == (privkeyFile == "") }

Try / catch

r, err := certloader.NewFileReloader(caFiles, certFile, privkeyFile)
if errors.Is(err, certloader.ErrInvalidKeypair) {
    return fmt.Errorf("TLS keypair incomplete: cert=%q key=%q", certFile, privkeyFile)
}

Prevention

When it happens

Trigger: Calling NewFileReloader/NewFileReloaderReady (used by validateMutualTLSFlags) with certFile set but privkeyFile empty, or privkeyFile set but certFile empty. Both empty may be allowed (non-mTLS); the mismatch is not.

Common situations: Incomplete Helm/CLI flags (e.g. setting --tls-cert but forgetting --tls-key); secret mounted with only one of the two files; typo in one file path causing the loader to see it as missing; partial Secret update in Kubernetes.

Understand the failure class

Related errors


AI-assisted analysis of cilium/cilium@ac7b90affa (2026-08-31). Data as JSON: /api/errors/a473c20b3e5be62c. Report an issue: GitHub.