{"record":{"id":"d2d9e8391da1b61f","repo":"grpc/grpc-go","slug":"pemfile-certificate-and-key-file-must-be-in-the-s","errorCode":null,"errorMessage":"pemfile: certificate and key file must be in the same directory","messagePattern":"pemfile: certificate and key file must be in the same directory","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"credentials/tls/certprovider/pemfile/watcher.go","lineNumber":93,"sourceCode":"\nfunc (o Options) canonical() []byte {\n\treturn []byte(fmt.Sprintf(\"%s:%s:%s:%s:%s\", o.CertFile, o.KeyFile, o.RootFile, o.SPIFFEBundleMapFile, o.RefreshDuration))\n}\n\nfunc (o Options) validate() error {\n\tif o.CertFile == \"\" && o.KeyFile == \"\" && o.RootFile == \"\" && o.SPIFFEBundleMapFile == \"\" {\n\t\treturn fmt.Errorf(\"pemfile: at least one credential file needs to be specified\")\n\t}\n\tif keySpecified, certSpecified := o.KeyFile != \"\", o.CertFile != \"\"; keySpecified != certSpecified {\n\t\treturn fmt.Errorf(\"pemfile: private key file and identity cert file should be both specified or not specified\")\n\t}\n\t// C-core has a limitation that they cannot verify that a certificate file\n\t// matches a key file. So, the only way to get around this is to make sure\n\t// that both files are in the same directory and that they do an atomic\n\t// read. Even though Java/Go do not have this limitation, we want the\n\t// overall plugin behavior to be consistent across languages.\n\tif certDir, keyDir := filepath.Dir(o.CertFile), filepath.Dir(o.KeyFile); certDir != keyDir {\n\t\treturn errors.New(\"pemfile: certificate and key file must be in the same directory\")\n\t}\n\treturn nil\n}\n\n// NewProvider returns a new certificate provider plugin that is configured to\n// watch the PEM files specified in the passed in options.\nfunc NewProvider(o Options) (certprovider.Provider, error) {\n\tif err := o.validate(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn newProvider(o), nil\n}\n\n// newProvider is used to create a new certificate provider plugin after\n// validating the options, and hence does not return an error.\nfunc newProvider(o Options) certprovider.Provider {\n\tif o.RefreshDuration == 0 {\n\t\to.RefreshDuration = defaultCertRefreshDuration","sourceCodeStart":75,"sourceCodeEnd":111,"githubUrl":"https://github.com/grpc/grpc-go/blob/03255a9237b6eb32710f6bc4f2de9a675b99fe36/credentials/tls/certprovider/pemfile/watcher.go#L75-L111","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\np, err := pemfile.NewProvider(pemfile.Options{\n    CertFile: \"/etc/ssl/certs/server.pem\",\n    KeyFile:  \"/etc/ssl/private/server.key\",\n}) // err: must be in the same directory\n\n// after\np, err := pemfile.NewProvider(pemfile.Options{\n    CertFile: \"/etc/grpc/identity.pem\",\n    KeyFile:  \"/etc/grpc/identity.key\",\n})","handlingStrategy":"validation","validationCode":"// Ensure cert and key share a directory before building the provider.\nfunc validatePemfile(o pemfile.Options) error {\n    if (o.CertFile == \"\") != (o.KeyFile == \"\") {\n        return errors.New(\"cert and key must both be set or both unset\")\n    }\n    if o.CertFile != \"\" && filepath.Dir(o.CertFile) != filepath.Dir(o.KeyFile) {\n        return fmt.Errorf(\"cert %q and key %q must be in the same directory\", o.CertFile, o.KeyFile)\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":"p, err := pemfile.NewProvider(opts)\nif err != nil { log.Fatalf(\"pemfile provider: %v\", err) }","preventionTips":["Adopt a convention of keeping cert+key in one directory per identity.","Generate both paths from a single base dir variable."],"tags":["go","grpc","security","tls","pemfile","certprovider","config-validation"],"analyzedSha":"03255a9237b6eb32710f6bc4f2de9a675b99fe36","analyzedAt":"2026-08-07T00:29:34.215Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}