slackhq/nebula · error

host certificate is a CA certificate

Error message

host certificate is a CA certificate

What it means

loadCertificate rejects certificates where c.IsCA() is true: a CA certificate is a signing identity, not a host identity, and cannot be used as pki.cert. This prevents operators from accidentally booting a node with the CA's own certificate and key material.

Solutions

  1. Point pki.cert at the host certificate issued by the CA, not ca.crt
  2. If no host cert exists, sign one: 'nebula-cert sign -ca-cpath ca.crt -ca-kpath ca.key -name host -ip 10.0.0.5'
  3. Keep pki.ca set to ca.crt and pki.cert set to host.crt in the config

Example fix

// before
pki:
  ca: /etc/nebula/ca.crt
  cert: /etc/nebula/ca.crt   # CA cert reused as host cert
// after
pki:
  ca: /etc/nebula/ca.crt
  cert: /etc/nebula/host.crt # host cert signed by the CA
Defensive patterns

Strategy: validation

Validate before calling

c, _, err := cert.UnmarshalCertificateFromPEM(certBytes)
if err == nil && c.IsCA() {
    return fmt.Errorf("%s is a CA cert; use the host cert for pki.cert", path)
}

Type guard

func isHostCert(c cert.Certificate) bool { return !c.IsCA() }

Try / catch

if err := startNebula(); err != nil && strings.Contains(err.Error(), "is a CA certificate") {
    return fmt.Errorf("pki.cert must be a host cert, not ca.crt: %w", err)
}

Prevention

When it happens

Trigger: newCertStateFromConfig is given a certificate produced by 'nebula-cert ca' (IsCA set) instead of one produced by 'nebula-cert sign'.

Common situations: Config copy-paste mixup where pki.cert and pki.ca both point at ca.crt; packaging scripts that bundle the wrong file as the host cert.

Understand the failure class

Related errors


AI-assisted analysis of slackhq/nebula@dd8f660c0a (2026-09-03). Data as JSON: /api/errors/9b9540e1915d8940. Report an issue: GitHub.

Appendix: source

Thrown at pki.go:534

	return
}

func loadCertificate(b []byte) (cert.Certificate, []byte, error) {
	c, b, err := cert.UnmarshalCertificateFromPEM(b)
	if err != nil {
		return nil, b, fmt.Errorf("error while unmarshaling pki.cert: %w", err)
	}

	if c.Expired(time.Now()) {
		return nil, b, fmt.Errorf("nebula certificate for this host is expired")
	}

	if len(c.Networks()) == 0 {
		return nil, b, fmt.Errorf("no networks encoded in certificate")
	}

	if c.IsCA() {
		return nil, b, fmt.Errorf("host certificate is a CA certificate")
	}

	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)

View on GitHub (pinned to dd8f660c0a)