nsqio/nsq · error

failed to read TLS root CA file %s - %s

Error message

failed to read TLS root CA file %s - %s

What it means

nsqadmin's New reads --http-client-tls-root-ca-file with os.ReadFile when the flag is non-empty, to build the RootCAs pool used for outbound HTTPS to nsqd/nsqlookupd. If the read fails, startup aborts with 'failed to read TLS root CA file %s - %s' including the underlying os error, which tells you whether it was missing (no such file), unreadable (permission denied), or e.g. a directory. Note this is a plain file read — a parse failure later produces the separate 'failed to AppendCertsFromPEM' error.

Source

Thrown at nsqadmin/nsqadmin.go:75

		return nil, errors.New("--http-client-tls-cert must be specified with --http-client-tls-key")
	}

	n.httpClientTLSConfig = &tls.Config{
		InsecureSkipVerify: opts.HTTPClientTLSInsecureSkipVerify,
	}
	if opts.HTTPClientTLSCert != "" && opts.HTTPClientTLSKey != "" {
		cert, err := tls.LoadX509KeyPair(opts.HTTPClientTLSCert, opts.HTTPClientTLSKey)
		if err != nil {
			return nil, fmt.Errorf("failed to LoadX509KeyPair %s, %s - %s",
				opts.HTTPClientTLSCert, opts.HTTPClientTLSKey, err)
		}
		n.httpClientTLSConfig.Certificates = []tls.Certificate{cert}
	}
	if opts.HTTPClientTLSRootCAFile != "" {
		tlsCertPool := x509.NewCertPool()
		caCertFile, err := os.ReadFile(opts.HTTPClientTLSRootCAFile)
		if err != nil {
			return nil, fmt.Errorf("failed to read TLS root CA file %s - %s",
				opts.HTTPClientTLSRootCAFile, err)
		}
		if !tlsCertPool.AppendCertsFromPEM(caCertFile) {
			return nil, fmt.Errorf("failed to AppendCertsFromPEM %s", opts.HTTPClientTLSRootCAFile)
		}
		n.httpClientTLSConfig.RootCAs = tlsCertPool
	}

	for _, address := range opts.NSQLookupdHTTPAddresses {
		_, err := net.ResolveTCPAddr("tcp", address)
		if err != nil {
			return nil, fmt.Errorf("failed to resolve --lookupd-http-address (%s) - %s", address, err)
		}
	}

	for _, address := range opts.NSQDHTTPAddresses {
		_, err := net.ResolveTCPAddr("tcp", address)
		if err != nil {

View on GitHub (pinned to 85cf10c09c)

Solutions

  1. Check existence and readability as the nsqadmin user: 'sudo -u nsqadmin test -r /path/ca.pem && echo ok'.
  2. Fix the path/permissions (or mount the secret) and confirm with 'ls -l /path/ca.pem'.
  3. If the target uses a private CA, ensure that CA's PEM was actually copied to this host — node certs are not enough.
  4. Restart nsqadmin; read success moves you past this error.

Example fix

# before
nsqadmin --http-client-tls-root-ca-file=/etc/nsq/tls/root-ca.pem
# failed to read TLS root CA file /etc/nsq/tls/root-ca.pem - open ...: no such file or directory

# after
sudo install -m 0644 ca/root-ca.pem /etc/nsq/tls/root-ca.pem
sudo -u nsqadmin test -r /etc/nsq/tls/root-ca.pem && echo readable
nsqadmin --http-client-tls-root-ca-file=/etc/nsq/tls/root-ca.pem
Defensive patterns

Strategy: validation

Validate before calling

// pre-start: readability check as the service user
fi, err := os.Stat(path)
if err != nil || fi.IsDir() || fi.Mode().Perm()&0o400 == 0 {
    return fmt.Errorf("CA file %s missing, a directory, or unreadable", path)
}

Try / catch

if err := startNsqadmin(cfg); err != nil && strings.Contains(err.Error(), "failed to read TLS root CA file") {
    return fmt.Errorf("fix path/permissions on %s (mount, chmod, SELinux) then restart", cfg.HTTPClientTLSRootCAFile)
}

Prevention

When it happens

Trigger: Pointing --http-client-tls-root-ca-file at a path that does not exist on the nsqadmin host, a file without read permission for the nsqadmin user, or a path that is a directory (e.g. trailing slash in a template). Distinct from a bad-PEM error: this fires before parsing, purely on os.ReadFile.

Common situations: Container images missing the mounted CA volume; paths written for a different host in Ansible/Chef; SELinux denying reads of custom CA locations; a typo'd absolute path; the file existing only on the nsqd nodes, not where nsqadmin runs.

Understand the failure class

Related errors


AI-assisted analysis of nsqio/nsq@85cf10c09c (2026-08-16). Data as JSON: /api/errors/d115810767665cb2. Report an issue: GitHub.