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.6.1 (94ffd50)
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" (legacy "chain" and other types existed in V2Ray; only field is supported now). |
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). |
port | string | Match by destination port. 80, 80-90, 80,443,8080-8090. |
sourcePort | string | Match by source port (same syntax). |
network | string | Match by transport. tcp, udp, tcp,udp. |
user | []string | Match by inbound-user email tag. |
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). |
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 field:
| Field | Description |
|---|---|
ruleTag | A human-readable name for the rule. Surfaced in the API and logs. |
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`](./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.6.1 (94ffd50)
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 now 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 listsdomainsExcludedand — new in recent Xray —ipsExcluded, 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.6.1 (94ffd50)
