Routing — Xray-core
The routing block is the engine that decides which outbound a connection uses. It carries a list of rules (matched in order, first wins), a global domainStrategy, and named load balancers that can be used as rule targets.
Top-level options
| Field | Type | Default | Allowed values | Description |
|---|---|---|---|---|
rules | []json.RawMessage | [] | <rule object array> | Routing rules, evaluated in declaration order. The first matching rule wins. |
domainStrategy | *string | AsIs | AsIs | IpIfNonMatch | IpOnDemand | How destination domains are resolved for rule matching. AsIs keeps domains as strings (only domain rules match); IpIfNonMatch resolves to IP when no domain rule matches; IpOnDemand resolves up front whenever IP rules exist. |
balancers | []*BalancingRule | [] | [BalancingRule] | Named load balancers usable as balancerTag in rule targets. |
Source: infra/conf/router.go:71-75 · pinned at v26.9.9 (52a412d)
rules[] — rule object
Each rule is a JSON object whose match keys select a set of connections and whose target keys decide where they go. The shape is polymorphic; the parser inspects which match keys are present.
Match keys (all optional, AND-combined):
| Field | Type | Description |
|---|---|---|
type | string | Must be "field" — the only rule type Xray supports. |
domain / domains | []string | Match by destination domain. Accepts patterns: full:example.com, domain:example.com, regexp:.+\\.com$, keyword:google, geosite:cn. |
ip | []string | Match by destination IP. Accepts plain IP, CIDR, geoip:cn. |
source | []string | Match by source IP (same syntax as ip). |
sourceIP | []string | Same as source (same syntax as ip); when both are set, sourceIP is used. |
port | string | Match by destination port. 80, 80-90, 80,443,8080-8090. |
sourcePort | string | Match by source port (same syntax). |
localIP | []string | Match by the local address the connection arrived on (the inbound's own IP). Same syntax as ip. |
localPort | string | Match by the local port the connection arrived on (the inbound's port). Same syntax as port. |
network | string | Match by transport. tcp, udp, tcp,udp. |
user | []string | Match by inbound-user email tag. |
vlessRoute | string | VLESS inbounds only: match by bytes 7–8 of the UUID the client sent — the third group of the UUID read as a hexadecimal number (e.g. …-01bb-… = 443). Port-list syntax ("443", "1-100,443"). Lets one server route by a value embedded in client UUIDs. |
inboundTag | []string | Match by inbound tag. |
protocol | []string | Match by sniffed application protocol. http, tls, bittorrent, quic. Sniffing must be enabled on the inbound. |
attrs | map[string]string | Match by sniffed attributes (e.g. Host). |
process | []string | Match by the local process that opened the connection: a process name (curl; a trailing .exe is ignored), an absolute path (/usr/bin/curl), a folder ending in / (prefix match), self/ (Xray's own process) or xray/ (Xray's executable path). Only matches TCP / UDP connections from the same machine; supported on Linux, Android, Windows and macOS. |
localOS | []string | Match by the operating system Xray itself runs on (Go runtime.GOOS: linux, windows, darwin, android, ios, freebsd, …; case-insensitive). Evaluated once when the rule is built — handy for one config shared across devices. |
domainMatcher | string | Domain matcher implementation. hybrid (default, fast) or linear. |
Target keys (one is required):
| Field | Description |
|---|---|
outboundTag | Send matched traffic to this outbound. |
balancerTag | Send matched traffic to a balancer (which picks an outbound from its pool). |
Plus the metadata fields:
| Field | Description |
|---|---|
ruleTag | A human-readable name for the rule. Surfaced in the API and logs. |
webhook | { url, deduplication, headers }. When the rule matches, POST a JSON event (inbound / outbound tags, source, destination, user email, protocol, timestamp, …) to url (5 s timeout; a Unix-socket URL form is also accepted). deduplication suppresses repeat events for the same user email within that many seconds (0 = off); headers are added to the request. Does not change routing. |
balancers[]
| Field | Type | Default | Allowed values | Description |
|---|---|---|---|---|
tag | string | (required) | <string> | Balancer name. Used by routing rules' balancerTag field. |
selector | StringList | (required) | [<outbound-tag prefix>] | Outbound tag prefixes. Any outbound whose tag starts with one of these is included in the balancer pool. |
strategy | StrategyConfig | {type: "random"} | {type: "random|leastLoad|leastPing|roundRobin", settings?: {...}} | Selection strategy. random picks any pool member; roundRobin rotates; leastPing picks the lowest-observed-latency one (requires observatory); leastLoad picks the least-loaded one. |
fallbackTag | string | (unset) | <outbound tag> | Outbound used when the balancer pool is empty or every pool member fails. |
Source: infra/conf/router.go:21-26 · pinned at v26.9.9 (52a412d)
Strategy variants
random— uniform random pick.roundRobin— rotate through the pool in order.leastPing— pick the lowest-latency member. RequiresobservatoryorburstObservatoryto be enabled so each outbound has a known latency.leastLoad— pick the least-loaded member. Carries astrategyLeastLoadConfig(seeinfra/conf/router_strategy.go) with health-check tuning fields.
Examples
CN-domestic direct routing (the canonical China pattern):
{
"routing": {
"domainStrategy": "IpIfNonMatch",
"rules": [
{ "type": "field", "ip": ["geoip:private"], "outboundTag": "direct" },
{ "type": "field", "domain": ["geosite:cn"], "outboundTag": "direct" },
{ "type": "field", "ip": ["geoip:cn"], "outboundTag": "direct" },
{ "type": "field", "outboundTag": "proxy" }
]
}
}Balancer-backed routing with latency-aware selection:
{
"routing": {
"rules": [
{ "type": "field", "outboundTag": "direct", "domain": ["geosite:cn"] },
{ "type": "field", "balancerTag": "proxy-balance" }
],
"balancers": [
{
"tag": "proxy-balance",
"selector": ["proxy-"],
"strategy": { "type": "leastPing" },
"fallbackTag": "direct"
}
]
},
"observatory": {
"subjectSelector": ["proxy-"],
"probeURL": "http://cp.cloudflare.com/generate_204",
"probeInterval": "30s"
}
}Match by application protocol (requires inbound sniffing):
{
"inbounds": [{
"port": 1080,
"protocol": "socks",
"sniffing": { "enabled": true, "destOverride": ["http", "tls"] }
}],
"routing": {
"rules": [
{ "type": "field", "protocol": ["bittorrent"], "outboundTag": "block" }
]
}
}Notes
- Rules are evaluated in declaration order. The first matching rule wins; subsequent rules see only unmatched traffic.
domainStrategydecides what happens when only IP rules exist:AsIs— domain destinations bypass IP rules. Cheapest.IpIfNonMatch— resolve the domain to IP only if no rule matched on the domain. Balanced.IpOnDemand— resolve every domain to IP upfront whenever there are IP rules. Most accurate, most expensive.
- Domain prefixes (
full:,domain:,regexp:,keyword:,geosite:) are parsed inside the matcher. Without a prefix, the default behavior isdomain:(suffix match). geoip:andgeosite:rules read from external data files (geoip.dat/geosite.dat) — typically placed next to the Xray binary. The category names (cn,private,apple,google, …) are defined in those files.- Those data files can be auto-updated: the top-level
geodatablock downloads freshgeoip.dat/geosite.daton a cron schedule through a chosen outbound. See Geodata. - Inbound sniffing feeds the
protocolandattrsmatch keys. The inboundsniffingblock takesdestOverrideplus exclusion listsdomainsExcludedandipsExcluded, which skip destination-override for matching domains / IPs (along withmetadataOnlyandrouteOnly). - Balancers with
strategy: leastPingonly work when the matching observatory or burst-observatory is configured. ruleTagenables runtime rule manipulation via the gRPC RoutingService (see API).
Cross-core notes
- sing-box uses snake_case structured rules with explicit per-criterion fields (
domain_suffix,ip_cidr,process_name). Rules can carry anaction(route, direct, reject, hijack-dns, sniff, resolve, route-options) rather than just an outbound tag. See Routing — sing-box. - mihomo uses compact string-form rules (
DOMAIN-SUFFIX,example.com,proxy) evaluated in order, plus a separaterule-providers:mechanism for remote rule lists. See Routing — mihomo.
Source: infra/conf/router.go:21-75 · v26.9.9 (52a412d)
