{"record":{"id":"e04924937b56fb48","repo":"caddyserver/caddy","slug":"failed-reading-ca-cert-v","errorCode":null,"errorMessage":"failed reading ca cert: %v","messagePattern":"failed reading ca cert: (.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"caddyconfig/httploader.go","lineNumber":202,"sourceCode":"\t\t\t// See https://github.com/securego/gosec/issues/1054#issuecomment-2072235199\n\t\t\t//nolint:gosec\n\t\t\ttlsConfig = &tls.Config{Certificates: certs}\n\t\t} else if hl.TLS.ClientCertificateFile != \"\" && hl.TLS.ClientCertificateKeyFile != \"\" {\n\t\t\tcert, err := tls.LoadX509KeyPair(hl.TLS.ClientCertificateFile, hl.TLS.ClientCertificateKeyFile)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\t//nolint:gosec\n\t\t\ttlsConfig = &tls.Config{Certificates: []tls.Certificate{cert}}\n\t\t}\n\n\t\t// trusted server certs\n\t\tif len(hl.TLS.RootCAPEMFiles) > 0 {\n\t\t\trootPool := x509.NewCertPool()\n\t\t\tfor _, pemFile := range hl.TLS.RootCAPEMFiles {\n\t\t\t\tpemData, err := os.ReadFile(pemFile)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, fmt.Errorf(\"failed reading ca cert: %v\", err)\n\t\t\t\t}\n\t\t\t\trootPool.AppendCertsFromPEM(pemData)\n\t\t\t}\n\t\t\tif tlsConfig == nil {\n\t\t\t\ttlsConfig = new(tls.Config)\n\t\t\t}\n\t\t\ttlsConfig.RootCAs = rootPool\n\t\t}\n\n\t\tclient.Transport = &http.Transport{TLSClientConfig: tlsConfig}\n\t}\n\n\treturn client, nil\n}\n\nvar _ caddy.ConfigLoader = (*HTTPLoader)(nil)\n","sourceCodeStart":184,"sourceCodeEnd":219,"githubUrl":"https://github.com/caddyserver/caddy/blob/50e54ee279aa1e504fe218ca49ab6ae16c100410/caddyconfig/httploader.go#L184-L219","documentation":"While building the http loader's TLS config, each file in tls.root_ca (RootCAPEMFiles) is read with os.ReadFile; any read failure (missing file, permission denied, path is a directory) is wrapped with this message. Note AppendCertsFromPEM's boolean result is not checked, so only the file-read step can error — a wrong-format-but-readable file fails later at handshake time instead.","triggerScenarios":"tls { root_ca /path/that/does/not/exist.pem }; the CA file present but unreadable due to permissions or container mount issues; a path pointing at a directory.","commonSituations":"Docker/Kubernetes mounts that changed and dropped the CA file; running Caddy under a user without read access; absolute paths from another host copied verbatim.","solutions":["Verify the path exists and is readable by the Caddy process: 'ls -l /path/ca.pem'.","Fix the path or mount the file into the container at the configured location.","Ensure permissions allow the runtime user to read the file.","Confirm the file is PEM-formatted so the pool actually gets populated once readable."],"exampleFix":"# before\nhttp https://cfg.internal/config.json {\n  tls {\n    root_ca /etc/ssl/cfg-ca.crt   # not mounted\n  }\n}\n\n# after\n# docker run ... -v ./cfg-ca.crt:/etc/caddy/cfg-ca.pem ...\nhttp https://cfg.internal/config.json {\n  tls {\n    root_ca /etc/caddy/cfg-ca.pem\n  }\n}","handlingStrategy":"validation","validationCode":"for f in /etc/caddy/ca.pem; do test -r \"$f\" || echo \"unreadable: $f\"; done","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Mount CA files read-only into containers at fixed paths and reference those paths.","Add a startup check that all configured root_ca files exist and are readable by the Caddy user.","Remember a readable-but-non-PEM file passes silently here and fails later at TLS handshake — verify format too."],"tags":["config-loader","tls","file-permissions","ca-certificate"],"backgroundTag":null,"analyzedSha":"50e54ee279aa1e504fe218ca49ab6ae16c100410","analyzedAt":"2026-08-15T09:20:21.641Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}