Billionmail/BillionMail · error

failed to write dovecot config: %v

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.

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)

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.