How to Write Custom V2Ray Routing Rules: domain, IP, geosite Syntax and Match Priority

A practical guide to domain, IP, and geosite rule syntax, wildcard scope, first-match order, and ready-to-use traffic-splitting examples.

At a glance

For users who have already imported nodes and want precise control over direct, proxied, and blocked traffic. You’ll learn to distinguish full hostnames, root domains, keywords, CIDR ranges, and geosite categories, understand top-to-bottom first-match behavior, and verify a configuration with a fallback rule.

What routing rules actually handle

The V2Ray or Xray routing module does not change the node protocol or increase the node’s bandwidth. It receives the connection’s destination information, then decides which outbound should handle it. Common outbound tags are proxy, direct, and block, meaning send the connection through a proxy node, connect directly, or reject it. Tag names are customizable, but outboundTag in a rule must exactly match the outbound configuration’s tag, including letter case.

A request can provide more matching data than just a domain. The destination may also include an IP address, port, and network type; some clients can provide a process name or inbound tag as well. This article focuses on the three conditions most often confused: domain, ip, and geosite. geosite is actually written inside the domain array: it references a prebuilt domain category, rather than being a standalone rule field.

Application sends a requestInbound receives the connectionRead destination detailsCheck rules in orderSelect the matching outbound

Routing follows one core principle: “top to bottom, first match wins.” The core reads the rule list in order. Once every condition in a rule is satisfied, it uses that rule’s outbound and stops checking the rules below it. So if a domain belongs to both an advertising category and a regional category, an earlier blocking rule takes effect before a later direct-connection rule.

Rule conditions Information read Typical use Key limitation
domain Request destination domain Route a specific site directly or through a proxy Cannot match when the destination is initially available only as an IP
geosite: Domain category data Route traffic in bulk by region or purpose Depends on a local category data file
ip Destination IP or resolved address Route private networks, regional IPs, and CIDR ranges Whether domain requests are resolved depends on policy
port Destination port Limit matching to ports such as 53, 80, and 443 Combined with other fields in the same rule using AND logic

How domain and geosite syntax differ

The domain array supports several prefixes. Rather than writing every address as a plain string, first decide whether you need an exact host, an entire root domain, or sites whose names contain a particular string. A scope that is too broad sends unrelated domains through the same outbound; one that is too narrow may miss static assets, APIs, or login subdomains.

Full domain matching

Syntax
full:api.example.com
Matches
api.example.com
Does not match
www.example.com
Scope
Single hostname

Best when changing routing for one API or host without affecting other subdomains on the site.

Root-domain matching

Syntax
domain:example.com
Matches
example.com
Matches both
cdn.example.com
Scope
Root domain and subdomains

Best for sending an entire site through one outbound; this is the most common form for hand-written rules.

Keyword matching

Syntax
example
Method
String contains
Scope
Every domain containing the string
Risk
Can expand the match scope unexpectedly

Use only when fuzzy matching is genuinely needed, and check short terms for false positives first.

Category-list matching

Syntax
geosite:cn
Source
Local domain category data
Method
Bulk-matched entries
Maintenance
Updated with the data file

Useful for broad traffic splitting, but custom domains should still come before category rules.

regexp: accepts regular expressions, such as regexp:^([a-z0-9-]+\.)*example\.com$. It can describe more complex hostname structures, but costs more to maintain than domain:. If you only need the root domain and all subdomains, domain:example.com is clearer and less likely to fail because of an escaping mistake.

geosite:cn references the domain category named cn, while geosite:category-ads-all is commonly used to match advertising-related domains. Category contents come from the data file used by the client; the rule itself stores only the category name. After upgrading the core, if logs report that a category is missing, check that the geosite data is complete and that the category name is supported by the current data version.

{
  "type": "field",
  "domain": [
    "full:api.example.com",
    "domain:static.example.com",
    "geosite:category-ads-all"
  ],
  "outboundTag": "block"
}

How ip, CIDR, and domainStrategy work together

The ip field accepts a single address, a CIDR range, or a geoip category. In CIDR notation, the number after the slash indicates the network prefix length. For example, 192.168.0.0/16 covers addresses from 192.168.0.0 through 192.168.255.255, while 10.0.0.0/8 covers the entire private range beginning with 10. For home routers, storage devices, and LAN services, geoip:private is usually easier than listing ranges one by one.

Whether a domain request can reach an IP rule depends on routing’s domainStrategy. With AsIs, the routing module uses the original destination and does not proactively resolve the domain for IP matching. With IPIfNonMatch, it resolves the IP after domain rules fail, then tries IP rules. With IPOnDemand, encountering a rule that requires the destination IP may trigger resolution during matching.

  1. Route by domain only: choose AsIs to avoid extra resolution during routing.
  2. Domain first, IP as a fallback: choose IPIfNonMatch so hand-written domains and geosite categories decide the route first.
  3. Earlier rules depend on IP data: consider IPOnDemand, and confirm that DNS can return usable results.
  4. LAN traffic must stay direct: place the geoip:private rule before regional IP rules and the final proxy fallback.
{
  "routing": {
    "domainStrategy": "IPIfNonMatch",
    "rules": [
      {
        "type": "field",
        "ip": [
          "geoip:private",
          "192.168.0.0/16",
          "10.0.0.0/8"
        ],
        "outboundTag": "direct"
      },
      {
        "type": "field",
        "ip": [
          "geoip:cn"
        ],
        "outboundTag": "direct"
      }
    ]
  }
}

A common mistake is putting both domain:example.com and geoip:cn in one rule. The intended meaning may be “send it direct if either the domain or regional IP matches,” but the actual meaning is “send it direct only when the domain belongs to example.com and its resolved address belongs to that IP category.” To express OR logic, split them into two rules and point both to the same outboundTag.

Conclusion: put domain rules first and use IP rules to catch the rest

Start with full, domain, and geosite to express clear intent, then place geoip and CIDR rules afterward. With IPIfNonMatch, only requests that miss domain rules proceed to IP matching, making the order easier to predict.

Match priority and a ready-to-use rule order

Rule priority is not determined by whether the condition uses domain, ip, or geosite, and there is no mechanism that automatically ranks “exact rules” above category rules. The only reliable priority is the order of the array. Place custom exceptions before broad categories, put blocking rules before direct-connection or proxy categories that could otherwise override them, and finish with fallback rules covering both TCP and UDP.

The example below is ordered as custom direct routes, ad blocking, private-address direct routes, regional domain direct routes, regional IP direct routes, and proxy everything else. Before using it, confirm that the outbound tags are actually direct, block, and proxy. If your subscription template uses different tags, replace only the tag values; do not casually change the condition order.

{
  "routing": {
    "domainStrategy": "IPIfNonMatch",
    "rules": [
      {
        "type": "field",
        "domain": [
          "full:portal.example.com",
          "domain:intranet.example"
        ],
        "outboundTag": "direct"
      },
      {
        "type": "field",
        "domain": [
          "geosite:category-ads-all"
        ],
        "outboundTag": "block"
      },
      {
        "type": "field",
        "ip": [
          "geoip:private"
        ],
        "outboundTag": "direct"
      },
      {
        "type": "field",
        "domain": [
          "geosite:cn"
        ],
        "outboundTag": "direct"
      },
      {
        "type": "field",
        "ip": [
          "geoip:cn"
        ],
        "outboundTag": "direct"
      },
      {
        "type": "field",
        "network": "tcp,udp",
        "outboundTag": "proxy"
      }
    ]
  }
}
Order Rule purpose Why it goes here
1 Hand-written exception Override the default decision made by later categories
2 Explicit blocking Prevent the request from being captured first by a regional direct category
3 Private address Keep routers and LAN devices directly accessible
4 Regional domain Decide by domain category first and reduce dependence on resolution
5 Regional IP Handle destinations with no domain or no category entry
6 Final fallback Give unmatched connections a deterministic exit

A fallback rule is not required in every core configuration, since unmatched connections may still use the default outbound. An explicit fallback is easier to read and migrate, however. When moving to another configuration, the last rule tells you where uncategorized traffic goes, so you do not have to guess which item comes first in the outbound array.

Conclusion: exceptions must come before categories

If a site must always use a proxy, place its full or domain rule before geosite:cn. If it comes later, the earlier category will match first and the core will never reach the exception.

Enter and verify rules in v2rayN

Using the v2rayN 7.12.x desktop interface as an example, open “Settings” → “Routing settings,” copy the rule set currently in use, and edit the copy. Copying instead of overwriting makes it easy to revert immediately if parsing fails. Button locations may shift between minor versions, but the fields to verify remain the rule order, domain list, IP list, destination ports, and outbound tags.

After finishing, select the new rule set and restart the core. Then open “Settings” → “Parameter settings” and confirm the local listening port. The common local port is 10808, but use the value shown in the current interface; if the port has changed, update the test command too. If the system proxy is enabled, visit one custom direct domain, one proxied domain, and one LAN address separately, then check whether each request reaches the expected outbound.

curl --proxy socks5h://127.0.0.1:10808 https://example.com
curl --proxy socks5h://127.0.0.1:10808 https://www.example.org

In socks5h, the h means the proxy handles domain resolution. This helps the core see the original domain and apply domain or geosite rules. If you instead resolve locally and send only the IP to the proxy, the log may show only the destination IP, so domain rules naturally cannot match.

Common problems and how to diagnose them

When the rules look correct but the result is wrong, the cause is usually not syntax itself. The active configuration may not have enabled the rules, the destination information may differ from expectations, or an earlier rule may already have matched. During troubleshooting, do not repeatedly reorder every rule. Narrow the issue to one target domain first, then use the log to confirm whether the core actually received a domain or an IP.

Why didn’t my domain rule match?

First check whether the destination in the log has already become an IP. If the test tool resolved it locally, switch to a method that passes the domain to the proxy. Also confirm that the rule appears before the geosite category and final fallback.

What if a geosite category cannot be found?

Check the data directory read by the current core and the geosite data file, then verify the category spelling. After upgrading the core, update the corresponding data as well and restart the core to load it again.

What if an internal site is sent through the proxy?

Add a geoip:private direct rule before the proxy fallback. If you use custom ranges, add the actual CIDR as well, such as 172.16.0.0/12, and check whether a broader forced-proxy rule appears earlier.

Is putting domain and IP in the same rule more accurate?

Only do this when both conditions genuinely need to be true. To express “domain or IP,” split them into two adjacent rules and use the same outbound tag for both.

Why does the site still use the old outbound after saving the rules?

First confirm that the new rule set is selected, then restart the core and create a new connection. Browser connection reuse can preserve the old outbound, so close the relevant pages, reopen them, and check the log again.

Finish with one order check: are hand-written exceptions before categories, blocking rules before broad direct routes, private addresses before the proxy fallback, and domainStrategy consistent with a domain-first design? If these four points are directly visible in the configuration, later additions are much less likely to break existing traffic splitting.

  1. Start with a full: rule for one target and confirm that the outbound tag works.
  2. Expand it to domain:, then test the root domain and two subdomains.
  3. Add the geosite category while keeping custom exceptions before it.
  4. Finally enable geoip, CIDR, and TCP/UDP fallbacks, checking the logs layer by layer.
Download V2Ray client