{"record":{"id":"306a019b78fe59f6","repo":"gohugoio/hugo","slug":"failed-to-parse-root-certificate","errorCode":null,"errorMessage":"failed to parse root certificate","messagePattern":"failed to parse root certificate","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"commands/server.go","lineNumber":729,"sourceCode":"\t\t\tif err := c.verifyCert(rootPem, certPEM, hostname); err == nil {\n\t\t\t\tc.r.Println(\"Using existing\", c.tlsCertFile, \"and\", c.tlsKeyFile)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t}\n\n\tc.r.Println(\"Creating TLS certificates in\", keyDir)\n\n\t// Yes, this is unfortunate, but it's currently the only way to use Mkcert as a library.\n\tos.Args = []string{\"-cert-file\", c.tlsCertFile, \"-key-file\", c.tlsKeyFile, hostname}\n\treturn mclib.RunMain()\n}\n\nfunc (c *serverCommand) verifyCert(rootPEM, certPEM []byte, name string) error {\n\troots := x509.NewCertPool()\n\tok := roots.AppendCertsFromPEM(rootPEM)\n\tif !ok {\n\t\treturn fmt.Errorf(\"failed to parse root certificate\")\n\t}\n\n\tblock, _ := pem.Decode(certPEM)\n\tif block == nil {\n\t\treturn fmt.Errorf(\"failed to parse certificate PEM\")\n\t}\n\tcert, err := x509.ParseCertificate(block.Bytes)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to parse certificate: %v\", err.Error())\n\t}\n\n\topts := x509.VerifyOptions{\n\t\tDNSName: name,\n\t\tRoots:   roots,\n\t}\n\n\tif _, err := cert.Verify(opts); err != nil {\n\t\treturn fmt.Errorf(\"failed to verify certificate: %v\", err.Error())","sourceCodeStart":711,"sourceCodeEnd":747,"githubUrl":"https://github.com/gohugoio/hugo/blob/52c9bd7908b4d02d4d0ff8f82a888834d6ee10d2/commands/server.go#L711-L747","documentation":"`serverCommand.verifyCert` builds an `x509.CertPool` from the mkcert root CA PEM (`rootCA.pem`). `AppendCertsFromPEM` returns false when no certificate could be parsed from the input, and Hugo wraps that as a plain parse error. This blocks reuse of an existing site certificate.","triggerScenarios":"Fires when `rootPem` (read from `$CAROOT/rootCA.pem`) is empty, truncated, or not valid PEM — so the pool rejects it. This is part of the cached-cert reuse path in `createCertificates`.","commonSituations":"The `$CAROOT` env var points at a stale or emptied directory; `rootCA.pem` was corrupted by an editor; mkcert was partially uninstalled leaving a zero-byte file; permissions prevent reading the full file.","solutions":["Verify `$CAROOT` is set and `rootCA.pem` exists with valid PEM content.","Regenerate the root CA with `mkcert -install` (or let Hugo recreate certs).","Delete the cached site cert under the cache `_mkcerts` dir so Hugo provisions fresh.","Confirm read permissions on `$CAROOT/rootCA.pem`."],"exampleFix":"// before\n// $CAROOT/rootCA.pem is 0 bytes\n// after\n// $ mkcert -uninstall && mkcert -install","handlingStrategy":"validation","validationCode":"import \"encoding/pem\"\nfunc validateRootCA(path string) error {\n  b, err := os.ReadFile(path)\n  if err != nil { return err }\n  var block *pem.Block\n  rest := b\n  found := false\n  for {\n    block, rest = pem.Decode(rest)\n    if block == nil { break }\n    if block.Type == \"CERTIFICATE\" { found = true; break }\n  }\n  if !found { return fmt.Errorf(\"rootCA.pem has no CERTIFICATE block\") }\n  return nil\n}","typeGuard":"null","tryCatchPattern":"// Treat cached-cert reuse failure as a signal to regenerate.\nif err := c.verifyCert(rootPem, certPEM, hostname); err != nil {\n  // fall through to provisioning fresh certs via mkcert\n}","preventionTips":["Keep `$CAROOT` stable and backed up; don't manually edit `rootCA.pem`.","Re-run `mkcert -install` after OS reinstalls or profile resets.","Delete the `_mkcerts` cache if certs start failing verification."],"tags":["hugo","server","tls","mkcert","certificate"],"backgroundTag":null,"analyzedSha":"52c9bd7908b4d02d4d0ff8f82a888834d6ee10d2","analyzedAt":"2026-08-09T21:49:36.660Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}