{"record":{"id":"e5f7c5759442ab84","repo":"router-for-me/CLIProxyAPI","slug":"home-tls-client-certificate-and-key-must-be-set-t","errorCode":null,"errorMessage":"home tls: client certificate and key must be set together","messagePattern":"home tls: client certificate and key must be set together","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/home/client.go","lineNumber":637,"sourceCode":"\t\treturn nil, nil\n\t}\n\n\tserverName := strings.TrimSpace(cfg.ServerName)\n\tif serverName == \"\" {\n\t\tserverName = strings.TrimSpace(fallbackServerName)\n\t}\n\n\ttlsConfig := &tls.Config{\n\t\tMinVersion:         tls.VersionTLS12,\n\t\tServerName:         serverName,\n\t\tInsecureSkipVerify: cfg.InsecureSkipVerify,\n\t}\n\n\tclientCertPath := strings.TrimSpace(cfg.ClientCert)\n\tclientKeyPath := strings.TrimSpace(cfg.ClientKey)\n\tif clientCertPath != \"\" || clientKeyPath != \"\" {\n\t\tif clientCertPath == \"\" || clientKeyPath == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"home tls: client certificate and key must be set together\")\n\t\t}\n\t\tcertPair, errLoad := tls.LoadX509KeyPair(clientCertPath, clientKeyPath)\n\t\tif errLoad != nil {\n\t\t\treturn nil, fmt.Errorf(\"home tls: load client certificate: %w\", errLoad)\n\t\t}\n\t\ttlsConfig.Certificates = []tls.Certificate{certPair}\n\t}\n\n\tcaCertPath := strings.TrimSpace(cfg.CACert)\n\tif caCertPath == \"\" {\n\t\treturn tlsConfig, nil\n\t}\n\n\tcaCertPEM, errRead := os.ReadFile(caCertPath)\n\tif errRead != nil {\n\t\treturn nil, fmt.Errorf(\"home tls: read ca-cert: %w\", errRead)\n\t}\n","sourceCodeStart":619,"sourceCodeEnd":655,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/home/client.go#L619-L655","documentation":"Returned by the TLS-config builder in internal/home/client.go when exactly one of client-cert / client-key is set (after trimming). mTLS requires the pair together; supplying only one is treated as a configuration error rather than silently skipping client certificates.","triggerScenarios":"Config sets client-cert but not client-key, or vice versa; one path left as an empty string while the other is populated; YAML indentation puts one key outside the home block.","commonSituations":"Copy-paste of a partial mTLS example; key path commented out accidentally; a templating variable for the key rendered empty in one environment (e.g. Kubernetes secret with a missing key).","solutions":["Set both client-cert and client-key paths (or neither, if the server does not require mTLS)","Check YAML indentation places both keys under the same home/tls block","If using templating/secrets, assert both files exist before starting"],"exampleFix":"# before\nhome:\n  tls:\n    client-cert: /etc/cliproxy/client.pem\n\n# after\nhome:\n  tls:\n    client-cert: /etc/cliproxy/client.pem\n    client-key: /etc/cliproxy/client.key","handlingStrategy":"validation","validationCode":"cert := strings.TrimSpace(cfg.TLS.ClientCert)\nkey := strings.TrimSpace(cfg.TLS.ClientKey)\nif (cert == \"\") != (key == \"\") {\n    return errors.New(\"client-cert and client-key must both be set, or both omitted\")\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Schema-validate configs (JSON schema or startup lint) rejecting half-set mTLS pairs","In Kubernetes, use one secret containing both tls.crt/tls.key so they deploy atomically"],"tags":["go","tls","mtls","configuration","home"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}