cloudflare/cloudflared · error

read CA certificate %s: %w

Error message

read CA certificate %s: %w

What it means

CreateTunnelConfig in tlsconfig builds the *tls.Config used to verify the edge/origin server. When a CA certificate path is supplied (caCert != ""), it reads that file from disk with os.ReadFile; if the read fails (missing file, bad permissions, is a directory), it wraps the OS error as "read CA certificate <path>: <underlying os error>" and aborts config creation. The wrapped error (from errors.Unwrap / %w) always carries the exact OS reason.

Source

Thrown at tlsconfig/origin_ca.go:81

	// nolint: gosec
	customOriginCA, err := os.ReadFile(originCAFilename)
	if err != nil {
		return nil, errors.Wrap(err, fmt.Sprintf("unable to read the file %s", originCAFilename))
	}

	if !certPool.AppendCertsFromPEM(customOriginCA) {
		return nil, fmt.Errorf("error appending custom CA to cert pool")
	}
	return certPool, nil
}

func CreateTunnelConfig(caCert string, serverName string) (*tls.Config, error) {
	tlsConfig := &tls.Config{ServerName: serverName}
	if caCert != "" {
		caCertPEM, err := os.ReadFile(caCert) //nolint:gosec
		if err != nil {
			return nil, fmt.Errorf("read CA certificate %s: %w", caCert, err)
		}

		rootCAPool := x509.NewCertPool()
		if !rootCAPool.AppendCertsFromPEM(caCertPEM) {
			return nil, fmt.Errorf("parse CA certificate %s", caCert)
		}
		tlsConfig.RootCAs = rootCAPool
	}

	if tlsConfig.RootCAs == nil {
		rootCAPool, err := x509.SystemCertPool()
		if err != nil {
			return nil, errors.Wrap(err, "unable to get x509 system cert pool")
		}
		cfRootCA, err := GetCloudflareRootCA()
		if err != nil {
			return nil, errors.Wrap(err, "could not append Cloudflare Root CAs to cloudflared certificate pool")
		}

View on GitHub (pinned to 2253eeeb25)

Solutions

  1. Verify the file exists and is readable: run `ls -l <path>` and `cat <path> > /dev/null` as the same user that runs cloudflared.
  2. Use an absolute path in the configuration/flag instead of a path relative to the current working directory of the daemon.
  3. Fix filesystem permissions (chown/chmod) or mount the CA file into the container/pod if running containerized.
  4. If the file is genuinely absent, obtain the correct CA bundle (e.g. Cloudflare origin CA or system bundle) and place it at the configured path, or pass an empty caCert to fall back to the system pool plus Cloudflare roots.

Example fix

// before
tlsCfg, err := tlsconfig.CreateTunnelConfig("certs/origin-ca.pem", "example.com")
// after — check the path up front
const caPath = "/etc/cloudflared/origin-ca.pem"
if _, err := os.Stat(caPath); err != nil {
	log.Fatal().Err(err).Msgf("CA certificate not readable at %s", caPath)
}
tlsCfg, err := tlsconfig.CreateTunnelConfig(caPath, "example.com")
Defensive patterns

Strategy: validation

Validate before calling

func caCertReadable(path string) error {
	if path == "" {
		return nil
	}
	fi, err := os.Stat(path)
	if err != nil {
		return fmt.Errorf("CA cert %s: %w", path, err)
	}
	if fi.IsDir() {
		return fmt.Errorf("CA cert %s is a directory", path)
	}
	f, err := os.Open(path)
	if err != nil {
		return fmt.Errorf("CA cert %s not readable: %w", path, err)
	}
	return f.Close()
}

Type guard

func caCertPathSet(cfg struct{ CACert string }) bool { return cfg.CACert != "" }

Try / catch

tlsCfg, err := tlsconfig.CreateTunnelConfig(caPath, serverName)
if err != nil {
	var perr *fs.PathError
	if errors.As(err, &perr) {
		log.Fatal().Err(perr).Msgf("cannot read CA cert %s", perr.Path)
	}
	log.Fatal().Err(err).Msg("failed to build tunnel TLS config")
}

Prevention

When it happens

Trigger: Calling CreateTunnelConfig (directly or via prepareTunnelConfig/probeTLSConfig) with a non-empty caCert path that cannot be read: the file does not exist, the path points to a directory, the process lacks read permission, or the path is misspelled/relative to the wrong working directory.

Common situations: Users pass --origin-ca-pool or equivalent with a typo'd or relative path; the cert file was deleted/moved after config was written; running cloudflared in a container where the CA file was not volume-mounted; running as a non-root service account that cannot read the file; SELinux/AppArmor denies read access.

Understand the failure class

Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.

Related errors


AI-assisted analysis of cloudflare/cloudflared@2253eeeb25 (2026-09-06). Data as JSON: /api/errors/d19ffc9848e361ed. Report an issue: GitHub.