Custom Xray Routing Rules: Domain, IP, Geosite Syntax and Match Order

Learn Xray routing rule prefixes like domain, full, regexp, geosite, and geoip, how rules match from top to bottom, and how domainStrategy affects routing.

At a Glance

This guide is for anyone preparing to edit Xray routing rules. Starting with the difference between domain and IP inputs, it covers rule syntax, field combinations, and resolution strategies, then provides a routing snippet you can verify. You’ll learn why a connection goes direct, through a proxy, or gets blocked—and which logs to check after changing a rule. Examples use reserved test domains and include no real service credentials.

Routing Inputs and Outbound Tags

Xray routing selects an outbound; it does not create requests on behalf of applications. An application connection first enters inbounds. Routing then checks details such as the destination address, port, and network type before handing the connection to the appropriate outbounds. v2rayN is the client interface; the code in this guide describes Xray core JSON configuration and cannot be imported directly as a subscription link.

Inbound connection Destination details Rule matching Outbound selection Remote connection

Each rule’s outboundTag must match the tag of an existing outbound. The direct, proxy, and block values used below are labels you define; using these names does not create those functions. They are typically assigned to a direct outbound, a configured proxy outbound, and a blocking outbound, respectively.

Domain, Full, and Regexp: What’s the Difference?

Domain match values go in a rule’s domain array. Here, domain is both a field name and a matching prefix that can appear in an array entry. The field says “check the domain”; the prefix says “how to compare it.” The example.test value below is a reserved test domain used to illustrate syntax, not a live service.

Array entryMatch typeScope
full:api.example.testExact domain matchMatches this host only, not its subdomains.
domain:example.testDomain and subdomain matchMatches the root domain and api.example.test, but not notexample.test.
exampleSubstring matchMatches a substring and usually covers more than a domain-suffix match.
regexp:^api[0-9]+\\.example\\.test$Regular expression matchThis is a JSON string, so backslashes must be escaped.
geosite:cnDomain-set matchReads the specified set from a local domain data file.

To proxy just one endpoint, use full:. To cover an entire domain tree, use domain:. Don’t use *.example.test as a generic wildcard in a regular domain entry. For wildcard-like matching, use a domain-suffix match or an explicit regular expression.

{
  "type": "field",
  "domain": [
    "full:api.example.test",
    "regexp:^edge[0-9]+\\.example\\.test$"
  ],
  "outboundTag": "proxy"
}

The two array entries in the example are OR conditions: either one can match. In a regular expression, a dot must be written as a literal dot, which means \\. in JSON. The leading ^ and trailing $ anchor the match and prevent accidental matches against hostnames with extra prefixes or suffixes.

IP, GeoIP, and Geosite Data Sets

The ip field checks the destination IP and can contain a single address, a CIDR range, or a geoip: set. Domain sets belong in domain; IP sets belong in ip. geosite and geoip are not online lookup services, nor can they be used interchangeably across fields.

{
  "type": "field",
  "ip": [
    "192.168.0.0/16",
    "10.0.0.0/8",
    "fc00::/7",
    "geoip:private"
  ],
  "outboundTag": "direct"
}

This example lists several private ranges explicitly and supplements them with the geoip:private set. Some ranges overlap to illustrate the syntax; in a real configuration, you can trim the list based on the data file you use. 192.168.0.0/16 denotes a network range, not just the single address 192.168.0.0.

Match Order and Field Combinations

routing.rules are checked in array order, and the first rule whose conditions are met determines the outbound. The core does not automatically move more specific rules higher. A broad rule near the top can end the search before later exceptions get a chance to match.

{
  "routing": {
    "domainStrategy": "AsIs",
    "rules": [
      {
        "type": "field",
        "domain": ["full:api.example.test"],
        "outboundTag": "proxy"
      },
      {
        "type": "field",
        "domain": ["domain:example.test"],
        "outboundTag": "direct"
      },
      {
        "type": "field",
        "network": "tcp,udp",
        "outboundTag": "proxy"
      }
    ]
  }
}

In this snippet, api.example.test goes through the proxy, www.example.test goes direct, and the final rule sends all other TCP and UDP connections through the proxy. If you move the second rule above the first, the API hostname will match the domain-tree rule first and go direct.

CombinationLogicWhat it means
Multiple entries in the same domain arrayORAny one matching entry satisfies the field.
Multiple ranges in the same ip arrayORAn address in any one of the ranges satisfies the field.
domain and port in the same ruleANDBoth the domain and port must match.
domain and ip in the same ruleANDThis is not an either-or choice between domain and IP; the IP condition also depends on destination address information and the resolution strategy.

Takeaway: Put Exceptions Before Set Rules

Start with special handling for individual hosts, then add domain trees and category sets, and finish with a fallback rule. If the goal is “go direct when either the domain or IP matches,” use two separate rules instead of putting both fields in one rule.

How domainStrategy Affects IP Matching

routing.domainStrategy determines whether routing supplements a domain with IP information. It does not change rule order or specify a DNS server address. Even if the configuration has a dns section, routing will not automatically perform IP matching for every domain.

StrategyRouting behaviorKeep in mind
AsIsDoes not resolve domains proactively for routing matches.If the destination is already an IP, IP rules can still match it.
IPIfNonMatchIf no domain rule matches, tries to resolve the domain and then evaluates rules that include IP conditions.If a broad fallback matches in the first pass, no resolution is triggered because the target has already matched.
IPOnDemandWhen a rule needs IP information, tries to resolve the domain and continues evaluating.An IP rule near the top may require resolution and affect whether later domain rules run.

A common misconception is that setting IPIfNonMatch, putting geoip:cn first, and ending with a proxy rule for all TCP and UDP traffic will route every domain by region using its IP. For a domain destination, the first pass may match the final proxy rule directly. Once a rule matches, there is no “resolve after no match” step.

  1. For routing primarily by domain sets: configure clear domain rules first, and decide how requests with only an IP should be handled.
  2. To check IPs only when no domain rule matches: use IPIfNonMatch and check for any broad rules that could match first.
  3. To include earlier IP conditions when deciding how to route domain requests: consider IPOnDemand and verify that DNS results and rule order produce the expected behavior.

An IP resolved during routing should not be assumed to be the address ultimately used for the connection. Whether the outbound continues using the domain or resolves it, and where resolution happens, also depends on the protocol and outbound settings. When troubleshooting, verify “why this outbound tag was selected” separately from “where the connection ultimately went.”

Build a Minimal Test by Port

Don’t import dozens of category rules at once and then check whether a webpage loads. Start with one clearly defined inbound port, one domain rule restricted by port, and an identifiable outbound tag to verify the matching flow. The three numbers below are explicit settings for this test configuration, not defaults for every client version.

10808
Local SOCKS inbound port in this example
443
Destination port in this example’s domain rule
53
Common destination port for traditional DNS
{
  "type": "field",
  "domain": ["full:api.example.test"],
  "port": "443",
  "network": "tcp",
  "outboundTag": "proxy"
}

This rule matches only connections that meet all three conditions: the destination domain, destination port 443, and TCP. It does not match port 80 on the same host or UDP on port 443. port checks the destination port, not the local SOCKS listening port, 10808. If a browser uses a UDP-based transport, this TCP rule cannot tell you what will happen.

  1. Back up the working configuration and confirm that the core has loaded the new routing configuration instead of running an old process.
  2. Verify that the local inbound is listening on 127.0.0.1:10808. If you use a different port, follow your actual configuration.
  3. In a test environment, prepare resolution or sniffing input for the reserved domain. If no test service is available, check only that the core can load the rules; don’t treat a failed connection as a routing failure.
  4. To compare DNS behavior, temporarily increase the log level and repeat the request. Check the destination received, DNS queries, and outbound tag, then restore the original log level when testing is complete.
  5. Change one thing at a time: adjust rule order first, then domainStrategy, and add data sets last. Keep the result from each step so changes don’t mask one another.

v2rayN Routing Troubleshooting FAQ

After changing routing in v2rayN, also check which routing configuration is selected and which core is in use. This guide covers Xray field semantics; the client may convert routing editor entries when writing the generated configuration. Don’t paste an entire JSON block into a field that accepts only domain entries.

Why does a full rule still use the old outbound?

Move the rule above broad domain sets and fallback rules, then reload the configuration. Check the logs to see whether the destination is still a domain. If it is only an IP, check whether the application resolved it itself and whether inbound sniffing actually provided a usable domain.

Why does the core fail to start after I save a geosite rule?

Check the core startup logs and verify that the data file is readable, the set name exists, and the rule is in the correct field. First test routing with a full: rule, then restore the set rule to distinguish a syntax issue from a data file issue.

Why don’t I see the expected DNS query with IPIfNonMatch configured?

Check whether the first pass already matched a domain rule or the network: tcp,udp fallback. An existing match does not meet the “no match” trigger condition. If you need IP conditions to be evaluated earlier, consider IPOnDemand rather than guessing based only on moving the IP rule.

Why do fewer connections match when domain and ip are in the same rule?

Both fields must match. If you intended either a domain-set match or an address-set match to go direct, split them into two rules with the same outboundTag. Then test separately with a domain destination and an IP destination.

What should I do if my changes to the generated config disappear after restarting the client?

Generated configuration may be rebuilt when you switch nodes or restart the core. Save rules through the client’s routing configuration options, or use a custom configuration method it supports. After restarting, check what the core actually loaded; saving successfully in an editor does not prove the changes took effect.

Follow the same verification order every time: identify the input target, check whether the rule conditions are met, find the first matching rule, confirm the tag exists, and verify that the outbound can connect. A webpage loading proves only that one request completed; it does not prove that routing worked as intended. Keeping a minimal test rule and its logs makes issues easier to isolate than changing several routing modes at once.

Download v2rayN