{"record":{"id":"f19bb9c0b8033fe3","repo":"crowdsecurity/crowdsec","slug":"missing-tls-key-file-f19bb9","errorCode":null,"errorMessage":"missing TLS key file","messagePattern":"missing TLS key file","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"pkg/apiserver/apiserver.go","lineNumber":377,"sourceCode":"\t}\n\n\treturn nil\n}\n\n// listenAndServeLAPI starts the http server and blocks until it's closed\n// it also updates the URL field with the actual address the server is listening on\n// it's meant to be run in a separate goroutine\nfunc (s *APIServer) listenAndServeLAPI(ctx context.Context, apiReady chan bool) error {\n\tserverError := make(chan error, 2)\n\n\tlistenConfig := &net.ListenConfig{}\n\n\tstartServer := func(listener net.Listener, canTLS bool) {\n\t\tvar err error\n\n\t\tif canTLS && s.cfg.TLS != nil && (s.cfg.TLS.CertFilePath != \"\" || s.cfg.TLS.KeyFilePath != \"\") {\n\t\t\tif s.cfg.TLS.KeyFilePath == \"\" {\n\t\t\t\tserverError <- errors.New(\"missing TLS key file\")\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif s.cfg.TLS.CertFilePath == \"\" {\n\t\t\t\tserverError <- errors.New(\"missing TLS cert file\")\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\terr = s.httpServer.ServeTLS(listener, s.cfg.TLS.CertFilePath, s.cfg.TLS.KeyFilePath)\n\t\t} else {\n\t\t\terr = s.httpServer.Serve(listener)\n\t\t}\n\n\t\tswitch {\n\t\tcase errors.Is(err, http.ErrServerClosed):\n\t\t\tbreak\n\t\tcase err != nil:\n\t\t\tserverError <- err","sourceCodeStart":359,"sourceCodeEnd":395,"githubUrl":"https://github.com/crowdsecurity/crowdsec/blob/909b5157986a2b2c2163300fdaef5ed01289f7d2/pkg/apiserver/apiserver.go#L359-L395","documentation":"The LAPI server enables TLS when a cert or key file path is configured, but ServeTLS requires both. If only cert_file_path is set and key_file_path is empty, the server reports 'missing TLS key file' on the serverError channel and refuses to start with TLS.","triggerScenarios":"api.server.tls configured in crowdsec config with cert_file_path set but key_file_path empty or omitted.","commonSituations":"Copy-pasted TLS config where the key line was deleted or mis-indented (wrong YAML nesting so the key lands outside the tls block), provisioning tools that templated only the cert path.","solutions":["Set api.server.tls.key_file_path in the config to the path of the private key file.","Verify YAML indentation: key_file_path must be nested under the same tls: section as cert_file_path.","If TLS is not wanted, remove both cert and key paths so the server falls back to plain HTTP."],"exampleFix":"# before\napi:\n  server:\n    tls:\n      cert_file_path: /etc/ssl/certs/lapi.crt\n# after\napi:\n  server:\n    tls:\n      cert_file_path: /etc/ssl/certs/lapi.crt\n      key_file_path: /etc/ssl/private/lapi.key","handlingStrategy":"validation","validationCode":"tls := cfg.API.Server.TLS\nif tls != nil && (tls.CertFilePath != \"\" || tls.KeyFilePath != \"\") {\n    if tls.CertFilePath == \"\" || tls.KeyFilePath == \"\" {\n        return fmt.Errorf(\"both cert_file_path and key_file_path are required\")\n    }\n    if _, err := os.Stat(tls.CertFilePath); err != nil { return err }\n    if _, err := os.Stat(tls.KeyFilePath); err != nil { return err }\n}","typeGuard":null,"tryCatchPattern":"if err := server.StartApiserver(ctx); err != nil {\n    log.Fatalf(\"LAPI startup failed: %v\", err) // inspect serverError channel output\n}","preventionTips":["Always configure cert_file_path and key_file_path as a pair (use config templates/anchors).","Validate the full config with `cscli config show` / `crowdsec -c ... -t` before restart.","Keep both files in the same provisioning playbook step."],"tags":["tls","lapi","configuration"],"backgroundTag":"missing-required-config-field","analyzedSha":"909b5157986a2b2c2163300fdaef5ed01289f7d2","analyzedAt":"2026-09-06T12:27:26.012Z","contentChangedAt":"2026-09-06T12:27:26.012Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}