router-for-me/CLIProxyAPI · error
home tls: load client certificate: %w
Error message
home tls: load client certificate: %w
What it means
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.
Source
Thrown at internal/home/client.go:641
if serverName == "" {
serverName = strings.TrimSpace(fallbackServerName)
}
tlsConfig := &tls.Config{
MinVersion: tls.VersionTLS12,
ServerName: serverName,
InsecureSkipVerify: cfg.InsecureSkipVerify,
}
clientCertPath := strings.TrimSpace(cfg.ClientCert)
clientKeyPath := strings.TrimSpace(cfg.ClientKey)
if clientCertPath != "" || clientKeyPath != "" {
if clientCertPath == "" || clientKeyPath == "" {
return nil, fmt.Errorf("home tls: client certificate and key must be set together")
}
certPair, errLoad := tls.LoadX509KeyPair(clientCertPath, clientKeyPath)
if errLoad != nil {
return nil, fmt.Errorf("home tls: load client certificate: %w", errLoad)
}
tlsConfig.Certificates = []tls.Certificate{certPair}
}
caCertPath := strings.TrimSpace(cfg.CACert)
if caCertPath == "" {
return tlsConfig, nil
}
caCertPEM, errRead := os.ReadFile(caCertPath)
if errRead != nil {
return nil, fmt.Errorf("home tls: read ca-cert: %w", errRead)
}
certPool, errPool := x509.SystemCertPool()
if errPool != nil || certPool == nil {
certPool = x509.NewCertPool()
}View on GitHub (pinned to 78f0c4079e)
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
Example fix
# cert rotated without the key — re-enroll to get a matching pair
cliproxy enroll --certificate-id proxy-node-01 --out /etc/cliproxy/
home:
tls:
client-cert: /etc/cliproxy/client.pem
client-key: /etc/cliproxy/client.key Defensive patterns
Strategy: validation
Validate before calling
// preflight the pair exactly like the runtime will
if _, err := tls.LoadX509KeyPair(cfg.TLS.ClientCert, cfg.TLS.ClientKey); err != nil {
return fmt.Errorf("preflight mTLS pair failed (rotate/re-enroll if key mismatch): %w", err)
} Try / catch
if err != nil {
var pathErr *fs.PathError
if errors.As(err, &pathErr) { fixPathsAndRestart() }
if strings.Contains(err.Error(), "private key does not match") { reEnroll() }
} Prevention
- Always rotate cert and key as an atomic pair from a single enrollment
- Run the LoadX509KeyPair preflight in a startup readiness probe
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- client key pem is invalid
- client key is not rsa
- client key pem type %q is unsupported
- home tls: client certificate and key must be set together
- home ca fingerprint is required
AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15).
Data as JSON: /api/errors/bd37d7478673d502.
Report an issue: GitHub.