{"record":{"id":"78e3d93772cf984f","repo":"Billionmail/BillionMail","slug":"failed-to-write-key-file-v","errorCode":null,"errorMessage":"failed to write key file: %v","messagePattern":"failed to write key file: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/internal/service/mail_service/certificate.go","lineNumber":244,"sourceCode":"\n// updatePostfixConfig updates Postfix configuration with new certificate\nfunc (c *Certificate) updatePostfixConfig(csrPem, keyPem string) error {\n\tmainCf := public.AbsPath(consts.POSTFIX_MAIN_CONF)\n\tcontent, err := os.ReadFile(mainCf)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to read postfix config: %v\", err)\n\t}\n\n\t// Write certificate and key to files\n\tcertPath := public.AbsPath(filepath.Join(consts.SSL_PATH, \"postfix.crt\"))\n\tkeyPath := public.AbsPath(filepath.Join(consts.SSL_PATH, \"postfix.key\"))\n\n\tif err := os.WriteFile(certPath, []byte(csrPem), 0755); err != nil {\n\t\treturn fmt.Errorf(\"failed to write certificate file: %v\", err)\n\t}\n\n\tif err := os.WriteFile(keyPath, []byte(keyPem), 0755); err != nil {\n\t\treturn fmt.Errorf(\"failed to write key file: %v\", err)\n\t}\n\n\t// Update SSL certificate configuration\n\tconfig := string(content)\n\tconfig = c.updateConfigLine(config, \"smtpd_tls_key_file\", keyPath)\n\tconfig = c.updateConfigLine(config, \"smtpd_tls_cert_file\", certPath)\n\n\tif err := os.WriteFile(mainCf, []byte(config), 0755); err != nil {\n\t\treturn fmt.Errorf(\"failed to write postfix config: %v\", err)\n\t}\n\n\treturn nil\n}\n\n// SetPostfixVMailCert configures SSL certificate for Postfix virtual mail\nfunc (c *Certificate) SetPostfixVMailCert(domain, csrPem, keyPem string) error {\n\t// Validate certificate data\n\tif err := c.verifyCertificate(csrPem, keyPem); err != nil {","sourceCodeStart":226,"sourceCodeEnd":262,"githubUrl":"https://github.com/Billionmail/BillionMail/blob/fc36c76c050c3775c5e899faf7403cf0262d2744/core/internal/service/mail_service/certificate.go#L226-L262","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nif err := os.WriteFile(keyPath, []byte(keyPem), 0755); err != nil {\n    return fmt.Errorf(\"failed to write key file: %v\", err)\n}\n// after\nif err := os.WriteFile(keyPath, []byte(keyPem), 0600); err != nil {\n    return fmt.Errorf(\"failed to write key file %s: %w\", keyPath, err)\n}","handlingStrategy":"validation","validationCode":"keyPath := public.AbsPath(filepath.Join(consts.SSL_PATH, \"postfix.key\"))\nif st, err := os.Stat(filepath.Dir(keyPath)); err != nil || !st.IsDir() {\n    return fmt.Errorf(\"ssl dir missing\")\n}\nif err := unix.Access(filepath.Dir(keyPath), unix.W_OK); err != nil {\n    return fmt.Errorf(\"ssl dir not writable: %v\", err)\n}","typeGuard":null,"tryCatchPattern":"err := svc.SetSSL(ctx, domain, certPem, keyPem)\nif err != nil && strings.Contains(err.Error(), \"failed to write key file\") {\n    log.Printf(\"verify SSL dir ownership and free space before retrying: %v\", err)\n}","preventionTips":["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."],"tags":["filesystem","ssl-certificate","private-key","postfix","file-write"],"backgroundTag":"file-write-permission-denied","analyzedSha":"fc36c76c050c3775c5e899faf7403cf0262d2744","analyzedAt":"2026-09-05T21:28:54.019Z","contentChangedAt":"2026-09-05T21:28:54.019Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}