V2Ray Routing Rules: Domain, IP, Geosite Syntax and Rule Priority

Learn V2Ray domain match types, geosite and geoip datasets, rule order, and how to prevent rules from overriding each other.

At a Glance

This guide is for users who have imported a server and want to fine-tune direct and proxy routing. First, distinguish domain matching from IP matching, then use configuration examples to understand rule order, DNS resolution strategies, and the default outbound. Note that keyword matching usually uses a plain string; keyword is not a universal JSON prefix. Examples use placeholder domains and do not include connectable proxy servers.

1. Routing Selects an Outbound; It Doesn’t Create Servers

V2Ray routing reads connection details such as the destination address, port, and network type, then sends the connection to an outbound. It does not turn subscriptions into servers, and adding outboundTag: "proxy" does not create a proxy server. A tag is only a reference: the current configuration must contain a matching tag in outbounds.

Inbound accepts the connectionDestination details are readRules are checked in orderAn outbound tag is selectedConnection to the destination is made

Whether a domain rule can match also depends on whether the core has received the domain name. If an app passes the domain through SOCKS, the core can match it directly. If the app resolves the name first and passes only an IP, the domain may already be lost. Traffic sniffing can identify hostnames in some protocols, but it cannot recover domains from all encrypted traffic and is no substitute for DNS.

2. Four Domain Match Types: Exact, Suffix, Keyword, and Regex

domain is an array of strings. Entries in the same array are ORed, so any matching entry is enough. The examples below use example.com to show the boundaries. Don’t put full URLs, paths, or query parameters in domain entries: routing matches the domain, not the entire URL in the browser’s address bar.

PatternWhat it matchesBoundary notes
full:example.comOnly example.comDoes not include www.example.com
domain:example.comThe root domain and its subdomainsDoes not match notexample.com
exampleStrings containing example in the domainMay also match notexample.net
regexp:^api[0-9]+\.example\.com$A specific subdomain with digits after apiEscape backslashes in JSON
{
  "type": "field",
  "domain": [
    "full:login.example.com",
    "domain:static.example.com",
    "regexp:^api[0-9]+\\.example\\.com$"
  ],
  "outboundTag": "proxy"
}

The regex above is a JSON string, so each backslash in the regex must be written twice. ^ and $ anchor the match to the start and end, avoiding a match on just part of a domain. If you need to route an entire site, prefer domain:; it’s easier to check than a broad keyword or complex regex.

3. Geosite and GeoIP: Categories Aren’t Inferred Automatically

geosite refers to domain categories and belongs in the domain array; geoip refers to IP categories and belongs in the ip array. Both depend on data files the core can read and categories that are actually present in those files. Specifying a category name doesn’t trigger an online search or automatically download missing data.

Domain category rule

Rule field
domain
Example entry
geosite:cn
Common data files
geosite.dat
Match basis
Domain rules listed in the dataset

Categories are determined by the data file; they don’t necessarily indicate where a server is located.

IP category rule

Rule field
ip
Example entry
geoip:private
Common data files
geoip.dat
Match basis
Address ranges included in the dataset

The example category must exist in the current data file. If loading fails, check the file and category name first.

A domain’s category doesn’t mean all its CDN addresses belong to the same IP region. Conversely, an IP being listed in a regional category doesn’t mean every domain using that address should connect directly. For business domains that need precise handling, create a separate rule and place it before broad category rules.

4. IP, CIDR, and Ports: OR Within a Field, AND Across Fields

ip accepts a single address, a CIDR range, or a supported dataset reference. The number after the slash in a CIDR is the prefix length, not a port. The three numbers below illustrate syntax boundaries for checking input; they are not network speed test results.

/32
IPv4 single-address prefix
/128
IPv6 single-address prefix
443
Destination port restricted by the example
{
  "type": "field",
  "ip": [
    "192.168.0.0/16",
    "10.0.0.0/8",
    "fc00::/7"
  ],
  "port": "443",
  "network": "tcp",
  "outboundTag": "direct"
}

This rule sends a connection directly only if its destination IP belongs to one of the three ranges, its destination port is 443, and its network type is TCP. It does not send all private-network traffic directly: connections to port 80 on private devices, or to UDP services, continue to later rules.

Adding domain to the same rule does not mean “match either the domain or the IP”; it adds another condition that must also be met. This combination can be especially confusing when the original destination is a domain and DNS resolution hasn’t happened yet. To route a specified domain directly, or a specified IP directly, use two separate rules.

Takeaway: Split rules first, then combine conditions

Create separate rules for domain exceptions and IP range exceptions. Confirm that each matches as intended before adding any port or network-type conditions that must also be met.

5. Rules Match Top to Bottom—Specificity Doesn’t Set Priority

During a rule scan, the first matching rule determines the outbound; later rules don’t override it. The core won’t automatically move full: ahead of geosite:, or give longer domains higher priority. Put specific exceptions, business rules, broad categories, and the final fallback in the order you want them evaluated.

{
  "routing": {
    "domainStrategy": "IPIfNonMatch",
    "rules": [
      {
        "type": "field",
        "domain": ["full:login.example.com"],
        "outboundTag": "proxy"
      },
      {
        "type": "field",
        "domain": ["domain:example.com"],
        "outboundTag": "direct"
      },
      {
        "type": "field",
        "ip": ["geoip:private"],
        "outboundTag": "direct"
      },
      {
        "type": "field",
        "network": "tcp,udp",
        "outboundTag": "proxy"
      }
    ]
  }
}

This is a routing snippet to merge into a configuration, not a complete client configuration. It assumes the proxy and direct outbounds already exist; direct typically uses the freedom protocol. The first rule sends the login domain through the proxy, while the second sends the rest of the root domain and its subdomains directly. If you swap the rules, the second one will match the login domain first.

  1. To try domain rules first, then IP rules if none match: Use IPIfNonMatch and avoid a fallback rule that matches all traffic too early.
  2. To attempt resolution when evaluation reaches IP rules: Consider IPOnDemand, accounting for the additional DNS requests and latency.
  3. Without a catch-all rule: Check the first entry in outbounds. Traffic that matches no rule uses the first outbound by default; tag names have no special default behavior.

6. domainStrategy and DNS: Start by Checking the Destination

The same rules may work in one browser but behave differently in another app. The rules may not be ignored; the two apps may be passing different destinations: one passes a domain, the other an already-resolved IP. Routing resolution strategy controls resolution for routing decisions only; it does not ensure the app, operating system, and core use the same DNS.

StrategyWhen resolution is triggeredWhat to check
AsIsRouting does not proactively resolve domains to IPsIf the original destination is a domain, don’t rely on IP categories for routing
IPIfNonMatchResolve the domain after no rule matches in the first pass, then match againAn early catch-all rule prevents the second pass
IPOnDemandResolve when a domain request reaches a rule that requires an IP matchBoth IP rule placement and DNS reachability affect the result

Existing long-lived connections usually won’t select a new outbound after routing changes. Close old connections in the test app and make a new request; restart the core if needed. If you change DNS, sniffing, and rule order at the same time, it’s hard to tell which change affected the result. Change one variable per test and keep a restorable copy of your configuration.

7. Apply Rules in Clients and Troubleshoot When They Don’t Match

v2rayN is a desktop client; v2rayNG uses the Xray core, and v2flyNG uses the v2fly core. The snippets in this guide use native routing fields common to these two core types, but that doesn’t mean all extensions, data files, or UI import formats are identical. Rules saved by a client may also use a different file format from the final core JSON it generates.

  1. Back up your current setup. In v2rayN, check the active routing profile under Settings → Routing Settings; the menu labels may vary slightly between interface versions. Copy the profile first, then add a clear full: exception.
  2. Check the generated configuration. Verify the order of routing.rules, the tags in outbounds, and whether global proxy or global direct mode has overridden the rules.
  3. Distinguish rule forms from a full configuration. In v2rayNG, use Settings → Routing Settings to review common domain and IP routing options; these forms are not a native JSON editor. For complex ordered rules, use a custom configuration method explicitly supported by your current version.
  4. Reconnect. Apply the settings and restart the service, then check the runtime logs; simply refreshing a page may keep using an old connection. If the log level doesn’t show routing details, raise it temporarily and restore it after testing.

I added the domain. Why is it still going through the proxy?

First, confirm the rule comes before the proxy fallback, then temporarily test with a single full: entry. Check whether the core received a domain or an IP; if it received only an IP, adding more domain entries won’t restore the missing information.

What if adding geosite prevents the service from starting?

Restore the previous configuration first, then use the logs to find the exact category name and file path. Confirm that the data file is readable and the category exists before adding the rule. Don’t cycle through category names at random.

The rule saved successfully, but nothing changed. Why?

Check that the active routing profile is the one you edited and that global mode hasn’t overridden it. Restart the core and establish a new connection, then verify the outbound tag in the actual configuration—not just the message in the form.

Can I put every condition in one rule?

Only do this when all conditions must be met at once. Domain, IP, port, and network type in the same rule are combined with AND. For independent exceptions, use separate rules and put the more specific exceptions first.

Finally, test the setup with three new connections: an exact-match exception domain, a regular subdomain, and a known private IP. Record the destination format, matching rule, and outbound tag for each. This lets you check whether the syntax loads, the rule matches, and the outbound can connect as separate issues—so a server problem isn’t mistaken for a routing failure. For more concepts, see the glossary; for configuration guidance, see Advanced Setup.

Download clientsv2rayN / v2rayNG