crowdsecurity/crowdsec · error
could not access CRL file: %w
Error message
could not access CRL file: %w
What it means
refresh() cannot stat the configured CRL file (os.Stat error) when loading or re-checking revocation lists for LAPI TLS client auth. The checker needs to know the file's mtime/size to skip redundant reloads, so an unreadable path is a hard error.
Source
Thrown at pkg/apiserver/middlewares/v1/crl.go:76
return crls, nil
}
// refresh() reads the CRL file if new or changed since the last time
func (cc *CRLChecker) refresh() error {
// noop if lastLoad is less than 5 seconds ago
if time.Since(cc.lastLoad) < 5*time.Second {
return nil
}
cc.mu.Lock()
defer cc.mu.Unlock()
cc.logger.Debugf("loading CRL file from %s", cc.path)
fileInfo, err := os.Stat(cc.path)
if err != nil {
return fmt.Errorf("could not access CRL file: %w", err)
}
// noop if the file didn't change
if cc.fileInfo != nil && fileInfo.ModTime().Equal(cc.fileInfo.ModTime()) && fileInfo.Size() == cc.fileInfo.Size() {
return nil
}
// the encoding/pem package wants bytes, not io.Reader
crlContent, err := os.ReadFile(cc.path)
if err != nil {
return fmt.Errorf("could not read CRL file: %w", err)
}
cc.crls, err = cc.decodeCRLs(crlContent)
if err != nil {
return err
}
View on GitHub (pinned to 909b515798)
Solutions
- Verify the file exists and is readable: ls -l <crl_path>; fix the path in the 'api.server.tls.crl_path' config key
- Recreate/download the CRL file from your CA
- Fix permissions/ownership so the crowdsec process user can read it
- If running in a container, mount the CRL (and its directory) into the container and check for stale volume paths
Example fix
// before (config.yaml)
api:
server:
tls:
crl_path: /etc/crowdsec/ssl/crl.pem.bak
// after
api:
server:
tls:
crl_path: /etc/crowdsec/ssl/crl.pem Defensive patterns
Strategy: validation
Validate before calling
// check before configuring/starting
if _, err := os.Stat(crlPath); err != nil {
return fmt.Errorf("CRL path %s is not accessible: %w", crlPath, err)
} Type guard
func crlFileAccessible(path string) bool {
fi, err := os.Stat(path)
return err == nil && !fi.IsDir()
} Try / catch
if err := checker.Refresh(); err != nil {
if strings.Contains(err.Error(), "could not access CRL file") {
log.Errorf("CRL missing at %s, revocation checks will fail: %v", crlPath, err)
}
} Prevention
- Verify crl_path exists and is readable by the crowdsec user before starting LAPI
- In containers, explicitly mount the CRL file or its directory
- Ensure CA rotation never deletes the CRL before crowdsec has re-read it
- Add a health check that stats the CRL path and alerts on disappearance
When it happens
Trigger: NewCRLChecker or isRevokedBy -> refresh runs while cc.path (api.crl_path in LAPI TLS config) does not exist, has wrong permissions, or the mount is unavailable; os.Stat returns *PathError and it is wrapped as 'could not access CRL file'.
Common situations: crl_path typo in config.yaml; CRL file deleted by CA rotation before crowdsec restart; permissions changed so the crowdsec user cannot read the directory; Docker volume not mounted.
Understand the failure class
Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.
Related errors
- could not read CRL file: %w
- cannot use TLS with a unix socket
- user/password authentication and TLS authentication are mutu
- could not parse file: %w
- failed to open feature flags file: %w
AI-assisted analysis of crowdsecurity/crowdsec@909b515798 (2026-09-06).
Data as JSON: /api/errors/b98af71bc07d9263.
Report an issue: GitHub.