{"record":{"id":"f888328e5870db8b","repo":"crowdsecurity/crowdsec","slug":"could-not-read-crl-file-w","errorCode":null,"errorMessage":"could not read CRL file: %w","messagePattern":"could not read CRL file: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/apiserver/middlewares/v1/crl.go","lineNumber":87,"sourceCode":"\tcc.mu.Lock()\n\tdefer cc.mu.Unlock()\n\n\tcc.logger.Debugf(\"loading CRL file from %s\", cc.path)\n\n\tfileInfo, err := os.Stat(cc.path)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"could not access CRL file: %w\", err)\n\t}\n\n\t// noop if the file didn't change\n\tif cc.fileInfo != nil && fileInfo.ModTime().Equal(cc.fileInfo.ModTime()) && fileInfo.Size() == cc.fileInfo.Size() {\n\t\treturn nil\n\t}\n\n\t// the encoding/pem package wants bytes, not io.Reader\n\tcrlContent, err := os.ReadFile(cc.path)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"could not read CRL file: %w\", err)\n\t}\n\n\tcc.crls, err = cc.decodeCRLs(crlContent)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tcc.fileInfo = fileInfo\n\tcc.lastLoad = time.Now()\n\tcc.onLoad()\n\n\treturn nil\n}\n\n// isRevoked checks if the client certificate is revoked by any of the CRL blocks\n// It returns a boolean indicating if the certificate is revoked and a boolean indicating\n// if the CRL check was successful and could be cached.\nfunc (cc *CRLChecker) isRevokedBy(cert *x509.Certificate, issuer *x509.Certificate) (bool, bool) {","sourceCodeStart":69,"sourceCodeEnd":105,"githubUrl":"https://github.com/crowdsecurity/crowdsec/blob/909b5157986a2b2c2163300fdaef5ed01289f7d2/pkg/apiserver/middlewares/v1/crl.go#L69-L105","documentation":"refresh() fails reading the CRL file contents with os.ReadFile after a successful stat, typically a race (file replaced between stat and read) or an I/O/permission problem surfacing at read time. The error is wrapped as 'could not read CRL file'.","triggerScenarios":"NewCRLChecker/isRevokedBy -> refresh: os.Stat succeeded but os.ReadFile(cc.path) returns EACCES, EISDIR, EIO, or 'file changed as we read it' because the CA rotation replaced the path mid-read.","commonSituations":"crl_path points at a directory instead of a file; crowdsec runs as non-root while the file is 0600 root-owned; concurrent atomic rename racing the reader; disk/network filesystem (NFS) I/O errors.","solutions":["Ensure crl_path is a regular file readable by the crowdsec user (chmod/chown)","Point crl_path at the stable symlink/file and have the writer do atomic rename, then retry","Restart crowdsec after fixing permissions — refresh retries on the next isRevokedBy call","Check filesystem health (dmesg, NFS mounts) if the error persists"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// check readability as the crowdsec user\nf, err := os.Open(crlPath)\nif err != nil {\n    return fmt.Errorf(\"cannot read CRL %s: %w\", crlPath, err)\n}\nf.Close()","typeGuard":null,"tryCatchPattern":"err := checker.Refresh()\nif err != nil && strings.Contains(err.Error(), \"could not read CRL file\") {\n    time.Sleep(100 * time.Millisecond)\n    err = checker.Refresh() // retry: often a race with atomic rename\n}","preventionTips":["Run the CRL writer with atomic rename to avoid read-during-replace races","Set permissions (chmod 644 or group-readable) matching the crowdsec service user","Point crl_path at a stable symlink, not a path writers recreate","Watch for NFS/network filesystem I/O errors on the CRL volume"],"tags":["tls","crl","filesystem","io","go"],"backgroundTag":"file-read-failed","analyzedSha":"909b5157986a2b2c2163300fdaef5ed01289f7d2","analyzedAt":"2026-09-06T12:27:26.012Z","contentChangedAt":"2026-09-06T12:27:26.012Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}