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
- Verify the source certificate/private key in the database for this renew record — reissue via Apply if empty
- Fix the upstream issuance failure so certificates are stored before renewal is scheduled
- Check renew_logs.error_info for the history of why the cert is empty
- 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
- Only schedule renewals after confirmed successful issuance
- Alert on renew_logs rows with status 2
- Backfill cert/key data before enabling auto-renew for imported records
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
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- failed to save certificate: %v
- invalid certificate
- fail to set ssl: %w
- Failed to generate user private key: {}
- DNS automated resolution failed: SecretId or SecretKey is em
AI-assisted analysis of Billionmail/BillionMail@fc36c76c05 (2026-09-05).
Data as JSON: /api/errors/ac5d433383789981.
Report an issue: GitHub.