{"record":{"id":"9b9540e1915d8940","repo":"slackhq/nebula","slug":"host-certificate-is-a-ca-certificate","errorCode":null,"errorMessage":"host certificate is a CA certificate","messagePattern":"host certificate is a CA certificate","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pki.go","lineNumber":534,"sourceCode":"\treturn\n}\n\nfunc loadCertificate(b []byte) (cert.Certificate, []byte, error) {\n\tc, b, err := cert.UnmarshalCertificateFromPEM(b)\n\tif err != nil {\n\t\treturn nil, b, fmt.Errorf(\"error while unmarshaling pki.cert: %w\", err)\n\t}\n\n\tif c.Expired(time.Now()) {\n\t\treturn nil, b, fmt.Errorf(\"nebula certificate for this host is expired\")\n\t}\n\n\tif len(c.Networks()) == 0 {\n\t\treturn nil, b, fmt.Errorf(\"no networks encoded in certificate\")\n\t}\n\n\tif c.IsCA() {\n\t\treturn nil, b, fmt.Errorf(\"host certificate is a CA certificate\")\n\t}\n\n\treturn c, b, nil\n}\n\nfunc loadCAPoolFromConfig(l *slog.Logger, c *config.C) (*cert.CAPool, error) {\n\tcaPathOrPEM := c.GetString(\"pki.ca\", \"\")\n\tif caPathOrPEM == \"\" {\n\t\treturn nil, errors.New(\"no pki.ca path or PEM data provided\")\n\t}\n\n\tvar caReader io.ReadCloser\n\tvar err error\n\n\tif strings.Contains(caPathOrPEM, \"-----BEGIN\") {\n\t\tcaReader = io.NopCloser(strings.NewReader(caPathOrPEM))\n\t} else {\n\t\tcaReader, err = os.Open(caPathOrPEM)","sourceCodeStart":516,"sourceCodeEnd":552,"githubUrl":"https://github.com/slackhq/nebula/blob/dd8f660c0ac37903ec4080ca4d3c861ba9342ceb/pki.go#L516-L552","documentation":"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.","triggerScenarios":"newCertStateFromConfig is given a certificate produced by 'nebula-cert ca' (IsCA set) instead of one produced by 'nebula-cert sign'.","commonSituations":"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.","solutions":["Point pki.cert at the host certificate issued by the CA, not ca.crt","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'","Keep pki.ca set to ca.crt and pki.cert set to host.crt in the config"],"exampleFix":"// before\npki:\n  ca: /etc/nebula/ca.crt\n  cert: /etc/nebula/ca.crt   # CA cert reused as host cert\n// after\npki:\n  ca: /etc/nebula/ca.crt\n  cert: /etc/nebula/host.crt # host cert signed by the CA","handlingStrategy":"validation","validationCode":"c, _, err := cert.UnmarshalCertificateFromPEM(certBytes)\nif err == nil && c.IsCA() {\n    return fmt.Errorf(\"%s is a CA cert; use the host cert for pki.cert\", path)\n}","typeGuard":"func isHostCert(c cert.Certificate) bool { return !c.IsCA() }","tryCatchPattern":"if err := startNebula(); err != nil && strings.Contains(err.Error(), \"is a CA certificate\") {\n    return fmt.Errorf(\"pki.cert must be a host cert, not ca.crt: %w\", err)\n}","preventionTips":["Name files unambiguously: ca.crt vs <host>.crt","Restrict CA key/cert distribution; hosts only need ca.crt in pki.ca","Config-lint that pki.cert != pki.ca","Sign host certs with 'nebula-cert sign', never reuse 'nebula-cert ca' output as identity"],"tags":["pki","certificate","config"],"backgroundTag":"ca-certificate-as-host-cert","analyzedSha":"dd8f660c0ac37903ec4080ca4d3c861ba9342ceb","analyzedAt":"2026-09-03T11:13:55.444Z","contentChangedAt":"2026-09-03T11:13:55.444Z","schemaVersion":2},"datasetVersion":"2026-09-10T17:17:09.494Z"}