V2Ray Routing Rules: Domain, IP and GeoSite Matching Syntax

Learn how domain, full, regexp, geosite and geoip matchers work, how rules are evaluated in order, and how to fix common conflicts.

At a glance

For users who have imported a subscription and want to customize routing in v2rayN. Start by distinguishing domain matches from IP matches, then learn how first-match order, DNS resolution strategy and outbound tags work. The JSON snippets are core configuration examples, not complete, runnable configs. Use them to diagnose two common issues: rules that never take effect and exceptions overridden by broader rules.

1. Routing rules: Choose the outbound for each request

A subscription provides connection settings for nodes; routing determines which requests use them. VMess and VLESS are connection protocols, not routing matchers. Switching a node to VLESS does not automatically change whether a site connects directly or through a proxy.

Request enters the core Destination details are extracted Rules are checked in order Outbound tag is selected Connection to destination is established

In common V2Ray and Xray routing configurations, routing.rules is an ordered array. Each rule checks conditions such as the destination domain, destination IP, port or inbound tag. When a rule matches, outboundTag points to a configured outbound. Tags are just names in the configuration; proxy and direct are not built-in outbounds.

2. domain, full and regexp: Choose a prefix for the match you need

domain is both a rule field and a possible matching prefix in a field value. The strings in the table below belong in the "domain": [...] array. Don't include the URL scheme, path or query string. Routing matches domain names, not browser address-bar URLs.

SyntaxWhat it matchesTypical boundary case
full:example.comExact hostname onlyMatches example.com, not www.example.com
domain:example.comA domain and its subdomainsMatches example.com and a.example.com, not notexample.com
example.comSubstring matchBroader than domain-boundary matching; may match notexample.com
regexp:^api[0-9]+[.]example[.]com$Regular expression matchMatches api1.example.com, not api.example.com
geosite:cnDomain group in a data fileDepends on whether the local data file provides the cn tag and what it contains

Use full: to proxy a single hostname. Use domain: to apply the same rule to a site and its subdomains. A plain string is for cases where you explicitly want a substring match; it is not shorthand for an exact domain. Don't write domain:*.example.com either: this syntax does not support wildcards.

{
  "type": "field",
  "domain": [
    "full:login.example.com",
    "domain:example.net",
    "regexp:^api[0-9]+[.]example[.]com$"
  ],
  "outboundTag": "proxy"
}

The three array entries above are OR conditions: any one matching domain is enough. The regular expression uses [.] for a literal dot, avoiding confusion from JSON backslash escaping. If you use \. in a regex instead, write it as \\. in a JSON string. The leading ^ and trailing $ anchor the match to the entire string.

3. ip, geoip and geosite: Keep address and domain groups separate

The ip field accepts IP addresses, CIDR ranges and geoip: groups. The domain field accepts domain matchers and geosite: groups. These group types are not interchangeable. geoip:cn matches an address classification; it does not mean all Chinese-language websites. geosite:cn is a domain list, and those domains are not guaranteed to always resolve to addresses in the same region.

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

This snippet routes private-network destinations directly. The first two ranges may overlap with geoip:private, so a real configuration usually only needs the specific ranges or the group. To route domains and IP addresses in mainland China directly, use two rules: one with domain, another with ip. Don't put both fields in one rule expecting them to work as OR conditions.

4. First match wins: Put exceptions before broad rules

Rules are checked in array order, and the first match determines the route. More specific rules do not automatically get higher priority. If you put domain:example.com for direct access before full:api.example.com for proxy access, the broader rule takes precedence.

{
  "routing": {
    "domainStrategy": "AsIs",
    "rules": [
      {
        "type": "field",
        "domain": ["full:api.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 configuration preserves a proxy exception for one hostname, then routes the entire domain directly, and finally sends remaining TCP and UDP requests through the proxy. It shows only routing; you need existing outbounds tagged proxy and direct to use it. If no rule matches, the core commonly uses the first outbound. Don't assume the default route based on a tag's name.

  1. List explicit exceptions. For example, login endpoints, internal services or domains that must use a specific exit.
  2. Add group rules. Then add verified geosite and geoip categories.
  3. Set a fallback. Check the final catch-all rule or first outbound to avoid sending unmatched requests directly by accident.
  4. Check the generated order. The client may generate both preset and custom rules, so the UI list may not show the final, complete array.

Port conditions also affect what a rule matches. For example, adding "port": "443" to a domain rule only handles requests to destination port 443; connections to port 80 on the same domain won't match. If you want to route an entire site through the proxy, don't add a port restriction unintentionally.

5. domainStrategy: When domain names are resolved to IP addresses

Even with a correctly written domain rule, an IP rule may not match because of when resolution happens. routing.domainStrategy controls whether domain names are resolved to addresses during route matching; it does not guarantee that DNS queries will go through the proxy. Check core DNS, outbound resolution and client DNS settings separately.

AsIs: Match the information already available

Config field
routing.domainStrategy
Value
AsIs
Domain-based request
Does not actively resolve an IP for route matching
IP-based request
IP rules can still match

Suitable when domain rules do most of the work and you want to minimize resolution during routing.

IPIfNonMatch: Resolve only if no rule matches

Config field
routing.domainStrategy
Value
IPIfNonMatch
When it triggers
A domain destination doesn't match any existing rule
What happens next
Resolve the domain, then try matching again

If a broad fallback rule has already matched, the request won't reach the resolution-on-no-match stage.

Another common value is IPOnDemand: resolving the destination domain can be triggered when a rule requires an IP during matching. Putting an IP rule near the top may therefore trigger DNS queries earlier. Decide whether domain-based or address-based classification should take priority before changing rule order; switching strategies isn't a universal fix.

6. Verify in v2rayN: Inspect the generated config and connection logs

Back up your current routing setup, then open routing settings in v2rayN to edit the rules. Menu names can vary between desktop interfaces; use the routing settings and rule editor shown in your current version. The JSON in this article shows V2Ray/Xray core fields; don't paste it as-is into a field that only accepts a list of domains.

443
Common HTTPS destination port
53
Traditional DNS port
1–65535
Port range supported by rules

These numbers describe port usage, not test results. Port 443 does not mean all web traffic, and port 53 does not cover all encrypted DNS. When routing by port, account for the actual transport; a port number alone doesn't identify an application's purpose.

  1. Check the input format. JSON uses straight double quotes and has no comments or trailing commas. Keep core field names unchanged.
  2. Check which core is running. Confirm that the expected core is selected and inspect the rule syntax the client actually generated. Don't mix in fields from another configuration format.
  3. Check tags and resources. Verify that outboundTag exists and that group tags can be loaded. Fix core startup errors before testing routing.
  4. Reconnect. After saving and applying the routing changes, close old connections in the test app and make a new request. Existing long-lived connections won't switch outbounds just because the rule list changed.
  5. Check access logs. In the available log details, inspect the destination domain or IP, destination port, inbound tag and outbound tag. Raise the log level temporarily if needed, then restore it after testing.

To test domain rules, prepare at least three cases: an exact exception, api.example.com; another host on the same domain, www.example.com; and a destination outside that domain. The first two distinguish full: from domain:; the third checks the fallback. Example domains are for illustrating match boundaries only. For connection tests, use domains you are authorized to access that actually host a service.

7. Common issues: Check overrides, address input and group errors

Why does a proxy rule still connect directly?

Check whether a broader direct rule appears earlier. Move the specific exception above the broad group, apply the changes and reconnect, then verify the outbound tag in the logs. If the generated order differs from the UI, check whether preset rules match first.

My domain rule is in place, but the logs show only an IP. What should I do?

First check whether the app resolves the domain locally and whether the inbound passes the domain through. Then check the current core's sniffing settings. Sniffing can only extract information from supported traffic and cannot recover the domain for every connection. For a known, fixed internal destination, use an explicit IP or range rule instead.

The core fails to start after I add geosite. What should I check?

Start with the resource file path and tag name shown in the logs. Confirm that the current data file includes the tag, and update it through the resource update option provided by the client if needed. Temporarily test basic routing with a single full: rule, then restore the group. This avoids changing multiple variables at once.

Rules stop working after I switch nodes. Does the protocol not support routing?

Check the generated configuration first; don't assume VMess or VLESS is the cause. Confirm that the rules still reference the correct proxy outbound after switching, that the routing setup is still selected and that the client hasn't switched to another custom configuration. A failed node connection and a routing rule selecting the wrong outbound are separate issues; check their logs independently.

A reliable workflow is to verify the outbound with one exact domain first, then expand to subdomains or groups, and add IP-based conditions and a fallback last. Change one type of condition at a time, and keep a routing setup you can restore. For more on the basics, see Concepts at a Glance. To troubleshoot DNS and routing together, see Advanced Setup.

Get the client download here