slackhq/nebula · error

unable to read pki.ca file %s: %s

Error message

unable to read pki.ca file %s: %s

What it means

loadCAPoolFromConfig reads pki.ca either as inline PEM or from a file via os.Open; when the file cannot be opened it wraps the OS error with this message. The trusted-CA pool is mandatory, so startup/reload aborts without it.

Source

Thrown at pki.go:554

	return c, b, nil
}

func loadCAPoolFromConfig(l *slog.Logger, c *config.C) (*cert.CAPool, error) {
	caPathOrPEM := c.GetString("pki.ca", "")
	if caPathOrPEM == "" {
		return nil, errors.New("no pki.ca path or PEM data provided")
	}

	var caReader io.ReadCloser
	var err error

	if strings.Contains(caPathOrPEM, "-----BEGIN") {
		caReader = io.NopCloser(strings.NewReader(caPathOrPEM))
	} else {
		caReader, err = os.Open(caPathOrPEM)
		if err != nil {
			return nil, fmt.Errorf("unable to read pki.ca file %s: %s", caPathOrPEM, err)
		}
	}
	defer caReader.Close()

	caPool, err := cert.NewCAPoolFromPEMReader(caReader)
	if errors.Is(err, cert.ErrExpired) {
		var expired int
		for _, crt := range caPool.CAs {
			if crt.Certificate.Expired(time.Now()) {
				expired++
				l.Warn("expired certificate present in CA pool", "cert", crt)
			}
		}

		if expired >= len(caPool.CAs) {
			return nil, errors.New("no valid CA certificates present")
		}

View on GitHub (pinned to dd8f660c0a)

Solutions

  1. Fix the pki.ca path in config to the actual ca.crt location
  2. Correct file permissions so the nebula process user can read it (chmod/chown)
  3. Or inline the CA PEM directly as the pki.ca value instead of a path
  4. Verify with 'sudo -u <nebula-user> cat /path/to/ca.crt'

Example fix

// before
pki:
  ca: /etc/nebula/car.crt    # typo
// after
pki:
  ca: /etc/nebula/ca.crt
Defensive patterns

Strategy: validation

Validate before calling

if fi, err := os.Stat(cfg.PKI.CA); err != nil || fi.IsDir() {
    return fmt.Errorf("pki.ca not readable: %v", err)
}

Type guard

func caFileReadable(path string) bool {
    f, err := os.Open(path)
    if err != nil { return false }
    f.Close()
    return true
}

Try / catch

pool, err := loadCAPoolFromConfig(logger, cfg)
if err != nil && strings.HasPrefix(err.Error(), "unable to read pki.ca") {
    return fmt.Errorf("check pki.ca path/permissions: %w", err)
}

Prevention

When it happens

Trigger: reloadCAPool (via newCertStateFromConfig or config reload) with pki.ca set to a path that does not exist, has wrong permissions, or points at a directory instead of a file.

Common situations: Typo in the pki.ca path; file deployed with root-only permissions and nebula runs unprivileged; path relative to the wrong working directory when running as a service; file deleted by config management.

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 slackhq/nebula@dd8f660c0a (2026-09-03). Data as JSON: /api/errors/3747dc84e170ef88. Report an issue: GitHub.