Billionmail/BillionMail · error
private key data is empty
Error message
private key data is empty
What it means
Validation guard in verifyCertificate: the keyPem argument (TLS private key PEM) is an empty string, so the certificate/key pair cannot be validated before being installed into Postfix/Dovecot. verifyCertificate rejects it before calling acme.GetCertInfo, since cert data without a key is unusable for TLS.
Source
Thrown at core/internal/service/mail_service/certificate.go:214
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)
content, err := os.ReadFile(mainCf)
if err != nil {
return fmt.Errorf("failed to read postfix config: %v", err)View on GitHub (pinned to fc36c76c05)
Solutions
- Ensure the private key PEM is present and passed as the second argument
- Re-export or regenerate the key pair if the key is truly lost
- Verify field mapping when loading cert/key from the database or API response
- Validate inputs before the call the same way verifyCertificate does
Example fix
// before
if err := c.SetSSL(certPem, ""); err != nil { ... }
// after
if keyPem == "" {
return errors.New("private key PEM is required to deploy the certificate")
}
if err := c.SetSSL(certPem, keyPem); err != nil { ... } Defensive patterns
Strategy: validation
Validate before calling
func readyToDeploy(certPem, keyPem string) error {
if strings.TrimSpace(keyPem) == "" {
return errors.New("private key PEM is empty; regenerate or re-export the key")
}
return nil
} Type guard
func hasPrivateKeyPEM(s string) bool {
return strings.Contains(s, "-----BEGIN ") && strings.Contains(s, "PRIVATE KEY-----")
} Try / catch
if err := certService.SetSSL(csrPem, keyPem); err != nil {
if strings.Contains(err.Error(), "private key data is empty") {
return fmt.Errorf("missing key for certificate; re-export key pair: %w", err)
}
return err
} Prevention
- Always persist the key alongside the cert at issuance time
- Back up the key pair before migrations
- Assert key PEM presence in integration tests for all Set* paths
When it happens
Trigger: Calling SetSSL/SetSNI/SetPostfixSSL/SetDovecotSSL/SetPostfixVMailCert with keyPem == "" — key not yet generated, wrong field read from the store, or key lost during export.
Common situations: ACME flow that stored only the cert; migrating certificates and losing the .key file; copy/paste omitting the key PEM block.
Related errors
- certificate data is empty
- certificate is invalid
- invalid certificate
- required column 'email' not found
- fail to set ssl: %w
AI-assisted analysis of Billionmail/BillionMail@fc36c76c05 (2026-09-05).
Data as JSON: /api/errors/915ab320f7839b20.
Report an issue: GitHub.