XTLS/Xray-core · error

VLESS fallbacks: "path" must be empty or start with "/"

Error message

VLESS fallbacks: "path" must be empty or start with "/"

What it means

Thrown by VLessInboundConfig.Build() when validating fallbacks for a VLESS inbound. Each fallback's optional "path" field must either be empty or begin with '/', because the path is matched against the URL path of the incoming HTTP request, which always starts with '/'. Any other value (e.g. "ws", "path=/ws") cannot match and is rejected at config-build time.

Source

Thrown at infra/conf/vless.go:185

			_ = json.Unmarshal(fb.Dest, &s)
		}
		config.Fallbacks = append(config.Fallbacks, &inbound.Fallback{
			Name: fb.Name,
			Alpn: fb.Alpn,
			Path: fb.Path,
			Type: fb.Type,
			Dest: s,
			Xver: fb.Xver,
		})
	}
	for _, fb := range config.Fallbacks {
		/*
			if fb.Alpn == "h2" && fb.Path != "" {
				return nil, errors.New(`VLESS fallbacks: "alpn":"h2" doesn't support "path"`)
			}
		*/
		if fb.Path != "" && fb.Path[0] != '/' {
			return nil, errors.New(`VLESS fallbacks: "path" must be empty or start with "/"`)
		}
		if fb.Type == "" && fb.Dest != "" {
			if fb.Dest == "serve-ws-none" {
				fb.Type = "serve"
			} else if filepath.IsAbs(fb.Dest) || fb.Dest[0] == '@' {
				fb.Type = "unix"
				if strings.HasPrefix(fb.Dest, "@@") && (runtime.GOOS == "linux" || runtime.GOOS == "android") {
					fullAddr := make([]byte, len(syscall.RawSockaddrUnix{}.Path)) // may need padding to work with haproxy
					copy(fullAddr, fb.Dest[1:])
					fb.Dest = string(fullAddr)
				}
			} else {
				if _, err := strconv.Atoi(fb.Dest); err == nil {
					fb.Dest = "localhost:" + fb.Dest
				}
				if _, _, err := net.SplitHostPort(fb.Dest); err == nil {
					fb.Type = "tcp"
				}

View on GitHub (pinned to 7d214f8b09)

Solutions

  1. Change the fallback path to start with '/', e.g. "path":"/ws"
  2. Remove "path" entirely if the fallback should catch all paths
  3. Double-check you are editing the fallbacks array, not the streamSettings wsSettings

Example fix

// before
"fallbacks": [{ "dest": 80, "path": "ws" }]
// after
"fallbacks": [{ "dest": 80, "path": "/ws" }]
Defensive patterns

Strategy: validation

Validate before calling

func validateFallbackPaths(cfg map[string]any) error {
	inbounds, _ := cfg["inbounds"].([]any)
	for _, ib := range inbounds {
		m, _ := ib.(map[string]any)
		settings, _ := m["settings"].(map[string]any)
		fbs, _ := settings["fallbacks"].([]any)
		for _, f := range fbs {
			fb, _ := f.(map[string]any)
			if p, ok := fb["path"].(string); ok && p != "" && !strings.HasPrefix(p, "/") {
				return fmt.Errorf("fallback path %q must be empty or start with /", p)
			}
		}
	}
	return nil
}

Type guard

func validFallbackPath(p any) bool {
	s, ok := p.(string)
	return !ok || s == "" || strings.HasPrefix(s, "/")
}

Prevention

When it happens

Trigger: Config with "fallbacks":[{"dest":"80","path":"ws"}] — path set without a leading slash. Also happens when users copy a WebSocket "path" value from a transport settings block (where no slash is required) into the fallback object.

Common situations: Migrating a config from websocket transport to fallbacks; mixing up transport "path" and fallback "path" semantics; typos like "path":"//ws" or "path":"=" prefix from proxy-share link generators.

Related errors


AI-assisted analysis of XTLS/Xray-core@7d214f8b09 (2026-08-15). Data as JSON: /api/errors/4dd20af6f9e47133. Report an issue: GitHub.