Skip to content

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

FieldTypeDefaultAllowed valuesDescription
rules[]json.RawMessage[]<rule object array>Routing rules, evaluated in declaration order. The first matching rule wins.
domainStrategy*stringAsIsAsIs | IpIfNonMatch | IpOnDemandHow 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):

FieldTypeDescription
typestringMust be "field" (legacy "chain" and other types existed in V2Ray; only field is supported now).
domain / domains[]stringMatch by destination domain. Accepts patterns: full:example.com, domain:example.com, regexp:.+\\.com$, keyword:google, geosite:cn.
ip[]stringMatch by destination IP. Accepts plain IP, CIDR, geoip:cn.
source[]stringMatch by source IP (same syntax as ip).
portstringMatch by destination port. 80, 80-90, 80,443,8080-8090.
sourcePortstringMatch by source port (same syntax).
networkstringMatch by transport. tcp, udp, tcp,udp.
user[]stringMatch by inbound-user email tag.
inboundTag[]stringMatch by inbound tag.
protocol[]stringMatch by sniffed application protocol. http, tls, bittorrent, quic. Sniffing must be enabled on the inbound.
attrsmap[string]stringMatch by sniffed attributes (e.g. Host).
domainMatcherstringDomain matcher implementation. hybrid (default, fast) or linear.

Target keys (one is required):

FieldDescription
outboundTagSend matched traffic to this outbound.
balancerTagSend matched traffic to a balancer (which picks an outbound from its pool).

Plus the metadata field:

FieldDescription
ruleTagA human-readable name for the rule. Surfaced in the API and logs.

balancers[]

FieldTypeDefaultAllowed valuesDescription
tagstring(required)<string>Balancer name. Used by routing rules' `balancerTag` field.
selectorStringList(required)[<outbound-tag prefix>]Outbound tag prefixes. Any outbound whose tag starts with one of these is included in the balancer pool.
strategyStrategyConfig{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.
fallbackTagstring(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. Requires observatory or burstObservatory to be enabled so each outbound has a known latency.
  • leastLoad — pick the least-loaded member. Carries a strategyLeastLoadConfig (see infra/conf/router_strategy.go) with health-check tuning fields.

Examples

CN-domestic direct routing (the canonical China pattern):

json
{
  "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:

json
{
  "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):

json
{
  "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.
  • domainStrategy decides 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 is domain: (suffix match).
  • geoip: and geosite: 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 geodata block downloads fresh geoip.dat / geosite.dat on a cron schedule through a chosen outbound. See Geodata.
  • Inbound sniffing feeds the protocol and attrs match keys. The inbound sniffing block takes destOverride plus exclusion lists domainsExcluded and — new in recent Xray — ipsExcluded, which skip destination-override for matching domains / IPs (along with metadataOnly and routeOnly).
  • Balancers with strategy: leastPing only work when the matching observatory or burst-observatory is configured.
  • ruleTag enables 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 an action (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 separate rule-providers: mechanism for remote rule lists. See Routing — mihomo.

Source: infra/conf/router.go:21-75 · v26.6.1 (94ffd50)

Core Tutorial by Argsment