Billionmail/BillionMail · error

failed to save private key: %v

Error message

failed to save private key: %v

What it means

This error wraps the underlying filesystem failure that occurred while writing the ACME account private key to disk in AcmeCLI.Apply. After saving the certificate succeeds, the private key is written via public.WriteFile; any I/O error (permissions, missing directory, disk full) is wrapped with this message. It aborts the certificate issuance flow, returning empty paths.

Source

Thrown at core/internal/service/acme/cli.go:210

	fmt.Printf("  Valid from: %s\n", certInfo.NotBefore)
	fmt.Printf("  Valid to: %s\n", certInfo.NotAfter)
	fmt.Printf("  Domains: %s\n", strings.Join(certInfo.DNSNames, ", "))

	// Certificate files
	certificatePath := filepath.Join(cli.OutputPath, "certificate.pem")
	privateKeyPath := filepath.Join(cli.OutputPath, "private_key.pem")

	// Save certificate and private key to files
	_, err = public.WriteFile(certificatePath, certificate)

	if err != nil {
		return "", "", fmt.Errorf("failed to save certificate: %v", err)
	}

	_, err = public.WriteFile(privateKeyPath, privateKey)

	if err != nil {
		return "", "", fmt.Errorf("failed to save private key: %v", err)
	}

	fmt.Printf("Certificate saved to: %s\n", certificatePath)
	fmt.Printf("Private key saved to: %s\n", privateKeyPath)

	return certificatePath, privateKeyPath, nil
}

/**
 * @brief Save certificate to database
 * @return error
 */
func (cli *AcmeCLI) SaveToDatabase(accountId int, certificate, privateKey string) (int, error) {
	// Get certificate info
	certInfo := GetCertInfo(certificate)
	if certInfo.Subject == "" {
		return 0, fmt.Errorf("invalid certificate")
	}

View on GitHub (pinned to fc36c76c05)

Solutions

  1. Check the wrapped %v detail to identify the actual filesystem error
  2. Ensure the parent directory of privateKeyPath exists (os.MkdirAll) before calling Apply
  3. Run the process with sufficient permissions or chown the output directory to the service user
  4. Verify the disk is not full and the path is writable (touch a test file in that directory)
  5. Use an absolute, valid path for the private key output

Example fix

// before
_, err = public.WriteFile(privatePath, privateKey)
// after
_ = os.MkdirAll(filepath.Dir(privateKeyPath), 0700)
_, err = public.WriteFile(privateKeyPath, privateKey)
Defensive patterns

Strategy: try-catch

Validate before calling

if _, err := os.Stat(filepath.Dir(privateKeyPath)); err != nil {
    if err := os.MkdirAll(filepath.Dir(privateKeyPath), 0700); err != nil { return err }
}
if f, err := os.OpenFile(privateKeyPath, os.O_CREATE|os.O_WRONLY, 0600); err != nil { return err } else { f.Close() }

Try / catch

certPath, keyPath, err := cli.Apply(...)
if err != nil {
    if strings.HasPrefix(err.Error(), "failed to save private key") {
        log.Printf("key write failed: %v — check path permissions/disk", err)
    }
    return err
}

Prevention

When it happens

Trigger: Calling Apply (directly or via applyCommand) when the target privateKeyPath directory does not exist, the process lacks write permission, the disk is full, or the path is invalid.

Common situations: Running the ACME CLI as a non-root user without write access to the config/cert directory; deploying in a container with a read-only filesystem; a typo in the configured key output path; parent directories never created before Apply runs.

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/3ecb539132109138. Report an issue: GitHub.