Advanced 13 min read

Custom routing rules: domain, IP, and geosite syntax with rule priority explained

Routing rules determine which traffic uses a proxy and which connects directly. This guide covers domain prefixes, IP/CIDR notation, geosite datasets, top-to-bottom matching, and practical rule combinations.

Understand what routing rules actually process

The V2Ray and Xray routing modules sit between inbound and outbound connections. After application traffic enters the core, the routing module reads details such as the destination domain, destination IP, port, network type, and inbound tag, then passes the connection to an outbound. Common outbound tags include proxy for proxied connections, direct for local direct connections, and block for rejected connections. These names are not reserved keywords; they are the tag values of entries in the configuration's outbounds. The outboundTag in a rule must exactly match the actual tag.

Protocols such as VMess and VLESS define how data travels between the client and the remote endpoint, while routing rules choose which outbound to use. They operate at different layers. Switching from VMess to VLESS does not automatically change split-routing results, and importing a new subscription does not guarantee that existing custom rules will remain applicable. When using v2rayN, v2rayNG, or v2flyNG, check the active configuration, routing mode, and core type separately to confirm that the rules are written into the configuration currently running.

A typical field rule consists of type, matching conditions, and a target outbound. The commonly used type is field. One rule can contain multiple condition categories, such as a domain and a port restriction; different categories usually must all match. Multiple values within the same category form a candidate set, so matching any one of them is enough to select the rule.

{
  "type": "field",
  "domain": [
    "domain:example.com",
    "full:api.example.net"
  ],
  "port": "443",
  "network": "tcp",
  "outboundTag": "proxy"
}

This example handles only TCP port 443, and the destination domain must match one of two domain entries. A request to the same domain on port 80 will not match. When configuring rules, do not add conditions arbitrarily in the name of being “more precise.” Each additional condition can exclude connections that should have been handled.

Four common domain matching patterns

The domain array accepts more than complete domain names. A prefix determines how the core interprets each entry. The most common forms are full:, domain:, regexp:, and a plain string without a prefix. Choosing the wrong prefix can make the match too broad, or match only the root host while missing its subdomains.

Pattern Match scope Best for
full:www.example.com Matches only the exact destination domain Handling one clearly defined hostname
domain:example.com Matches the root domain and its subdomains Routing an entire site and its common subdomains
regexp:^api\d+\.example\.com$ Matching with a regular expression Domains with stable numbering or a fixed format
example Matching domain keywords Covering domains that contain a specific string

full:: match one exact domain

full:api.example.com is suitable for clearly bounded targets such as an API host or update server. It will not also match www.example.com or cdn.api.example.com. If you want to change routing for one subdomain without affecting other services under the same parent domain, prefer full:.

domain:: cover the root domain and its subdomains

domain:example.com matches example.com as well as subdomains such as www.example.com and static.example.com, but it does not treat similarly named domains with different boundaries as the same site. For site-wide routing, this is generally safer than keyword matching.

Plain strings and regular expressions: broader scope, use with care

A value without a prefix is matched like a keyword. The entry example may cover multiple domains containing that string, which can work when target names vary but follow a clear pattern; it can also catch unrelated sites more easily. Regular expressions offer greater control but increase maintenance and troubleshooting costs. If full: or domain: expresses the requirement, there is no need to start with a complex regular expression.

{
  "type": "field",
  "domain": [
    "full:status.example.com",
    "domain:media.example.net",
    "regexp:^edge-[0-9]+\\.example\\.org$"
  ],
  "outboundTag": "proxy"
}

Backslashes in JSON strings must be escaped, so \. in a regular expression must be written as \\. in a JSON file. This is a common reason why a regular expression works in isolation but prevents the configuration from starting. If the core exits immediately after saving, first check the runtime log for JSON parsing and rule parsing errors.

Using ip, CIDR, and geoip

The ip array matches destination IP addresses. It can contain individual addresses, CIDR ranges, or references to geoip datasets. Individual addresses suit fixed servers, while CIDR is useful for contiguous ranges. In IPv4, 192.0.2.0/24 represents the addresses covered by that network prefix; IPv6 uses the same prefix-length notation, for example 2001:db8::/32.

{
  "type": "field",
  "ip": [
    "192.0.2.25",
    "198.51.100.0/24",
    "2001:db8::/32"
  ],
  "outboundTag": "direct"
}

CIDR is about the network prefix, not a textual shorthand for a start and end address. The larger the prefix length, the smaller the range. In IPv4, /32 points to one address, while /24 typically covers 256 addresses under the same prefix. Do not expand one address currently returned by a provider into an overly broad range, or unrelated services on the same network may be rerouted too.

geoip:private is commonly used to match private networks and addresses reserved for local use, making it useful as part of a direct-connection rule. This keeps LAN gateways, internal dashboards, and local devices from being sent through a remote proxy. When using broad traffic interception such as TUN, place private-address direct rules before general proxy rules.

{
  "type": "field",
  "ip": [
    "geoip:private"
  ],
  "outboundTag": "direct"
}

Entries such as geoip:cn depend on the IP data files loaded by the active core. A dataset represents a group of network addresses, not a domain collection, and it does not guarantee that every server for a site will always belong to the same region. Large sites often use content delivery networks, so the same domain may resolve to different addresses across networks and at different times. IP geolocation alone is therefore not always stable for site-level routing.

geosite is a domain collection, not a protocol name

Place geosite: entries in the domain array to reference curated domain collections. For example, geosite:cn commonly represents a category-specific group of domains, while geosite:category-ads-all commonly represents advertising-related domains. Available names depend on the active data file; a missing name, unloaded file, or incompatible data version can prevent the rule from working.

{
  "type": "field",
  "domain": [
    "geosite:cn"
  ],
  "outboundTag": "direct"
}

The advantage of geosite is that it avoids manually maintaining large domain lists. It works well as a baseline routing layer, but it is not a real-time online lookup. The collection changes only when the data file is updated. If a new domain is not yet included, add an explicit full: or domain: rule before the geosite rule to fix the current route first, then consider updating the data.

To block an advertising category, first configure the corresponding reject outbound and make sure the rule references the correct tag. Writing only outboundTag: "block" is not enough if the complete configuration has no block tag.

{
  "type": "field",
  "domain": [
    "geosite:category-ads-all"
  ],
  "outboundTag": "block"
}

Category-based routing also requires attention to exceptions. If a domain is classified as direct by a collection rule but must use a proxy on the current network, place an explicit proxy rule for that domain before the collection rule. Conversely, if a broad proxy collection covers a domain that must always connect directly, add the more specific direct rule first.

Rule priority: top to bottom, stop at the first match

Routing rules are checked in the order listed in the rules array. The first rule whose conditions are satisfied determines the outbound, and later rules are skipped. This means a “more specific rule” does not automatically receive higher priority; its real priority is its position. Even if the second rule uses an exact full: match, it will never run once a broad first rule has matched.

This order makes the direct rule for full:special.example.com ineffective because the preceding domain:example.com already covers it:

[
  {
    "type": "field",
    "domain": [
      "domain:example.com"
    ],
    "outboundTag": "proxy"
  },
  {
    "type": "field",
    "domain": [
      "full:special.example.com"
    ],
    "outboundTag": "direct"
  }
]

The correct approach is to put exceptions first and broader rules afterward:

[
  {
    "type": "field",
    "domain": [
      "full:special.example.com"
    ],
    "outboundTag": "direct"
  },
  {
    "type": "field",
    "domain": [
      "domain:example.com"
    ],
    "outboundTag": "proxy"
  }
]

When organizing rule order, use the following model: “protect local traffic, handle explicit exceptions, apply category collections, apply regional rules, then add a final fallback”:

  1. Handle private networks, LAN traffic, and local connections that must remain direct first.
  2. Then add explicit exceptions such as individual domains and IP addresses.
  3. Next add geosite, geoip, and larger CIDR collections.
  4. Finally, use a general rule covering TCP and UDP to decide how to handle the remaining traffic.

Always put the general fallback rule at the end. If a proxy rule with network: "tcp,udp" comes first, most connections will match immediately, making the later direct and blocking rules effectively useless.

How domainStrategy affects domain and IP rules

routing.domainStrategy determines how domain resolution is handled during routing. Common values include AsIs, IPIfNonMatch, and IPOnDemand. Behavior can vary across core versions and the way clients generate configurations, so verify the final running configuration before making changes rather than relying only on a label in the interface.

Strategy What it controls What to check
AsIs Apply domain rules to the original destination information without actively resolving an IP for routing Domain connections that rely on IP-based routing may not match the expected rule
IPIfNonMatch Resolve the IP and try IP rules after no domain rule matches DNS servers, resolution results, and final fallback order
IPOnDemand Resolution may be triggered when routing needs an IP condition Rule count, DNS paths, and additional lookup behavior

If routing mainly relies on domain: and geosite:, keeping domain information available during matching is usually more straightforward. If domain connections must also be classified by geoip: or CIDR, check both domainStrategy and DNS. The IP address returned by resolution directly affects the outcome of subsequent IP rules.

Routing DNS behavior must also be distinguished from the application's own resolution behavior. Some connections submit an IP directly, so the core never sees the original domain; domain rules cannot match and only IP, port, and similar conditions are available. Other applications submit a domain and can be evaluated by domain or geosite rules first. When troubleshooting, inspect the destination format in the logs rather than assuming that the address shown in the browser is the domain received by the core.

Three practical rule combinations

Combination 1: direct LAN traffic, proxy everything else

This structure preserves access to local devices while sending the remaining TCP and UDP traffic to the proxy outbound. The first rule catches private addresses, and the second serves as the final fallback.

{
  "domainStrategy": "IPIfNonMatch",
  "rules": [
    {
      "type": "field",
      "ip": [
        "geoip:private"
      ],
      "outboundTag": "direct"
    },
    {
      "type": "field",
      "network": "tcp,udp",
      "outboundTag": "proxy"
    }
  ]
}

Before enabling it, confirm that the proxy outbound supports the required network types, and check how local DNS, LAN domains, and private IP addresses correspond. If internal services use custom domains, add an internal-domain direct rule before the final fallback.

Combination 2: direct common collections, proxy the rest

This structure protects private networks first, sends geosite and geoip collections directly, and gives unmatched connections to the proxy. It suits routing based on regional collections, but the actual result depends on the data files and DNS resolution.

{
  "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",
      "network": "tcp,udp",
      "outboundTag": "proxy"
    }
  ]
}

If a domain covered by a geosite collection needs to use a proxy, add an exact proxy entry before geosite:cn. If the exception comes after the collection, the earlier direct rule will catch it first.

Combination 3: block categories, preserve exceptions, route the rest by collection

A more complete structure puts required exceptions first, processes blocked categories next, then applies direct collections and a proxy fallback. Placing an exception before the blocking rule gives it higher priority.

{
  "domainStrategy": "IPIfNonMatch",
  "rules": [
    {
      "type": "field",
      "domain": [
        "full:required.example.com"
      ],
      "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",
      "network": "tcp,udp",
      "outboundTag": "proxy"
    }
  ]
}

The domains in the example illustrate the structure only; replace them with real targets in practice. The entire routing configuration must also be embedded in the complete configuration's routing object; the fragment cannot run on its own. If the client provides a graphical rule editor, create the rules one by one in the same order and inspect the generated result after saving.

Implementing the rules in v2rayN, v2rayNG, and v2flyNG

The v2rayN desktop client typically manages split routing through routing settings, rule sets, or custom configuration. Interface labels may change between versions, but the verification process is the same: confirm the selected routing mode, verify that the rules are attached to the configuration in use, then restart the relevant core and inspect the logs. Saving in an editor without switching to the corresponding rule profile will not change runtime behavior.

When v2rayNG uses the Xray core, common rules can be applied through the client's routing options. When v2flyNG uses the v2fly core, rule semantics still center on domains, IPs, ports, and outbound tags, but available fields should be confirmed against the capabilities of the active core. Importing a subscription usually updates server node information; do not assume it merges all local custom split-routing rules for you.

Before changing rules, copy the currently working configuration, then change only one variable at a time. For example, add and verify one full: domain rule, then add a geosite collection, and only afterward adjust domainStrategy. If dozens of rules are added at once and connections fail, it is difficult to tell whether the cause is the JSON structure, tag names, data files, or match order.

Check in this order when a rule does not work

  1. Confirm that the configuration is actually enabled. Make sure the client is running the routing profile you just edited, not another node configuration or the default rules.
  2. Check the JSON and field placement. rules must be inside the routing object, with complete string escaping, commas, and brackets.
  3. Check the outbound tag. outboundTag must match the tag in the complete configuration character for character, including consistent capitalization.
  4. Check earlier rules. Determine whether the destination has already matched an earlier domain, IP, port, or general network rule.
  5. Check whether the destination is a domain or an IP. If the application connects directly to an IP, no domain or geosite rule can recover the original domain from that connection.
  6. Check DNS and domainStrategy. When a second IP-based decision is required, confirm that the routing stage can obtain a resolution result.
  7. Check the data files. geosite and geoip entries depend on the data currently loaded and the available category names.
  8. Review the runtime log. Look for configuration parsing errors, missing outbound tags, data-loading failures, and the outbound actually selected.

For testing, choose a repeatable target and account for connection reuse by the application. Existing long-lived connections usually will not be rerouted immediately after a rule change. Restart the core after saving the configuration, then create a new connection for clearer results. DNS caches may also retain an old address, which is especially important for geoip or CIDR rules.

Keep a clear decision chain when writing rules

A stable routing configuration depends less on the number of rules than on clear boundaries at every layer. Exact domains handle exceptions, domain: handles entire sites, geosite handles domain batches, CIDR and geoip handle network addresses, and the final fallback handles everything else. Put narrow-scope rules first and broad-scope rules later to avoid most priority errors.

After completing the configuration, check each rule with three questions: “What is the target, which rule matches first, and which outbound receives it?” As long as this decision chain can be traced through the logs and configuration, you can quickly determine whether an issue lies in the node connection or local split routing when adding VMess or VLESS nodes or updating a subscription.

Download v2rayN