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.
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.
routing.rules: Rules evaluated in array order.type: "field": The field-matching rule type used in this guide.outboundTag: The outbound tag selected on a match; case-sensitive.domainStrategy: When routing adds IP information for a domain; this is not a DNS server address.
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.
| Pattern | What it matches | Boundary notes |
|---|---|---|
full:example.com | Only example.com | Does not include www.example.com |
domain:example.com | The root domain and its subdomains | Does not match notexample.com |
example | Strings containing example in the domain | May also match notexample.net |
regexp:^api[0-9]+\.example\.com$ | A specific subdomain with digits after api | Escape 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.
- Startup error says “file not found”: Check the actual working directory, resource paths, and the client’s data file settings.
- Category not found: Check which tags are in the current file; don’t guess based on documentation for a different data package.
- Results changed after an update: Compare the category contents first, then check whether the rule order changed.
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.
{
"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.
- To try domain rules first, then IP rules if none match: Use
IPIfNonMatchand avoid a fallback rule that matches all traffic too early. - To attempt resolution when evaluation reaches IP rules: Consider
IPOnDemand, accounting for the additional DNS requests and latency. - 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.
| Strategy | When resolution is triggered | What to check |
|---|---|---|
AsIs | Routing does not proactively resolve domains to IPs | If the original destination is a domain, don’t rely on IP categories for routing |
IPIfNonMatch | Resolve the domain after no rule matches in the first pass, then match again | An early catch-all rule prevents the second pass |
IPOnDemand | Resolve when a domain request reaches a rule that requires an IP match | Both 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.
- 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. - Check the generated configuration. Verify the order of
routing.rules, the tags inoutbounds, and whether global proxy or global direct mode has overridden the rules. - 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.
- 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.