Billionmail/BillionMail · error

failed to create domain directory: %v

Error message

failed to create domain directory: %v

What it means

updatePostfixVMailConfig creates a per-domain directory under SSL_PATH (os.MkdirAll) before writing fullchain.pem/privkey.pem. If MkdirAll fails, the OS error is wrapped with this message. Per-domain SNI certs cannot be stored without this directory.

Source

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

	// Update Postfix virtual mail configuration
	if err := c.updatePostfixVMailConfig(domain, csrPem, keyPem); err != nil {
		return err
	}

	// Restart Postfix service
	if err := c.restartPostfix(); err != nil {
		return err
	}

	return nil
}

// updatePostfixVMailConfig updates Postfix virtual mail configuration
func (c *Certificate) updatePostfixVMailConfig(domain, csrPem, 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)
	}

	vmailCert := filepath.Join(domainDir, "fullchain.pem")
	vmailKey := filepath.Join(domainDir, "privkey.pem")

	// Write certificate and key to files
	if err := os.WriteFile(vmailCert, []byte(csrPem), 0755); err != nil {
		return fmt.Errorf("failed to write certificate file: %v", err)
	}

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

	// Create SNI mapping table
	if err := c.updatePostfixSNIMap(public.FormatMX(domain), vmailCert, vmailKey); err != nil {
		return fmt.Errorf("failed to update SNI map: %v", err)
	}

View on GitHub (pinned to fc36c76c05)

Solutions

  1. Sanitize/validate the domain before using it as a directory name (reject '/', '..', empty).
  2. Ensure SSL_PATH exists and is writable by the process user.
  3. Check the wrapped OS error for the exact errno and fix the mount/permission accordingly.
  4. Verify the Docker volume backing SSL_PATH is present and writable.
  5. Retry SetSNI and confirm the domain directory now exists.

Example fix

// before
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)
}
// after
if strings.ContainsAny(domain, "/\\") || strings.Contains(domain, "..") {
    return fmt.Errorf("invalid domain for ssl directory: %q", domain)
}
domainDir := filepath.Join(consts.SSL_PATH, domain)
if err := os.MkdirAll(domainDir, 0755); err != nil {
    return fmt.Errorf("failed to create domain directory %s: %w", domainDir, err)
}
Defensive patterns

Strategy: validation

Validate before calling

func validDomainForPath(d string) bool {
    if d == "" || strings.ContainsAny(d, "/\\") || strings.Contains(d, "..") {
        return false
    }
    return true
}
// before calling:
if !validDomainForPath(domain) { return fmt.Errorf("invalid domain: %q", domain) }
if err := os.MkdirAll(consts.SSL_PATH, 0755); err != nil { return err }

Try / catch

err := svc.SetSNI(ctx, domain)
if err != nil && strings.Contains(err.Error(), "failed to create domain directory") {
    log.Printf("validate domain characters and SSL_PATH writability: %v", err)
}

Prevention

When it happens

Trigger: SetSNI or SetPostfixVMailCert called with a domain whose directory path cannot be created — SSL_PATH missing/unwritable, invalid characters in domain used as path segment, or read-only filesystem.

Common situations: Wildcard names or user-supplied domains containing '/' or '..' causing bad path segments; SSL volume unmounted; parent directory owned by root; read-only container FS.

Related errors


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