{"record":{"id":"be363ef04dc5726e","repo":"Billionmail/BillionMail","slug":"failed-to-save-certificate-file","errorCode":null,"errorMessage":"Failed to save certificate file: {}","messagePattern":"Failed to save certificate file: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/internal/service/acme/acme.go","lineNumber":484,"sourceCode":"\t}\n\n\t// Save certificate files if path is provided\n\tif savePath != \"\" {\n\t\t// Create directory if it doesn't exist\n\t\tif !public.FileExists(savePath) {\n\t\t\terr = os.MkdirAll(savePath, 0750)\n\t\t\tif err != nil {\n\t\t\t\treturn \"\", \"\", errors.New(public.LangCtx(ctx, \"Failed to create directory: {}\", err.Error()))\n\t\t\t}\n\t\t}\n\n\t\t// Save certificate and private key files\n\t\tcertificateFile := filepath.Join(savePath, \"certificate.pem\")\n\t\tprivateKeyFile := filepath.Join(savePath, \"private_key.pem\")\n\n\t\t_, err = public.WriteFile(certificateFile, string(certificates.Certificate))\n\t\tif err != nil {\n\t\t\treturn \"\", \"\", errors.New(public.LangCtx(ctx, \"Failed to save certificate file: {}\", err.Error()))\n\t\t}\n\n\t\t_, err = public.WriteFile(privateKeyFile, string(certificates.PrivateKey))\n\t\tif err != nil {\n\t\t\treturn \"\", \"\", errors.New(public.LangCtx(ctx, \"Failed to save private key file: {}\", err.Error()))\n\t\t}\n\t}\n\n\t// Return certificate\n\treturn string(certificates.Certificate), string(certificates.PrivateKey), nil\n}\n\ntype CertInfo v1.CertInfo\n\n/**\n * @description: Get certificate information\n * @param {string} certificateStr Certificate string\n * @return {CertInfo} Certificate information","sourceCodeStart":466,"sourceCodeEnd":502,"githubUrl":"https://github.com/Billionmail/BillionMail/blob/fc36c76c050c3775c5e899faf7403cf0262d2744/core/internal/service/acme/acme.go#L466-L502","documentation":"Thrown in ApplySSLWithExistingServer when public.WriteFile fails to persist certificate.pem into savePath after a successful issuance. The message embeds the underlying error, typically an I/O permission problem or the directory not existing despite the earlier MkdirAll (e.g. removed concurrently or race with another process).","triggerScenarios":"ApplySSLWithExistingServer reaches public.WriteFile(certificateFile, string(certificates.Certificate)) and the write fails — savePath was deleted between MkdirAll and write, disk full, or permissions deny the process from creating the file.","commonSituations":"Disk quota/full volume on busy mail servers; directory recreated by config management with different ownership between mkdir and write; SELinux/AppArmor blocking writes to the cert directory; running service as non-root while path is root-owned.","solutions":["Read the embedded OS error: fix permission denied / no space left on device accordingly","Ensure the process user owns savePath (chown app-user:app-user <savePath>) or run with sufficient privileges","Check disk space (df -h) and inode availability on the target volume","Confirm no external process (systemd tmpfiles, config sync) deletes/recreates the directory during issuance"],"exampleFix":"// before\nsvc.ApplySSLWithExistingServer(ctx, d, \"rsa2048\", cert, key, \"/root/certs\") // app runs as non-root\n// after\nsvc.ApplySSLWithExistingServer(ctx, d, \"rsa2048\", cert, key, \"/var/lib/billionmail/certs\") // chown app-user","handlingStrategy":"validation","validationCode":"if err := os.MkdirAll(savePath, 0750); err != nil { return err }\nprobe := filepath.Join(savePath, \".write_probe\")\nif err := os.WriteFile(probe, []byte(\"ok\"), 0600); err != nil { return err }\nos.Remove(probe)\nif st, err := os.Statfs(savePath); err == nil && st.Bavail == 0 { return errors.New(\"no space on target volume\") }","typeGuard":null,"tryCatchPattern":"if _, _, err := svc.ApplySSLWithExistingServer(ctx, d, kt, c, k, savePath); err != nil {\n    var pe *fs.PathError\n    if errors.As(err, &pe) && pe.Op == \"open\" {\n        log.Printf(\"cannot write cert to %s: %v\", savePath, pe.Err)\n    }\n    return err\n}","preventionTips":["Check disk space/quotas on the cert volume regularly","Keep savePath stable — avoid external tooling recreating it between writes","Ensure SELinux/AppArmor policy allows writes to the cert directory","Write as the same user the service runs as"],"tags":["filesystem","ssl","certificate","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"}