This guide is for users who can connect to a server and want to fine-tune routing rules. First check whether the core receives a domain name or an IP address, then choose the right match syntax, order the rules, and use logs to verify the outbound. The JSON snippets here are core configuration, not subscription data. Understanding the difference can help diagnose issues such as added rules having no effect, private-network traffic going through a proxy, or category rules failing to load.
1. How routing rules choose an outbound
v2rayN provides the client interface; the V2Ray or Xray core handles matching and forwarding. VMess and VLESS are protocols used to connect to servers—they do not determine which outbound a website must use. During connection handling, routing uses details such as the destination domain, destination IP, port, and inbound tag to select an existing outbound.
routing.rules are checked from top to bottom. The first matching rule selects the outbound; the core does not keep searching for a “more specific” match. If no rule matches, the first entry in the outbounds array is usually used as the default. Check the default outbound too—not just the last rule.
- Multiple values in one field usually mean “OR”: if any item in the
domainarray matches, the domain condition is satisfied. - Different conditions in one rule usually mean “AND”: if both
domainandportare set, both the domain and port must match. - Rules are checked in order: put specific exceptions before broad categories, and keep fallback rules last.
- The outbound name must exist:
outboundTagmust match an outbound'stag; it is not free-form descriptive text.
2. Matching boundaries for domain, full, and regexp
domain can refer to the field in a rule or to a prefix on a field value; these are different things. The field holds domain conditions, while the prefix defines how they match. To cover a root domain and all its subdomains, use domain:. To match only one exact hostname, use full:.
| Syntax | How it matches | Boundary example |
|---|---|---|
example.com | Matches a substring when no prefix is used | May also match notexample.com |
domain:example.com | Matches the root domain and its subdomains | Matches img.example.com, but not notexample.com |
full:api.example.com | Matches the exact domain | Does not match img.api.example.com |
regexp:^api[0-9]+\\.example\\.com$ | Regular expression; shown here as written inside a JSON string | Matches api12.example.com, but not api.example.com |
Domain rules do not match URL paths. For https://api.example.com/v1/status, the destination domain is api.example.com; don't include the scheme, path, or query parameters in the domain field. HTTPS paths are not conditions that ordinary routing rules can inspect directly.
{
"type": "field",
"domain": [
"full:api.example.com",
"domain:static.example.net",
"regexp:^edge[0-9]+\\.example\\.org$"
],
"port": "443",
"outboundTag": "proxy"
}
This rule means that any one of the three domain conditions must match, and the destination port must be 443 for traffic to use proxy. The dot in a regular expression must be escaped. Inside a JSON string, the backslash must also be escaped, so write \\.. In a client's plain-text domain field, you usually only need \.. Don't mix up the two input formats.
3. When to use IP, CIDR, and GeoIP
The ip field matches the destination IP. It accepts individual addresses, CIDR ranges, and geoip: categories. This is the address the request is trying to reach—not your computer's network interface address or the server's address. IP rules can still match when an app connects directly to an IP without resolving a domain name.
Explicit IP ranges
- Single address
192.168.1.10- IPv4 range
192.168.0.0/16- IPv6 range
fc00::/7- Data requirements
- Does not require geographic database files
Best for LANs or fixed service ranges with known boundaries.
GeoIP category rules
- Private address category
geoip:private- Regional IP category
geoip:cn- Common data files
geoip.dat- What to check
- The category exists and its data can be read
The category's coverage depends on the dataset; it does not reflect real-time network location.
{
"type": "field",
"ip": [
"127.0.0.0/8",
"10.0.0.0/8",
"172.16.0.0/12",
"192.168.0.0/16",
"::1/128",
"fc00::/7"
],
"outboundTag": "direct"
}
The snippets above show explicit direct-routing ranges; they are not a complete list of special-use addresses. If you use geoip:private, check what the current dataset includes. For a company network, it's safer to specify the ranges actually in use rather than assume they belong to a “private” category.
4. Using GeoSite categories and data files
Use geosite: in the domain array to reference a domain collection; use geoip: in the ip array. These prefixes are not interchangeable. geosite:cn references the collection named cn in the dataset; it does not simply mean every domain ending in .cn.
{
"type": "field",
"domain": [
"geosite:cn"
],
"outboundTag": "direct"
}
- First, confirm the collection exists: files with the same name can come from different maintainers, and their category names, contents, and attributes may differ.
- Distinguish the two types of load error: a missing
geosite.datpoints to a missing file or resource path; a missing category points to a collection name or data version issue. - Verify rules again after updates: changes to a collection can change which rules match. A successful update does not guarantee that existing routing behavior stays the same.
- Use attribute filters when needed: for example,
geosite:google@cnworks as expected only if the dataset in use provides that attribute for the relevant entry. Don't infer support from the name alone.
If the core exits as soon as it loads the configuration, check the logs for resource-loading errors before switching servers repeatedly. A speed test cannot confirm that routing is correct if the category rules never loaded. Temporarily remove the problematic category reference and keep one explicit domain rule to help distinguish configuration errors from network problems.
5. How domainStrategy affects IP rules
routing.domainStrategy controls whether routing resolves a domain target to an IP for matching. It is not the same as the DNS policy used by the final outbound. It also cannot recover a domain name that the app has already discarded: if the app submits only an IP, using domain rules may require inbound sniffing and other conditions—and sniffing does not work for all traffic.
| Strategy | How domain targets are handled | Typical effect |
|---|---|---|
AsIs | Does not resolve domains to IPs for routing matches | Domain targets generally cannot be routed by IP rules alone; targets that are already IPs can still match |
IPIfNonMatch | Matches using the available target information first; resolves the domain and retries if nothing matches | If a fallback rule matches on the first pass, the retry resolution is never triggered |
IPOnDemand | Resolves the domain when a condition requires the destination IP during matching | An earlier IP rule may match first and take precedence over later domain exceptions |
A common pitfall is setting IPIfNonMatch and then adding a final proxy rule that matches all TCP and UDP traffic. Since that fallback matches on the first pass, domain targets never reach the “resolve IP if nothing matches” step. As a result, an earlier geoip:cn rule may never get a chance to check the resolved address.
In short: choose a fallback approach before choosing a resolution strategy
If you want IP categories to handle domains that did not match, with IPIfNonMatch you can rely on the first outbound as the fallback instead of adding a final rule that always matches on the first pass. With IPOnDemand, put domain exceptions that must take precedence before broad IP categories.
DNS results directly affect IP category matching here. If the same domain goes direct one day and through a proxy the next, check both its resolved addresses and the category data before assuming rule order has failed. For more on DNS and routing fields, see the configuration reference.
6. Routing example, ordered by priority
This example uses IPOnDemand to show the order clearly: specific domain exceptions, private-network direct routing, domain categories, IP categories, then proxy. This is only a routing object to merge into an existing configuration. The configuration must already have outbound tags named proxy and direct, and the required category data must be available.
{
"routing": {
"domainStrategy": "IPOnDemand",
"rules": [
{
"type": "field",
"domain": [
"full:api.example.com"
],
"outboundTag": "proxy"
},
{
"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"
}
]
}
}
api.example.com is a reserved example domain used to explain the rules, not a service for testing availability. It's placed first, so it uses the proxy even if a later IP category could match. In actual use, replace it with a real business domain that needs a fixed outbound, or remove the rule.
- Keep a copy of your current configuration: Export it from the client and note which routing scheme is enabled, so you can restore it if changes go wrong.
- Check the actual tags: Look at
tagvalues underoutboundsin the generated configuration and confirm they match the example. Your client may use different tag names. - Merge only the routing object: Don't replace the entire configuration with this snippet, or you'll lose essential sections such as inbounds, node outbounds, and DNS.
- Save and enable the configuration in the UI: v2rayN menu names vary by version. A common path is Settings → Routing settings. After adding rules, make sure the intended routing scheme is enabled.
- Start a fresh test connection: After saving, reload or restart the core as prompted by the client, then close existing browser connections before testing. Existing connections may not be routed again.
7. How to verify that a rule actually matched
A website loading only confirms that the connection succeeded; it doesn't confirm that traffic used the intended outbound. To verify routing, record the destination, protocol, matched outbound, and test time. Start with one exact domain and a minimal test, then add category rules gradually. This makes problems easier to isolate than changing a dozen rules at once.
- Confirm traffic reaches the core: Check whether your browser follows the system proxy. For a command-line test, use
curl --proxy socks5h://127.0.0.1:10808 https://example.com/ -I. Replace10808with the SOCKS inbound port actually used by your client. - Preserve the domain as the target: The command above uses
socks5h, which sends the domain to the proxy for resolution. If the app resolves the domain locally and submits an IP instead, the conditions for testing domain rules have changed. - Check the core logs: Look for the destination address and outbound tag; log formats vary by core version. If access logging is disabled, enable it temporarily according to your version's settings. Don't assume no traffic occurred just because nothing was logged.
- Run three comparison tests: Test one explicit domain exception, one reachable private-network address, and one ordinary public domain. Record the expected and actual outbound for each. Use port
80or443for the private-network service, as appropriate. - Clear the test conditions: Close existing connections and retry; restart the test app if needed. When finished, restore the usual log level to avoid accumulating detailed access logs.
Why doesn't a full rule proxy traffic to subdomains?
full:example.com matches only that exact hostname. To include www.example.com, use domain:example.com, move the rule above any broad direct-routing rule, and start a new connection.
Why is it harder to match when both domain and IP are set?
The two fields in one rule are not alternatives. If you want “these domains or these IPs” to go direct, split them into two rules and set the same outboundTag on each.
What should I do if the core won't start after I add a GeoSite category?
Check the first loading error and verify the resource path for geosite.dat and the category name. Remove the category first and use a full: rule to confirm the core runs, then restore a valid data file and category.
Can I use the entire desktop configuration as-is on Android?
v2rayNG uses the Xray core, while v2flyNG uses the v2fly core. Their basic matching syntax is similar, but outbound tags, resource file locations, and import methods depend on the client. Check which fields the selected core supports before migrating routing snippets. Don't replace the entire runtime configuration.
When troubleshooting, check in this order: target information, rule order, resolution strategy, data files, and actual outbound. First verify that one precise rule works, then expand to domain collections and IP categories. This is usually more reliable than repeatedly replacing the entire set of rules.