grpc/grpc-go · error
pemfile: private key file and identity cert file should be b
Error message
pemfile: private key file and identity cert file should be both specified or not specified
What it means
Options.validate() requires CertFile and KeyFile to be specified together or both omitted. Specifying one without the other is invalid because an identity requires both the certificate chain and its private key. validate() checks this with the XOR `keySpecified != certSpecified`.
Source
Thrown at credentials/tls/certprovider/pemfile/watcher.go:85
// SPIFFEBundleMapFile, the SPIFFEBundleMapFile will be preferred.
// Optional.
SPIFFEBundleMapFile string
// RefreshDuration is the amount of time the plugin waits before checking
// for updates in the specified files.
// Optional. If not set, a default value (1 hour) will be used.
RefreshDuration time.Duration
}
func (o Options) canonical() []byte {
return []byte(fmt.Sprintf("%s:%s:%s:%s:%s", o.CertFile, o.KeyFile, o.RootFile, o.SPIFFEBundleMapFile, o.RefreshDuration))
}
func (o Options) validate() error {
if o.CertFile == "" && o.KeyFile == "" && o.RootFile == "" && o.SPIFFEBundleMapFile == "" {
return fmt.Errorf("pemfile: at least one credential file needs to be specified")
}
if keySpecified, certSpecified := o.KeyFile != "", o.CertFile != ""; keySpecified != certSpecified {
return fmt.Errorf("pemfile: private key file and identity cert file should be both specified or not specified")
}
// C-core has a limitation that they cannot verify that a certificate file
// matches a key file. So, the only way to get around this is to make sure
// that both files are in the same directory and that they do an atomic
// read. Even though Java/Go do not have this limitation, we want the
// overall plugin behavior to be consistent across languages.
if certDir, keyDir := filepath.Dir(o.CertFile), filepath.Dir(o.KeyFile); certDir != keyDir {
return errors.New("pemfile: certificate and key file must be in the same directory")
}
return nil
}
// NewProvider returns a new certificate provider plugin that is configured to
// watch the PEM files specified in the passed in options.
func NewProvider(o Options) (certprovider.Provider, error) {
if err := o.validate(); err != nil {
return nil, err
}View on GitHub (pinned to 03255a9237)
Solutions
- Set both certificate_file and private_key_file to valid, matching PEM paths.
- If you do not need a client/server identity (only root CA validation), remove both fields entirely so neither is set.
- Verify cert and key pair with `openssl x509 -noout -modulus | openssl md5` and the matching key modulus.
Example fix
// before
pemfile.Options{CertFile: "/tls/server.crt"} // missing key
// after
pemfile.Options{CertFile: "/tls/server.crt", KeyFile: "/tls/server.key"} Defensive patterns
Strategy: validation
Validate before calling
func validateCertKeyPair(o pemfile.Options) error {
if (o.CertFile == "") != (o.KeyFile == "") {
return errors.New("pemfile: cert and key must be set together")
}
return nil
} Try / catch
if err := opts.validate(); err != nil { return err } Prevention
- Treat certificate_file and private_key_file as a single required-together tuple.
- Cross-check that both files exist and form a matching pair (openssl modulus compare).
- In config templates, gate both variables on the same flag.
When it happens
Trigger: Configuring only certificate_file without private_key_file, or only private_key_file without certificate_file, in either the Options struct passed to NewProvider or the file_watcher JSON.
Common situations: Operator updates the cert path but forgets the key path; templating only substitutes one of the two variables; mTLS config where the user thought the key file was optional.
Related errors
- pemfile: at least one credential file needs to be specified
- pemfile: json.Unmarshal(%s) failed: %v
- pemfile: protojson.Unmarshal(%+v) failed: %v
- pemfile: certificate and key file must be in the same direct
- wrr: errorUtilizationPenalty must be non-negative
AI-assisted analysis of grpc/grpc-go@03255a9237 (2026-08-07).
Data as JSON: /api/errors/21af7dc18c624c78.
Report an issue: GitHub.