{"record":{"id":"850d726758474a18","repo":"XTLS/Xray-core","slug":"trojan-fallbacks-path-must-be-empty-or-start-wi","errorCode":null,"errorMessage":"Trojan fallbacks: \"path\" must be empty or start with \"/\"","messagePattern":"Trojan fallbacks: \"path\" must be empty or start with \"/\"","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"infra/conf/trojan.go","lineNumber":175,"sourceCode":"\t\t\t_ = json.Unmarshal(fb.Dest, &s)\n\t\t}\n\t\tconfig.Fallbacks = append(config.Fallbacks, &trojan.Fallback{\n\t\t\tName: fb.Name,\n\t\t\tAlpn: fb.Alpn,\n\t\t\tPath: fb.Path,\n\t\t\tType: fb.Type,\n\t\t\tDest: s,\n\t\t\tXver: fb.Xver,\n\t\t})\n\t}\n\tfor _, fb := range config.Fallbacks {\n\t\t/*\n\t\t\tif fb.Alpn == \"h2\" && fb.Path != \"\" {\n\t\t\t\treturn nil, errors.New(`Trojan fallbacks: \"alpn\":\"h2\" doesn't support \"path\"`)\n\t\t\t}\n\t\t*/\n\t\tif fb.Path != \"\" && fb.Path[0] != '/' {\n\t\t\treturn nil, errors.New(`Trojan fallbacks: \"path\" must be empty or start with \"/\"`)\n\t\t}\n\t\tif fb.Type == \"\" && fb.Dest != \"\" {\n\t\t\tif fb.Dest == \"serve-ws-none\" {\n\t\t\t\tfb.Type = \"serve\"\n\t\t\t} else if filepath.IsAbs(fb.Dest) || fb.Dest[0] == '@' {\n\t\t\t\tfb.Type = \"unix\"\n\t\t\t\tif strings.HasPrefix(fb.Dest, \"@@\") && (runtime.GOOS == \"linux\" || runtime.GOOS == \"android\") {\n\t\t\t\t\tfullAddr := make([]byte, len(syscall.RawSockaddrUnix{}.Path)) // may need padding to work with haproxy\n\t\t\t\t\tcopy(fullAddr, fb.Dest[1:])\n\t\t\t\t\tfb.Dest = string(fullAddr)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif _, err := strconv.Atoi(fb.Dest); err == nil {\n\t\t\t\t\tfb.Dest = \"localhost:\" + fb.Dest\n\t\t\t\t}\n\t\t\t\tif _, _, err := net.SplitHostPort(fb.Dest); err == nil {\n\t\t\t\t\tfb.Type = \"tcp\"\n\t\t\t\t}","sourceCodeStart":157,"sourceCodeEnd":193,"githubUrl":"https://github.com/XTLS/Xray-core/blob/7d214f8b094f75322fa3990f8aadad1c912f24f5/infra/conf/trojan.go#L157-L193","documentation":"Thrown while validating Trojan inbound fallbacks: any non-empty 'path' must start with '/' because it is matched as an HTTP path prefix. A path like \"ws\" or \"api/v1\" is rejected.","triggerScenarios":"A fallback object { \"dest\": 80, \"path\": \"ws\" } — missing the leading slash — in a Trojan inbound's \"fallbacks\" array.","commonSituations":"Adapting an nginx/caddy location string that omitted the slash, or copying a WebSocket path from a client share link (which often shows it without '/').","solutions":["Prefix the path with '/': \"path\": \"/ws\"","Omit \"path\" entirely if the fallback should catch all unmatched requests"],"exampleFix":"// before\n{ \"dest\": 80, \"path\": \"ws\" }\n// after\n{ \"dest\": 80, \"path\": \"/ws\" }","handlingStrategy":"validation","validationCode":"for _, fb := range fallbacks {\n    p := gjson.Get(fb.Raw, \"path\").String()\n    if p != \"\" && !strings.HasPrefix(p, \"/\") {\n        return fmt.Errorf(\"fallback path %q must start with '/'\", p)\n    }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Normalize all HTTP paths with a leading slash in templates","Mirror the exact path between server fallbacks and client wsPath"],"tags":["config","trojan","fallback","http"],"backgroundTag":null,"analyzedSha":"7d214f8b094f75322fa3990f8aadad1c912f24f5","analyzedAt":"2026-08-15T14:26:24.325Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}