{"record":{"id":"2365d72858e68f2b","repo":"joewalnes/websocketd","slug":"failed-to-read-ca-file-s-w","errorCode":null,"errorMessage":"failed to read CA file %s: %w","messagePattern":"failed to read CA file (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"main.go","lineNumber":108,"sourceCode":"\t\treturn serveMutualTLS(listener, config.CertFile, config.KeyFile, config.SslCaFile, log)\n\t}\n\tserver := &http.Server{ReadHeaderTimeout: readHeaderTimeout, TLSConfig: tlsConfig()}\n\treturn server.ServeTLS(listener, config.CertFile, config.KeyFile)\n}\n\n// tlsConfig returns the base TLS settings shared by all HTTPS servers. It pins\n// a minimum protocol version explicitly rather than relying on the Go default,\n// which has drifted across releases.\nfunc tlsConfig() *tls.Config {\n\treturn &tls.Config{MinVersion: tls.VersionTLS12}\n}\n\n// serveMutualTLS runs an HTTPS server on the given listener that requires\n// client certificates verified against the given CA file.\nfunc serveMutualTLS(listener net.Listener, certFile, keyFile, caFile string, log *libwebsocketd.LogScope) error {\n\tcaCert, err := os.ReadFile(caFile)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to read CA file %s: %w\", caFile, err)\n\t}\n\tcaCertPool := x509.NewCertPool()\n\tif !caCertPool.AppendCertsFromPEM(caCert) {\n\t\treturn fmt.Errorf(\"failed to parse CA certificates from %s\", caFile)\n\t}\n\n\tcfg := tlsConfig()\n\tcfg.ClientAuth = tls.RequireAndVerifyClientCert\n\tcfg.ClientCAs = caCertPool\n\tserver := &http.Server{\n\t\tReadHeaderTimeout: readHeaderTimeout,\n\t\tTLSConfig:         cfg,\n\t}\n\tlog.Info(\"server\", \"Mutual TLS enabled (client certs verified against %s)\", caFile)\n\treturn server.ServeTLS(listener, certFile, keyFile)\n}\n\n// unixSocketProbeTimeout bounds the liveness probe against an existing socket","sourceCodeStart":90,"sourceCodeEnd":126,"githubUrl":"https://github.com/joewalnes/websocketd/blob/7a8683dc7f9778dc615945aaed2a8dc77290227b/main.go#L90-L126","documentation":"When --ssl --ssl-ca-file=FILE is given, websocketd enables mutual TLS: it reads the CA bundle to build the x509 pool that verifies client certificates. This error wraps os.ReadFile's failure — the file could not be opened or read — and aborts startup.","triggerScenarios":"os.ReadFile(caFile) fails: the --ssl-ca-file path doesn't exist, has a typo, points into a container without the file mounted, or the service user lacks read permission on it.","commonSituations":"Docker/Kubernetes volume not mounted at the configured path; file created by root with 0600 while the daemon drops privileges; relative path resolved from a different working directory under systemd; secret name typo in deployment manifests.","solutions":["Verify the path in --ssl-ca-file exists from the server's perspective: ls -l <path> as the same user websocketd runs as.","Fix file permissions (chmod/chown) or the volume mount so the process can read the CA file.","Use an absolute path so it resolves regardless of the daemon's working directory.","Confirm the secret/configmap containing the CA is actually deployed and mounted in containerized setups."],"exampleFix":"# before\nwebsocketd --ssl --ssl-cert=cert.pem --ssl-key=key.pem --ssl-ca-file=./ca.pem ./handler\n# after (absolute path, readable by the service user)\nwebsocketd --ssl --ssl-cert=cert.pem --ssl-key=key.pem --ssl-ca-file=/etc/websocketd/ca.pem ./handler","handlingStrategy":"validation","validationCode":"import \"os\"\nfunc assertReadableCA(path string) error {\n    fi, err := os.Stat(path)\n    if err != nil { return fmt.Errorf(\"CA file %s: %w\", path, err) }\n    f, err := os.Open(path)\n    if err != nil { return fmt.Errorf(\"CA file %s not readable: %w\", path, err) }\n    defer f.Close()\n    _ = fi\n    return nil\n}","typeGuard":null,"tryCatchPattern":"if err := startServer(); err != nil {\n    var pe *fs.PathError\n    if errors.As(err, &pe) && strings.Contains(err.Error(), \"failed to read CA file\") {\n        log.Fatalf(\"CA file %s unreadable: %v\", pe.Path, pe.Err)\n    }\n    return err\n}","preventionTips":["Use absolute paths for --ssl-ca-file.","Mount CA secrets in containers and verify with `ls -l` as the daemon user before start.","Set file mode 0644 (readable) on CA bundles; they are public material.","Validate the path exists in deployment scripts/health checks before launch."],"tags":["tls","mutual-tls","file-not-found","configuration"],"backgroundTag":"ca-file-unreadable","analyzedSha":"7a8683dc7f9778dc615945aaed2a8dc77290227b","analyzedAt":"2026-09-03T13:52:22.309Z","contentChangedAt":"2026-09-03T13:52:22.309Z","schemaVersion":2},"datasetVersion":"2026-09-10T17:17:09.494Z"}