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

  1. 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
  2. Validate the pair offline: openssl x509 -noout -modulus -in client.pem | openssl md5 vs openssl rsa -noout -modulus -in client.key | openssl md5
  3. 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

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

Related errors


AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15). Data as JSON: /api/errors/bd37d7478673d502. Report an issue: GitHub.