Billionmail/BillionMail · error

failed to write dovecot config

Error message

failed to write dovecot config: %v

What it means

After updating ssl_cert/ssl_key lines in memory, updateDovecotConfig writes the modified config back to c.DovecotSslConf with os.WriteFile (0755). A failure is wrapped as 'failed to write dovecot config: %v'. At this point the new cert/key files are already on disk, so the config file may not reference them until this succeeds. Thrown by SetSSL and SetDovecotSSL.

Solutions

  1. Make the dovecot conf file writable (chmod/chown, remove chattr +i with chattr -i)
  2. Remount the dovecot config volume read-write
  3. Free disk space
  4. After fixing, re-run SetSSL and restart the dovecot container so it picks up the new ssl_cert/ssl_key paths

Example fix

// before
if err := os.WriteFile(dovecotConf, []byte(config), 0755); err != nil {
    return fmt.Errorf("failed to write dovecot config: %v", err)
}
// after
if err := os.WriteFile(dovecotConf, []byte(config), 0644); err != nil {
    return fmt.Errorf("failed to write dovecot config %s: %w", dovecotConf, err)
}
return c.restartDovecot()
Defensive patterns

Strategy: try-catch

Validate before calling

if info, err := os.Stat(dovecotConf); err != nil || info.IsDir() {
    return fmt.Errorf("dovecot conf missing")
}
if f, err := os.OpenFile(dovecotConf, os.O_WRONLY, 0644); err != nil {
    return fmt.Errorf("dovecot conf not writable: %w", err)
} else {
    f.Close()
}

Try / catch

if err := certSvc.SetSSL(ctx, domain); err != nil {
    if strings.Contains(err.Error(), "failed to write dovecot config") {
        // ensure conf volume is rw and not immutable, then retry
    }
    return err
}

Prevention

When it happens

Trigger: SetSSL/SetDovecotSSL invoked when the dovecot ssl conf file is read-only, owned by another user, on a full disk, or the path became unwritable after the read step.

Common situations: Config volume mounted read-only (common with bind-mounted /etc/dovecot); file immutable bit set (chattr +i); SELinux/AppArmor denying write; disk full after writing certs.

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


AI-assisted analysis of Billionmail/BillionMail@fc36c76c05 (2026-09-05). Data as JSON: /api/errors/2e41d42f7f854631. Report an issue: GitHub.

Appendix: source

Thrown at core/internal/service/mail_service/certificate.go:374

	// Write certificate and key to files
	certPath := public.AbsPath(filepath.Join(consts.SSL_PATH, "dovecot.crt"))
	keyPath := public.AbsPath(filepath.Join(consts.SSL_PATH, "dovecot.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, "ssl_cert", "<"+certPath)
	config = c.updateConfigLine(config, "ssl_key", "<"+keyPath)

	if err := os.WriteFile(dovecotConf, []byte(config), 0755); err != nil {
		return fmt.Errorf("failed to write dovecot config: %v", err)
	}

	return nil
}

// updateDovecotSNIConfig updates Dovecot SNI configuration
func (c *Certificate) updateDovecotSNIConfig(domain, certPem, keyPem string) error {
	// Ensure domain directory exists
	domainDir := filepath.Join(consts.SSL_PATH, domain)
	if err := os.MkdirAll(domainDir, 0755); err != nil {
		return fmt.Errorf("failed to create domain directory: %v", err)
	}

	sniCert := filepath.Join(domainDir, "fullchain.pem")
	sniKey := filepath.Join(domainDir, "privkey.pem")

	// Write certificate and key to files
	if err := os.WriteFile(sniCert, []byte(certPem), 0755); err != nil {

View on GitHub (pinned to fc36c76c05)