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 = defaultCertRefreshDuration

View on GitHub (pinned to 03255a9237)

Solutions

  1. 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).
  2. If files must live apart, create a symlink so both paths resolve into a common directory.
  3. 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

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

Related errors


AI-assisted analysis of grpc/grpc-go@03255a9237 (2026-08-07). Data as JSON: /api/errors/d2d9e8391da1b61f. Report an issue: GitHub.