Billionmail/BillionMail · error
failed to write key file
Error message
failed to write key file: %v
What it means
Same write path as the certificate error but for the private key file <SSL_PATH>/postfix.key. updatePostfixConfig wraps os.WriteFile failure for the TLS private key with the OS error embedded. Without this key Postfix cannot complete the TLS handshake, so SSL setup aborts.
Solutions
- Verify SSL_PATH exists and the process user can write to it (ls -ld, chown/chmod as needed).
- Check disk space and Docker volume mounts for the SSL path.
- Remove/rename any directory incorrectly sitting at the postfix.key path.
- Read the embedded OS error to distinguish permission vs space vs read-only causes.
- Re-run SetSSL and confirm postfix.key exists with correct contents.
Example fix
// before
if err := os.WriteFile(keyPath, []byte(keyPem), 0755); err != nil {
return fmt.Errorf("failed to write key file: %v", err)
}
// after
if err := os.WriteFile(keyPath, []byte(keyPem), 0600); err != nil {
return fmt.Errorf("failed to write key file %s: %w", keyPath, err)
} Defensive patterns
Strategy: validation
Validate before calling
keyPath := public.AbsPath(filepath.Join(consts.SSL_PATH, "postfix.key"))
if st, err := os.Stat(filepath.Dir(keyPath)); err != nil || !st.IsDir() {
return fmt.Errorf("ssl dir missing")
}
if err := unix.Access(filepath.Dir(keyPath), unix.W_OK); err != nil {
return fmt.Errorf("ssl dir not writable: %v", err)
} Try / catch
err := svc.SetSSL(ctx, domain, certPem, keyPem)
if err != nil && strings.Contains(err.Error(), "failed to write key file") {
log.Printf("verify SSL dir ownership and free space before retrying: %v", err)
} Prevention
- Check that postfix.key path is not shadowed by a directory after manual restores.
- Set key file mode to 0600 to satisfy strict permission checks by mail daemons.
- Ensure adequate free disk space before bulk SSL operations.
- Avoid running the service as a different UID than the one that created existing keys.
When it happens
Trigger: SetSSL or SetPostfixSSL invoked when SSL_PATH is missing/unwritable, the key write hits ENOSPC/EACCES/EROFS, or the key file path collides with a directory.
Common situations: Read-only or unmounted SSL volume in Docker; key file previously created by root and now written by a lower-privileged process; disk full; path exists as a directory.
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
- Failed to save private key file
- failed to write postfix config
- failed to create backup
- failed to create domain directory
AI-assisted analysis of Billionmail/BillionMail@fc36c76c05 (2026-09-05).
Data as JSON: /api/errors/78e3d93772cf984f.
Report an issue: GitHub.
Appendix: source
Thrown at core/internal/service/mail_service/certificate.go:244
// 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
}
// 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 {View on GitHub (pinned to fc36c76c05)