{"record":{"id":"84a914dafc3842f2","repo":"XTLS/Xray-core","slug":"headers-can-t-contain-host","errorCode":null,"errorMessage":"\"headers\" can't contain \"host\"","messagePattern":"\"headers\" can't contain \"host\"","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"infra/conf/transport_method.go","lineNumber":330,"sourceCode":"\t\t}\n\t\textra.Host = c.Host\n\t\textra.Path = c.Path\n\t\textra.Mode = c.Mode\n\t\tc = &extra\n\t}\n\n\tswitch c.Mode {\n\tcase \"\":\n\t\tc.Mode = \"auto\"\n\tcase \"auto\", \"packet-up\", \"stream-up\", \"stream-one\":\n\tdefault:\n\t\treturn nil, errors.New(\"unsupported mode: \" + c.Mode)\n\t}\n\n\t// Priority (client): host > serverName > address\n\tfor k := range c.Headers {\n\t\tif strings.ToLower(k) == \"host\" {\n\t\t\treturn nil, errors.New(`\"headers\" can't contain \"host\"`)\n\t\t}\n\t}\n\n\tif c.XPaddingBytes != (Int32Range{}) && (c.XPaddingBytes.From <= 0 || c.XPaddingBytes.To <= 0) {\n\t\treturn nil, errors.New(\"xPaddingBytes cannot be disabled\")\n\t}\n\n\tif c.XPaddingKey == \"\" {\n\t\tc.XPaddingKey = \"x_padding\"\n\t}\n\n\tif c.XPaddingHeader == \"\" {\n\t\tc.XPaddingHeader = \"X-Padding\"\n\t}\n\n\tswitch c.XPaddingPlacement {\n\tcase \"\":\n\t\tc.XPaddingPlacement = \"queryInHeader\"","sourceCodeStart":312,"sourceCodeEnd":348,"githubUrl":"https://github.com/XTLS/Xray-core/blob/7d214f8b094f75322fa3990f8aadad1c912f24f5/infra/conf/transport_method.go#L312-L348","documentation":"SplitHTTPConfig.Build() iterates the \"headers\" map and rejects any key equal (case-insensitively) to \"host\" at transport_method.go:328-332. The HTTP Host header is governed by the dedicated \"host\" field (priority on the client: host > serverName > address, per the comment), so duplicating it inside \"headers\" is ambiguous and forbidden.","triggerScenarios":"splithttp transportSettings with \"headers\": {\"Host\": \"example.com\"} or {\"host\": \"...\"} — any casing passes strings.ToLower. Triggers regardless of mode, before padding checks.","commonSituations":"Copying a full browser header set (including Host) into headers for camouflage; migrating from websocket/http transport configs where a Host header was the normal way to set SNI/authority; merging example configs that predate this restriction.","solutions":["Remove the Host entry from \"headers\" and use the top-level \"host\" field instead","Keep all other camouflage headers (User-Agent, Accept, etc.) in \"headers\" as-is","Search case-insensitively when auditing — \"HOST\", \"Host\", \"hOsT\" all trigger it"],"exampleFix":"// before\n\"host\": \"dl.example.com\", \"headers\": { \"Host\": \"dl.example.com\", \"User-Agent\": \"curl/8\" }\n// after\n\"host\": \"dl.example.com\", \"headers\": { \"User-Agent\": \"curl/8\" }","handlingStrategy":"validation","validationCode":"// Go: reject Host keys before Build()\nfor k := range cfg.Headers {\n\tif strings.EqualFold(k, \"host\") {\n\t\treturn errors.New(\"move Host to the top-level host field\")\n\t}\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never include Host in splithttp headers — use the host field","Audit imported header sets case-insensitively for 'host'"],"tags":["config","splithttp","headers","http"],"backgroundTag":null,"analyzedSha":"7d214f8b094f75322fa3990f8aadad1c912f24f5","analyzedAt":"2026-08-15T14:26:24.325Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}