Billionmail/BillionMail · error

let's encrypt certificate is empty

Error message

let's encrypt certificate is empty

What it means

StartRenew aborts a certificate renewal when either the stored certificate or private key is empty, marking the renew_log with status 2 and this error_info, logging it, and returning the error. Renewal cannot proceed without both artifacts, so the routine fails fast before contacting Let's Encrypt-adjacent renewal logic.

Source

Thrown at core/internal/service/acme/renew.go:83

	})

	// Try to apply for certificate
	ctx := gctx.New()
	certificate, privateKey, err := ApplySSLWithExistingServer(ctx, domains, email, vtype, dnsProvider, dnsProviderToken, certPath)
	progress := GetAcmeLogBody(ctx)
	if err != nil {
		// Update renewal log
		public.M("renew_logs").Where("renew_id = ?", renewId).Update(g.Map{"status": 2, "progress": progress, "error_info": err.Error()})
		g.Log().Error(context.Background(), "Renew certificate failed: ", err.Error())
		return err
	}

	// Certificate is empty
	if certificate == "" || privateKey == "" {
		// Update renewal log
		public.M("renew_logs").Where("renew_id = ?", renewId).Update(g.Map{"status": 2, "progress": progress, "error_info": "let's encrypt certificate is empty"})
		g.Log().Error(context.Background(), "Renew certificate failed: let's encrypt certificate is empty")
		return errors.New("let's encrypt certificate is empty")
	}

	// Update renewal log
	public.M("renew_logs").Where("renew_id = ?", renewId).Update(g.Map{"status": 1, "progress": progress, "error_info": ""})
	g.Log().Info(context.Background(), "Renew certificate success for domains: ", domains)

	dnsNames := "[]"
	status := 1
	certInfo := GetCertInfo(certificate)
	notAfter := certInfo.NotAfter
	notBefore := certInfo.NotBefore
	subject := certInfo.Subject
	endTime := certInfo.Endtime
	dnsNamesBytes, err := json.Marshal(domains)
	if err == nil {
		dnsNames = string(dnsNamesBytes)
	}

View on GitHub (pinned to fc36c76c05)

Solutions

  1. Verify the source certificate/private key in the database for this renew record — reissue via Apply if empty
  2. Fix the upstream issuance failure so certificates are stored before renewal is scheduled
  3. Check renew_logs.error_info for the history of why the cert is empty
  4. Delete/repair the broken renew record and create a fresh one

Example fix

// before
err := acme.StartRenew(renewId)
// after
cert, key := loadCertAndKey(renewId)
if cert == "" || key == "" {
    return errors.New("re-run issuance for this domain before renewing")
}
err := acme.StartRenew(renewId)
Defensive patterns

Strategy: validation

Validate before calling

cert, key := store.GetCertificateAndKey(renewId)
if cert == "" || key == "" {
    return fmt.Errorf("renew record %d has empty cert/key; reissue first", renewId)
}

Try / catch

if err := acme.StartRenew(renewId); err != nil {
    if err.Error() == "let's encrypt certificate is empty" {
        scheduleReissue(renewId) // full Apply instead of renew
    }
    return err
}

Prevention

When it happens

Trigger: Calling StartRenew (via renewCommand or Renew) for a record whose certificate or privateKey column/string is empty — e.g., issuance never completed, a previous Apply/SaveToDatabase failure left blank values, or the DB row was created without cert data.

Common situations: Renewal scheduled for a domain whose initial issuance failed; certificate record wiped by a failed migration or manual DB edit; code path saved the DNS/record info but not the certificate after an ACME error.

Understand the failure class

Related errors


AI-assisted analysis of Billionmail/BillionMail@fc36c76c05 (2026-09-05). Data as JSON: /api/errors/ac5d433383789981. Report an issue: GitHub.