grpc/grpc-go · error
pemfile: certificate and key file must be in the same direct
Error message
pemfile: certificate and key file must be in the same directory
What it means
Returned by pemfile Options.validate (credentials/tls/certprovider/pemfile/watcher.go:93) when filepath.Dir(o.CertFile) != filepath.Dir(o.KeyFile). The pemfile provider watches identity cert + private key files; the comment at lines 87-91 explains the constraint is deliberate cross-language consistency (the C-core cannot pair an arbitrary cert with a key, so all implementations require both files in one directory to allow an atomic read). This is enforced in NewProvider before any file is read.
Source
Thrown at credentials/tls/certprovider/pemfile/watcher.go:93
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
}
return newProvider(o), nil
}
// newProvider is used to create a new certificate provider plugin after
// validating the options, and hence does not return an error.
func newProvider(o Options) certprovider.Provider {
if o.RefreshDuration == 0 {
o.RefreshDuration = defaultCertRefreshDurationView on GitHub (pinned to 03255a9237)
Solutions
- Place the cert and key files in the same directory and pass paths that share filepath.Dir (e.g. /etc/grpc/cert.pem and /etc/grpc/key.pem).
- If files must live apart, create a symlink so both paths resolve into a common directory.
- Double-check the rendered paths at startup by printing filepath.Dir of each before calling NewProvider.
Example fix
// before
p, err := pemfile.NewProvider(pemfile.Options{
CertFile: "/etc/ssl/certs/server.pem",
KeyFile: "/etc/ssl/private/server.key",
}) // err: must be in the same directory
// after
p, err := pemfile.NewProvider(pemfile.Options{
CertFile: "/etc/grpc/identity.pem",
KeyFile: "/etc/grpc/identity.key",
}) Defensive patterns
Strategy: validation
Validate before calling
// Ensure cert and key share a directory before building the provider.
func validatePemfile(o pemfile.Options) error {
if (o.CertFile == "") != (o.KeyFile == "") {
return errors.New("cert and key must both be set or both unset")
}
if o.CertFile != "" && filepath.Dir(o.CertFile) != filepath.Dir(o.KeyFile) {
return fmt.Errorf("cert %q and key %q must be in the same directory", o.CertFile, o.KeyFile)
}
return nil
} Try / catch
p, err := pemfile.NewProvider(opts)
if err != nil { log.Fatalf("pemfile provider: %v", err) } Prevention
- Adopt a convention of keeping cert+key in one directory per identity.
- Generate both paths from a single base dir variable.
When it happens
Trigger: Calling pemfile.NewProvider(Options{CertFile: "/a/cert.pem", KeyFile: "/b/key.pem"}) where the two parent directories differ. Setting only one of CertFile/KeyFile does NOT hit this (a separate earlier check requires both-or-neither); this fires only when both are set but live in different directories.
Common situations: Storing certs and keys in separate directories by convention (e.g. /etc/ssl/certs vs /etc/ssl/private); symlinking one file into another dir so filepath.Dir differs; templating that interpolates different base dirs for cert and key.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- provider instance is closed
- empty token_exchange_service_uri in options
- required field SubjectTokenPath is not specified
- required field SubjectTokenType is not specified
- missing fallback credentials
AI-assisted analysis of grpc/grpc-go@03255a9237 (2026-08-07).
Data as JSON: /api/errors/d2d9e8391da1b61f.
Report an issue: GitHub.