Billionmail/BillionMail · error
failed to write postfix config: %v
Error message
failed to write postfix config: %v
What it means
After updating the smtpd_tls_key_file / smtpd_tls_cert_file lines, updatePostfixConfig fails to persist main.cf via os.WriteFile and wraps the OS error. The certificate files may already be written, leaving main.cf pointing at old cert paths until retried.
Source
Thrown at core/internal/service/mail_service/certificate.go:253
// 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
}
// SetPostfixVMailCert configures SSL certificate for Postfix virtual mail
func (c *Certificate) SetPostfixVMailCert(domain, csrPem, keyPem string) error {
// Validate certificate data
if err := c.verifyCertificate(csrPem, keyPem); err != nil {
return err
}
// Update Postfix Master configuration
if err := c.SetPostfixMasterSSL(); err != nil {
return err
}
// Update Postfix virtual mail configurationView on GitHub (pinned to fc36c76c05)
Solutions
- Check that the resolved main.cf path exists and is writable by the service user (chown/chmod or fix the volume).
- Confirm the Postfix configuration volume is not mounted read-only in Docker Compose.
- Free disk space if ENOSPC is the wrapped error.
- Read the embedded %v error to pinpoint the errno before changing anything.
- Retry SetSSL; then reload/restart Postfix so main.cf changes take effect.
Example fix
// before
if err := os.WriteFile(mainCf, []byte(config), 0755); err != nil {
return fmt.Errorf("failed to write postfix config: %v", err)
}
// after
if err := os.WriteFile(mainCf, []byte(config), 0644); err != nil {
return fmt.Errorf("failed to write postfix config %s: %w", mainCf, err)
} Defensive patterns
Strategy: validation
Validate before calling
mainCf := public.AbsPath(filepath.Join(consts.POSTFIX_CONF_PATH, "main.cf"))
if _, err := os.Stat(mainCf); err != nil {
return fmt.Errorf("main.cf missing: %v", err)
}
if err := unix.Access(mainCf, unix.W_OK); err != nil {
return fmt.Errorf("main.cf not writable: %v", err)
} Try / catch
err := svc.SetPostfixSSL(ctx, domain)
if err != nil && strings.Contains(err.Error(), "failed to write postfix config") {
log.Printf("postfix main.cf unwritable — check volume mounts and ownership: %v", err)
} Prevention
- Mount Postfix config dir as writable volume in compose (no :ro).
- Back up main.cf before SSL updates so a partial write can be rolled back.
- Keep main.cf owned by the service user or grant group write.
- Watch for host-level read-only remounts after filesystem errors.
When it happens
Trigger: SetSSL or SetPostfixSSL runs while the Postfix main.cf path is missing, read-only, permission-denied, or the disk is full — typically a container mount or ownership problem.
Common situations: Postfix conf volume mounted read-only; main.cf owned by root while service runs unprivileged; container filesystem in read-only mode; out-of-space host.
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
- failed to write certificate file: %v
- failed to write key file: %v
- Failed to save certificate file: {}
- Failed to save private key file: {}
- failed to save certificate: %v
AI-assisted analysis of Billionmail/BillionMail@fc36c76c05 (2026-09-05).
Data as JSON: /api/errors/48cc196ac75f8e11.
Report an issue: GitHub.