Billionmail/BillionMail · error
Failed to save certificate file: {}
Error message
Failed to save certificate file: {} What it means
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).
Source
Thrown at core/internal/service/acme/acme.go:484
}
// Save certificate files if path is provided
if savePath != "" {
// Create directory if it doesn't exist
if !public.FileExists(savePath) {
err = os.MkdirAll(savePath, 0750)
if err != nil {
return "", "", errors.New(public.LangCtx(ctx, "Failed to create directory: {}", err.Error()))
}
}
// Save certificate and private key files
certificateFile := filepath.Join(savePath, "certificate.pem")
privateKeyFile := filepath.Join(savePath, "private_key.pem")
_, err = public.WriteFile(certificateFile, string(certificates.Certificate))
if err != nil {
return "", "", errors.New(public.LangCtx(ctx, "Failed to save certificate file: {}", err.Error()))
}
_, err = public.WriteFile(privateKeyFile, string(certificates.PrivateKey))
if err != nil {
return "", "", errors.New(public.LangCtx(ctx, "Failed to save private key file: {}", err.Error()))
}
}
// Return certificate
return string(certificates.Certificate), string(certificates.PrivateKey), nil
}
type CertInfo v1.CertInfo
/**
* @description: Get certificate information
* @param {string} certificateStr Certificate string
* @return {CertInfo} Certificate informationView on GitHub (pinned to fc36c76c05)
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
Example fix
// before svc.ApplySSLWithExistingServer(ctx, d, "rsa2048", cert, key, "/root/certs") // app runs as non-root // after svc.ApplySSLWithExistingServer(ctx, d, "rsa2048", cert, key, "/var/lib/billionmail/certs") // chown app-user
Defensive patterns
Strategy: validation
Validate before calling
if err := os.MkdirAll(savePath, 0750); err != nil { return err }
probe := filepath.Join(savePath, ".write_probe")
if err := os.WriteFile(probe, []byte("ok"), 0600); err != nil { return err }
os.Remove(probe)
if st, err := os.Statfs(savePath); err == nil && st.Bavail == 0 { return errors.New("no space on target volume") } Try / catch
if _, _, err := svc.ApplySSLWithExistingServer(ctx, d, kt, c, k, savePath); err != nil {
var pe *fs.PathError
if errors.As(err, &pe) && pe.Op == "open" {
log.Printf("cannot write cert to %s: %v", savePath, pe.Err)
}
return err
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
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.
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- Failed to create directory: {}
- Failed to save private key file: {}
- failed to save certificate: %v
- fail to set ssl: %w
- failed to save private key: %v
AI-assisted analysis of Billionmail/BillionMail@fc36c76c05 (2026-09-05).
Data as JSON: /api/errors/be363ef04dc5726e.
Report an issue: GitHub.