Billionmail/BillionMail · error

failed to save certificate: %v

Error message

failed to save certificate: %v

What it means

Apply throws this when public.WriteFile fails to save certificate.pem into cli.OutputPath after successful issuance, wrapping the OS error with %v. Like errors 111-112 but in the AcmeCLI.Apply path (file name derived from first domain under OutputPath).

Source

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

	// Get certificate info
	certInfo := GetCertInfo(certificate)
	fmt.Printf("Certificate issued successfully:\n")
	fmt.Printf("  Subject: %s\n", certInfo.Subject)
	fmt.Printf("  Issuer: %s\n", certInfo.Issuer)
	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
 */

View on GitHub (pinned to fc36c76c05)

Solutions

  1. Create OutputPath beforehand (mkdir -p) and ensure the running user can write to it
  2. Pass an absolute, existing --output-path when invoking applyCommand
  3. Fix the wrapped OS error (permissions, disk space) indicated in the message
  4. Use a dedicated certs directory owned by the service user

Example fix

// before
cli := &AcmeCLI{Email: e, Domains: d, VerifyType: "http", OutputPath: ""} // writes to relative cwd
// after
out := "/var/lib/acme/certs"
os.MkdirAll(out, 0750)
cli := &AcmeCLI{Email: e, Domains: d, VerifyType: "http", OutputPath: out}
Defensive patterns

Strategy: validation

Validate before calling

out := cli.OutputPath
if out == "" { return errors.New("output path must be set") }
if err := os.MkdirAll(out, 0750); err != nil { return err }
if err := unix.Access(out, unix.W_OK); err != nil { return fmt.Errorf("%s not writable: %w", out, err) }

Try / catch

if _, _, err := cli.Apply(ctx); err != nil {
    if strings.Contains(err.Error(), "failed to save certificate") {
        log.Printf("cert write failed for %s: %v; check OutputPath permissions/space", cli.OutputPath, err)
    }
    return err
}

Prevention

When it happens

Trigger: Apply with OutputPath set to a non-existent or unwritable directory, or OutputPath empty so relative write fails; the WriteFile for certificatePath returns an error.

Common situations: Running the CLI from a cwd without write permission while OutputPath was defaulted; OutputPath directory not created before Apply; read-only container filesystem; disk full.

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