nats-io/nats-server · error

failed to parse root ca certificate

Error message

failed to parse root ca certificate

What it means

The NATS server could not add any certificate from the configured root CA PEM file into an x509.CertPool. x509 pool.AppendCertsFromPEM silently returns false when the file contains no parseable CERTIFICATE PEM blocks, and the server converts that into this error when building the TLS config's ClientCAs for mTLS.

Source

Thrown at server/opts.go:5883

			}
			config.Certificates[i] = cert
		}
	}

	// Require client certificates as needed
	if tc.Verify {
		config.ClientAuth = tls.RequireAndVerifyClientCert
	}
	// Add in CAs if applicable.
	if tc.CaFile != _EMPTY_ {
		rootPEM, err := os.ReadFile(tc.CaFile)
		if err != nil || rootPEM == nil {
			return nil, err
		}
		pool := x509.NewCertPool()
		ok := pool.AppendCertsFromPEM(rootPEM)
		if !ok {
			return nil, fmt.Errorf("failed to parse root ca certificate")
		}
		config.ClientCAs = pool
	}
	// Allow setting TLS minimum version.
	if tc.MinVersion > 0 {
		if tc.MinVersion < tls.VersionTLS12 {
			return nil, fmt.Errorf("unsupported minimum TLS version: %s", tls.VersionName(tc.MinVersion))
		}
		config.MinVersion = tc.MinVersion
	}

	return &config, nil
}

// MergeOptions will merge two options giving preference to the flagOpts
// if the item is present.
func MergeOptions(fileOpts, flagOpts *Options) *Options {
	if fileOpts == nil {

View on GitHub (pinned to 3a66a489d2)

Solutions

  1. Validate the CA file with `openssl x509 -in ca.pem -text -noout` and confirm it prints a certificate.
  2. Point ca_file at the actual CA certificate/bundle, not the server cert or key.
  3. Convert DER to PEM: `openssl x509 -inform der -in ca.cer -out ca.pem`.
  4. Check the file is non-empty and readable at the resolved path (relative to cwd or config dir).

Example fix

// before
ca_file: "./server.key"
// after
ca_file: "./certs/ca.pem"
Defensive patterns

Strategy: validation

Validate before calling

// Verify CA file contains at least one parseable certificate before pointing ca_file at it
caPEM, err := os.ReadFile(caFile)
if err != nil { return err }
pool := x509.NewCertPool()
if !pool.AppendCertsFromPEM(caPEM) {
    return fmt.Errorf("%s contains no valid PEM certificates", caFile)
}

Type guard

func isValidCAPEM(path string) bool {
    pemBytes, err := os.ReadFile(path)
    if err != nil { return false }
    return x509.NewCertPool().AppendCertsFromPEM(pemBytes)
}

Try / catch

if !pool.AppendCertsFromPEM(rootPEM) {
    return fmt.Errorf("CA file %s has no parseable PEM certs; run: openssl x509 -in %s -text -noout", caPath, caPath)
}

Prevention

When it happens

Trigger: Setting the trust/CA option (ca_file in the TLS config block) to a file with no valid PEM certificates, e.g. an empty file, a DER-encoded cert, or a private key file.

Common situations: Pointing ca_file at the server's own cert/key instead of the CA bundle, DER-format CAs from Windows exports, empty or truncated file, wrong path resolution relative to the config file, or a bundle containing only expired/foreign blocks that fail to parse.

Understand the failure class

Related errors


AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02). Data as JSON: /api/errors/ac6e49acc814d9bb. Report an issue: GitHub.