amir20/dozzle · error

failed to parse certificate

Error message

failed to parse certificate: %w

What it means

NewClient fails with "failed to parse certificate: %w" when x509.ParseCertificate cannot decode the DER bytes of the leaf certificate loaded from shared_cert.pem. This means the certificate file exists but its contents are corrupt, truncated, or not a valid X.509 certificate.

Solutions

  1. Regenerate the shared certificates with `make generate` on the host and restart both agent and server so both sides use the same keypair
  2. Verify shared_cert.pem is a valid PEM certificate: `openssl x509 -in shared_cert.pem -text -noout`; replace if it errors
  3. Ensure the cert volume/mount is complete and not truncated (check file size and that the container sees the updated file)
  4. Check for version skew: upgrade client and agent to the same Dozzle release

Example fix

// before
certPEM, _ := os.ReadFile("stale-shared_cert.pem")
certificates, err := tls.X509KeyPair(certPEM, keyPEM) // leaf may be corrupt
client, err := agent.NewClient(endpoint, certificates)
// after
certPEM, err := os.ReadFile("shared_cert.pem") // regenerated via make generate
if err != nil { return err }
if _, err := tls.X509KeyPair(certPEM, keyPEM); err != nil {
    return fmt.Errorf("invalid shared cert, regenerate with make generate: %w", err)
}
client, err := agent.NewClient(endpoint, certificates)
Defensive patterns

Strategy: validation

Validate before calling

certPEM, err := os.ReadFile("shared_cert.pem")
if err != nil { return err }
if _, err := tls.X509KeyPair(certPEM, keyPEM); err != nil {
    return fmt.Errorf("corrupt shared cert; run make generate: %w", err)
}

Try / catch

client, err := agent.NewClient(endpoint, certs)
if err != nil {
    if strings.Contains(err.Error(), "failed to parse certificate") {
        log.Fatal().Err(err).Msg("regenerate shared certs with make generate")
    }
    return err
}

Prevention

When it happens

Trigger: agent.NewClient(endpoint, certificates) is called and certificates.Certificate[0] (the leaf DER bytes from the shared cert file) cannot be parsed by crypto/x509.

Common situations: shared_cert.pem regenerated or corrupted mid-deploy so clients hold a stale/mismatched copy; file mounted empty or with placeholder text; an old client image using certs from a newer algorithm; copying the PEM text file where DER bytes were expected.

Understand the failure class

Related errors


AI-assisted analysis of amir20/dozzle@d9463cbe21 (2026-09-07). Data as JSON: /api/errors/53e569cc38f326d2. Report an issue: GitHub.

Appendix: source

Thrown at internal/agent/client.go:49

type Client struct {
	client       pb.AgentServiceClient
	conn         *grpc.ClientConn
	endpoint     string
	nameOverride string
	group        string
}

func NewClient(endpoint string, certificates tls.Certificate, opts ...grpc.DialOption) (*Client, error) {
	endpoint, nameOverride, group, err := ParseEndpoint(endpoint)
	if err != nil {
		return nil, err
	}

	caCertPool := x509.NewCertPool()
	c, err := x509.ParseCertificate(certificates.Certificate[0])
	if err != nil {
		return nil, fmt.Errorf("failed to parse certificate: %w", err)
	}
	caCertPool.AddCert(c)
	tlsConfig := &tls.Config{
		Certificates:       []tls.Certificate{certificates},
		RootCAs:            caCertPool,
		InsecureSkipVerify: true, // Set to true if the server's hostname does not match the certificate
	}

	// Create the gRPC transport credentials
	creds := credentials.NewTLS(tlsConfig)

	opts = append(opts,
		grpc.WithTransportCredentials(creds),
		grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(10*1024*1024), grpc.UseCompressor(gzip.Name)),
		grpc.WithKeepaliveParams(keepalive.ClientParameters{
			Time:                30 * time.Second,
			Timeout:             10 * time.Second,
			PermitWithoutStream: true,

View on GitHub (pinned to d9463cbe21)