Billionmail/BillionMail · error

failed to write certificate file: %v

Error message

failed to write certificate file: %v

What it means

updatePostfixConfig in the BillionMail mail_service Certificate module wraps the failure of os.WriteFile when saving the TLS public certificate to <SSL_PATH>/postfix.crt. The underlying OS error (permissions, missing directory, read-only mount, disk full) is embedded in the message. It is thrown while applying a newly issued/updated certificate so Postfix can serve TLS.

Source

Thrown at core/internal/service/mail_service/certificate.go:240

	}

	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)
	}

	// Write certificate and key to files
	certPath := public.AbsPath(filepath.Join(consts.SSL_PATH, "postfix.crt"))
	keyPath := public.AbsPath(filepath.Join(consts.SSL_PATH, "postfix.key"))

	if err := os.WriteFile(certPath, []byte(csrPem), 0755); err != nil {
		return fmt.Errorf("failed to write certificate file: %v", err)
	}

	if err := os.WriteFile(keyPath, []byte(keyPem), 0755); err != nil {
		return fmt.Errorf("failed to write key file: %v", err)
	}

	// Update SSL certificate configuration
	config := string(content)
	config = c.updateConfigLine(config, "smtpd_tls_key_file", keyPath)
	config = c.updateConfigLine(config, "smtpd_tls_cert_file", certPath)

	if err := os.WriteFile(mainCf, []byte(config), 0755); err != nil {
		return fmt.Errorf("failed to write postfix config: %v", err)
	}

	return nil
}

View on GitHub (pinned to fc36c76c05)

Solutions

  1. Ensure the SSL_PATH directory exists and is writable (mkdir -p and chown the path for the process user, or fix the Docker volume mount).
  2. Check disk space (df -h) and inode usage on the SSL volume.
  3. Inspect the wrapped %v error in logs to identify the exact errno (EACCES vs ENOSPC vs EROFS).
  4. Run the container/service with sufficient privileges (root) since Postfix config paths are system locations.
  5. Retry SetSSL after fixing storage; verify postfix.crt was written.

Example fix

// before
if err := os.WriteFile(certPath, []byte(csrPem), 0755); err != nil {
    return fmt.Errorf("failed to write certificate file: %v", err)
}
// after
if err := os.MkdirAll(filepath.Dir(certPath), 0755); err != nil {
    return fmt.Errorf("failed to create ssl directory: %v", err)
}
if err := os.WriteFile(certPath, []byte(csrPem), 0644); err != nil {
    return fmt.Errorf("failed to write certificate file %s: %w", certPath, err)
}
Defensive patterns

Strategy: validation

Validate before calling

certPath := public.AbsPath(filepath.Join(consts.SSL_PATH, "postfix.crt"))
if err := os.MkdirAll(filepath.Dir(certPath), 0755); err != nil {
    return err
}
if fi, err := os.Stat(filepath.Dir(certPath)); err != nil || !fi.IsDir() {
    return fmt.Errorf("ssl dir not usable: %v", err)
}
if err := syscall.Access(filepath.Dir(certPath), unix.W_OK); err != nil {
    return fmt.Errorf("ssl dir not writable: %v", err)
}

Try / catch

cert, err := svc.SetSSL(ctx, domain, certPem, keyPem)
if err != nil && strings.Contains(err.Error(), "failed to write certificate file") {
    log.Printf("check SSL volume mount/permissions/disk space: %v", err)
}

Prevention

When it happens

Trigger: Calling SetSSL or SetPostfixSSL when consts.SSL_PATH does not exist, the process lacks write permission to it, or the filesystem is full/read-only.

Common situations: Docker volume for the SSL path not mounted or mounted read-only; running container as non-root; SSL_PATH directory deleted by cleanup scripts; disk quota exhausted after bulk cert issuance.

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