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

  1. Verify the file exists and is readable: ls -l <crl_path>; fix the path in the 'api.server.tls.crl_path' config key
  2. Recreate/download the CRL file from your CA
  3. Fix permissions/ownership so the crowdsec process user can read it
  4. 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

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


AI-assisted analysis of crowdsecurity/crowdsec@909b515798 (2026-09-06). Data as JSON: /api/errors/b98af71bc07d9263. Report an issue: GitHub.