go-task/task · error

failed to load client certificate: %w

Error message

failed to load client certificate: %w

What it means

This error wraps a tls.LoadX509KeyPair failure inside buildHTTPClient in taskfile/node_http.go. The library throws it when a client certificate/key pair was configured for the HTTP node but the files could not be read or parsed as a valid PEM key pair. The underlying TLS error is preserved via %w so you can see whether the problem was file access or certificate/key content.

Source

Thrown at taskfile/node_http.go:61

	// Load custom CA certificate if provided
	if caCert != "" {
		caCertData, err := os.ReadFile(caCert)
		if err != nil {
			return nil, fmt.Errorf("failed to read CA certificate: %w", err)
		}
		caCertPool := x509.NewCertPool()
		if !caCertPool.AppendCertsFromPEM(caCertData) {
			return nil, fmt.Errorf("failed to parse CA certificate")
		}
		tlsConfig.RootCAs = caCertPool
	}

	// Load client certificate and key if provided
	if cert != "" && certKey != "" {
		clientCert, err := tls.LoadX509KeyPair(cert, certKey)
		if err != nil {
			return nil, fmt.Errorf("failed to load client certificate: %w", err)
		}
		tlsConfig.Certificates = []tls.Certificate{clientCert}
	}

	return &http.Client{
		Transport: &http.Transport{
			TLSClientConfig: tlsConfig,
		},
	}, nil
}

func NewHTTPNode(
	entrypoint string,
	dir string,
	insecure bool,
	opts ...NodeOption,
) (*HTTPNode, error) {
	base := NewBaseNode(dir, opts...)

View on GitHub (pinned to 385e5ad92a)

Solutions

  1. Verify both cert and certKey files exist at the exact paths passed and are readable by the process (ls -l / absolute paths).
  2. Open both files and confirm they contain PEM blocks (-----BEGIN CERTIFICATE----- / -----BEGIN ... PRIVATE KEY-----); convert DER with `openssl x509 -inform der` / `openssl rsa -inform der` if needed.
  3. Confirm the key matches the cert: `openssl x509 -noout -modulus -in cert.pem` vs `openssl rsa -noout -modulus -in key.pem` should be equal.
  4. If you do not need mutual TLS, pass empty cert/certKey strings so the branch is skipped entirely.

Example fix

// before (paths wrong / files missing)
NewHTTPNode("https://example.com/taskfile.yml", cert: "certs/client.crt", certKey: "certs/client.key")
// after (absolute, verified paths)
NewHTTPNode("https://example.com/taskfile.yml", cert: "/etc/task/certs/client.crt", certKey: "/etc/task/certs/client.key")
Defensive patterns

Strategy: validation

Validate before calling

for _, p := range []string{certPath, keyPath} {
    if p == "" { continue }
    fi, err := os.Stat(p)
    if err != nil { return fmt.Errorf("missing TLS file %s: %w", p, err) }
    if fi.Size() == 0 { return fmt.Errorf("empty TLS file %s", p) }
    if _, err := tls.LoadX509KeyPair(certPath, keyPath); err != nil {
        return fmt.Errorf("invalid key pair: %w", err)
    }
}

Prevention

When it happens

Trigger: Calling NewHTTPNode (or buildHTTPClient via tests) with both a cert and certKey path set where the files do not exist, are unreadable (permissions), are empty, are in the wrong format (e.g. DER instead of PEM), or the key does not match the certificate.

Common situations: Misconfigured taskfile remote-fetch TLS settings: typo in cert/key path, relative path resolved against wrong working directory, certificate rotated and old files deleted, base64-encoded PEM not decoded to disk, or key regenerated without updating the cert.

Understand the failure class

Related errors


AI-assisted analysis of go-task/task@385e5ad92a (2026-09-05). Data as JSON: /api/errors/821348300e1ee2db. Report an issue: GitHub.