Billionmail/BillionMail · error
certificate data is empty
Error message
certificate data is empty
What it means
verifyCertificate is the entry validation for all certificate-install paths (SetSSL, SetSNI, SetPostfixSSL, SetDovecotSSL, SetPostfixVMailCert). It rejects an empty certificate PEM string before any parsing, guarding the rest of the pipeline from nil/empty certificate data.
Source
Thrown at core/internal/service/mail_service/certificate.go:211
// Update Dovecot configuration
if err := c.updateDovecotConfig(csrPem, keyPem); err != nil {
return err
}
// Restart Dovecot service
if err := c.restartDovecot(); err != nil {
return err
}
return nil
}
// verifyCertificate validates certificate data
func (c *Certificate) verifyCertificate(csrPem, keyPem string) error {
// Check if certificate data is empty
if csrPem == "" {
return fmt.Errorf("certificate data is empty")
}
if keyPem == "" {
return fmt.Errorf("private key data is empty")
}
// Validate certificate
cInfo := acme.GetCertInfo(csrPem)
if cInfo.Endtime == 0 {
return fmt.Errorf("certificate is invalid")
}
return nil
}
// updatePostfixConfig updates Postfix configuration with new certificate
func (c *Certificate) updatePostfixConfig(csrPem, keyPem string) error {
mainCf := public.AbsPath(consts.POSTFIX_MAIN_CONF)View on GitHub (pinned to fc36c76c05)
Solutions
- Confirm the certificate record actually contains PEM data before calling the Set* function
- Wait for/retry the ACME issuance and re-fetch the certificate
- Check that the correct parameter order (csrPem, keyPem) is used at the call site
- Add pre-call validation with a clear user-facing message when cert data is missing
Example fix
// before
cert := getCertFromStore(id)
_ = certService.SetSSL(cert.Cert, cert.Key)
// after
cert := getCertFromStore(id)
if cert == nil || cert.Cert == "" {
return fmt.Errorf("no certificate available for id %s; issue it first", id)
}
return certService.SetSSL(cert.Cert, cert.Key) Defensive patterns
Strategy: validation
Validate before calling
func readyToDeploy(certPem, keyPem string) error {
if strings.TrimSpace(certPem) == "" {
return errors.New("certificate PEM is empty; issue the certificate first")
}
return nil
}
// call before:
// if err := readyToDeploy(cert, key); err != nil { return err } Type guard
func hasCertificatePEM(s string) bool {
return strings.Contains(s, "-----BEGIN CERTIFICATE-----")
} Try / catch
if err := certService.SetSSL(csrPem, keyPem); err != nil {
if strings.Contains(err.Error(), "certificate data is empty") {
return fmt.Errorf("certificate for domain not yet issued; run issuance then retry: %w", err)
}
return err
} Prevention
- Only invoke Set* after confirming the ACME order state is 'issued'
- Store cert and key in separate, non-nullable columns and check both on load
- Log the cert record ID when the PEM is empty to speed diagnosis
When it happens
Trigger: Calling any Set* certificate method with csrPem == "", typically because the certificate was fetched from a store/API and returned empty (issue not yet completed, wrong record ID, or DB field null).
Common situations: Requesting SSL deployment before the ACME order finished; passing the key file content into the cert parameter by mistake; database row deleted between listing and applying.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- private key data is empty
- certificate is invalid
- invalid certificate
- Add up to 3 URLs
- supplier name, base URL, and API key are required
AI-assisted analysis of Billionmail/BillionMail@fc36c76c05 (2026-09-05).
Data as JSON: /api/errors/6a7c07be72cfde62.
Report an issue: GitHub.