Billionmail/BillionMail · error

Failed to save private key file: {}

Error message

Failed to save private key file: {}

What it means

Thrown in ApplySSLWithExistingServer when public.WriteFile fails to persist private_key.pem into savePath, after certificate.pem was written successfully. It wraps the raw OS error so the actual cause (permissions, disk full, path issues) is in the message.

Source

Thrown at core/internal/service/acme/acme.go:489

		if !public.FileExists(savePath) {
			err = os.MkdirAll(savePath, 0750)
			if err != nil {
				return "", "", errors.New(public.LangCtx(ctx, "Failed to create directory: {}", err.Error()))
			}
		}

		// Save certificate and private key files
		certificateFile := filepath.Join(savePath, "certificate.pem")
		privateKeyFile := filepath.Join(savePath, "private_key.pem")

		_, err = public.WriteFile(certificateFile, string(certificates.Certificate))
		if err != nil {
			return "", "", errors.New(public.LangCtx(ctx, "Failed to save certificate file: {}", err.Error()))
		}

		_, err = public.WriteFile(privateKeyFile, string(certificates.PrivateKey))
		if err != nil {
			return "", "", errors.New(public.LangCtx(ctx, "Failed to save private key file: {}", err.Error()))
		}
	}

	// Return certificate
	return string(certificates.Certificate), string(certificates.PrivateKey), nil
}

type CertInfo v1.CertInfo

/**
 * @description: Get certificate information
 * @param {string} certificateStr Certificate string
 * @return {CertInfo} Certificate information
 */
func GetCertInfo(certificateStr string) CertInfo {
	certInfo := CertInfo{}
	block, _ := pem.Decode([]byte(certificateStr))
	if block == nil {

View on GitHub (pinned to fc36c76c05)

Solutions

  1. Read the embedded OS error and fix the filesystem condition (permissions, disk space)
  2. Verify the process can create files in savePath (touch a test file as the service user)
  3. Free disk space / raise quota if 'no space left on device' is reported
  4. Ensure nothing removes or chmods savePath between the certificate and key writes

Example fix

// before
// cert dir writable only by root; service runs as mailuser
_, _, err = svc.ApplySSLWithExistingServer(ctx, d, keyType, c, k, "/etc/ssl/private")
// after
os.Chown("/etc/ssl/private/mailuser", uid, gid) // or use a dedicated dir owned by the service user
_, _, err = svc.ApplySSLWithExistingServer(ctx, d, keyType, c, k, "/etc/ssl/private/mailuser")
Defensive patterns

Strategy: validation

Validate before calling

if err := os.MkdirAll(savePath, 0750); err != nil { return err }
if err := unix.Access(savePath, unix.W_OK); err != nil { return fmt.Errorf("%s not writable: %w", savePath, err) }
// ensure free space for both files
if st, err := os.Statfs(savePath); err == nil && st.Bavail*uint64(st.Bsize) < 64*1024 { return errors.New("insufficient space for key material") }

Try / catch

if _, _, err := svc.ApplySSLWithExistingServer(ctx, d, kt, c, k, savePath); err != nil {
    if strings.Contains(err.Error(), "private key") {
        log.Printf("key persistence failed for %s: %v", savePath, err)
        // cert.pem may exist; clean up partial state before retry
        os.Remove(filepath.Join(savePath, "certificate.pem"))
    }
    return err
}

Prevention

When it happens

Trigger: ApplySSLWithExistingServer calls public.WriteFile(privateKeyFile, string(certificates.PrivateKey)) and the OS write fails — typically same causes as the certificate write: permissions, full disk, directory removed mid-operation.

Common situations: Save directory ownership changed between writes by an external sync; quota exceeded mid-write; disk filled up by the preceding certificate write in a constrained container.

Understand the failure class

Background: "Permission denied" / "Failed to write" file errors: why a library can't write its files to disk (EACCES, EPERM, ENOSPC) and how to fix them — this error's family across 43 libraries.

Related errors


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