{"record":{"id":"bd37d7478673d502","repo":"router-for-me/CLIProxyAPI","slug":"home-tls-load-client-certificate-w","errorCode":null,"errorMessage":"home tls: load client certificate: %w","messagePattern":"home tls: load client certificate: %w","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/home/client.go","lineNumber":641,"sourceCode":"\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\n\tcertPool, errPool := x509.SystemCertPool()\n\tif errPool != nil || certPool == nil {\n\t\tcertPool = x509.NewCertPool()\n\t}","sourceCodeStart":623,"sourceCodeEnd":659,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/home/client.go#L623-L659","documentation":"Wraps the error from tls.LoadX509KeyPair in internal/home/client.go when loading the configured client certificate and key for home mTLS. The %w wrap preserves the crypto/tls cause: file missing/unreadable, malformed PEM, or 'private key does not match public key' when cert and key are from different pairs.","triggerScenarios":"client-cert/client-key paths point at nonexistent files; cert and key were regenerated independently so they no longer match; either file is not valid PEM; file permissions (0600 root-owned) deny the process read access.","commonSituations":"Certificate rotated but only the cert file replaced; paths valid in dev but the mount is different in the container; files owned by root while the proxy runs as a non-root user; enrollment wrote the key to a different directory than configured.","solutions":["Read the wrapped error: 'private key does not match public key' => re-issue the pair together (re-run enrollment); open/permission errors => fix path or chmod/chown","Validate the pair offline: openssl x509 -noout -modulus -in client.pem | openssl md5 vs openssl rsa -noout -modulus -in client.key | openssl md5","Ensure the process user can read both files"],"exampleFix":"# cert rotated without the key — re-enroll to get a matching pair\ncliproxy enroll --certificate-id proxy-node-01 --out /etc/cliproxy/\n\nhome:\n  tls:\n    client-cert: /etc/cliproxy/client.pem\n    client-key: /etc/cliproxy/client.key","handlingStrategy":"validation","validationCode":"// preflight the pair exactly like the runtime will\nif _, err := tls.LoadX509KeyPair(cfg.TLS.ClientCert, cfg.TLS.ClientKey); err != nil {\n    return fmt.Errorf(\"preflight mTLS pair failed (rotate/re-enroll if key mismatch): %w\", err)\n}","typeGuard":null,"tryCatchPattern":"if err != nil {\n    var pathErr *fs.PathError\n    if errors.As(err, &pathErr) { fixPathsAndRestart() }\n    if strings.Contains(err.Error(), \"private key does not match\") { reEnroll() }\n}","preventionTips":["Always rotate cert and key as an atomic pair from a single enrollment","Run the LoadX509KeyPair preflight in a startup readiness probe"],"tags":["go","tls","mtls","private-key","home"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}