amir20/dozzle · critical

failed to parse certificate

Error message

failed to parse certificate: %w

What it means

NewServer wraps the error from x509.ParseCertificate when the first leaf certificate of the supplied tls.Certificate cannot be parsed into an x509.Certificate. This certificate is used both as the server cert and added to the CA cert pool for mTLS verification of agents, so the gRPC agent server cannot be created at all. It always carries the underlying x509 parse error via %w.

Solutions

  1. Regenerate the certificates with 'make generate' and restart the agent
  2. Inspect shared_cert.pem: it must contain a valid '-----BEGIN CERTIFICATE-----' block, not a key or CSR
  3. Verify the code loading certificates returns tls.X509KeyPair(cert, key) correctly and errors are not swallowed
  4. Check file permissions/read errors so the cert file is not read as empty bytes

Example fix

// before: loading cert file raw into tls.Certificate
cert := tls.Certificate{Certificate: [][]byte{certFileBytes}}
// after: parse a proper keypair and propagate errors
cert, err := tls.LoadX509KeyPair("shared_cert.pem", "shared_key.pem")
if err != nil {
    return fmt.Errorf("loading agent certs: %w", err)
}
Defensive patterns

Strategy: validation

Validate before calling

if len(cert.Certificate) == 0 {
    return fmt.Errorf("no certificate data supplied")
}
if _, err := x509.ParseCertificate(cert.Certificate[0]); err != nil {
    return fmt.Errorf("invalid agent certificate: %w", err)
}

Try / catch

if _, err := agent.NewServer(svc, cert, version, handler); err != nil {
    var parseErr *x509.CertificateInvalidError
    if errors.As(err, &parseErr) { /* regenerate certs */ }
    log.Fatalf("agent server init failed: %v", err)
}

Prevention

When it happens

Trigger: Calling internal/agent.NewServer with a tls.Certificate whose Certificate[0] is empty, malformed PEM/DER data, or otherwise not a valid X.509 certificate (e.g. cert generated by 'make generate' failed or file loaded with wrong type, like loading the key as a certificate).

Common situations: shared_cert.pem is empty, corrupted, or truncated; user copied a private key or CSR into the certificate file; certificates generated with an unsupported algorithm; certificate file replaced by an HTML error page or placeholder during image builds.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


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

Appendix: source

Thrown at internal/agent/server.go:563

		pbStat := &pb.NotificationSubscriptionStats{
			SubscriptionId:        int32(s.SubscriptionID),
			TriggerCount:          s.TriggerCount,
			TriggeredContainerIds: s.TriggeredContainerIDs,
		}
		if s.LastTriggeredAt != nil {
			pbStat.LastTriggeredAt = timestamppb.New(*s.LastTriggeredAt)
		}
		pbStats[i] = pbStat
	}

	return &pb.GetNotificationStatsResponse{Stats: pbStats}, nil
}

func NewServer(service ClientService, certificates tls.Certificate, dozzleVersion string, notificationHandler NotificationConfigHandler) (*grpc.Server, error) {
	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)

	// Create the TLS configuration
	tlsConfig := &tls.Config{
		Certificates: []tls.Certificate{certificates},
		ClientCAs:    caCertPool,
		ClientAuth:   tls.RequireAndVerifyClientCert, // Require client certificates
	}

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

	grpcServer := grpc.NewServer(
		grpc.Creds(creds),
		grpc.KeepaliveEnforcementPolicy(keepalive.EnforcementPolicy{
			MinTime:             15 * time.Second,
			PermitWithoutStream: true,

View on GitHub (pinned to d9463cbe21)