This guide is for users who can already connect to a server and want to fine-tune traffic routing. First, distinguish domain entries from IP fields; then use three rules to understand first-match behavior, combined conditions, and DNS resolution. Finally, save a recoverable custom setup in v2rayN. The examples use reserved example domains to illustrate syntax, not to test connectivity.
First, distinguish domain prefixes from IP fields
Xray's routing.rules is an ordered array of rules. Each rule specifies which connections should use which outbound: domain, ip, and port are matching conditions, while outboundTag points to an existing outbound. Routing order is unrelated to the order of servers in a subscription, and it does not change the authentication parameters of VMess or VLESS servers.
| Syntax | Where to enter it | What it matches |
|---|---|---|
full:api.example.com | domain array | Matches only this exact domain, not its subdomains. |
domain:example.com | domain array | Matches the root domain and its subdomains, such as www.example.com, but not notexample.com. |
regexp:^api[0-9]+\.example\.com$ | domain array | Matches domains that fit the regular expression, such as api12.example.com. |
geosite:cn | domain array | References a category in the current domain database; its actual coverage depends on the data file in use. |
192.168.0.0/16 | ip array | Matches this IPv4 range; do not prepend ip:. |
geoip:private | ip array | References the private address set in the IP database; the relevant data must be available. |
The easily confused point is the “ip prefix”: here, ip is a JSON field name, not a general-purpose domain-entry prefix parallel to domain:. Putting ip:192.168.1.1 in the domain list does not match a destination IP. Domain strings without a prefix are generally matched as substrings, so example.com can match more broadly than the explicit domain:example.com.
Choose syntax by match scope
Routing a single endpoint through a proxy usually does not require a regex. Handling an entire site and its subdomains does not require listing every hostname either. Start with clearly bounded match patterns to make later troubleshooting easier.
Set clear domain boundaries
RecommendedUse full: for a single host and domain: for a root domain and its subdomains. Both are easy to verify rule by rule and make good starting points for manually written rules.
Best for: fixing the route for one site or adding an exception for a specific endpoint.
Reference a category set
Use geosite: to match a set of domains. Category names are not arbitrary labels; they must exist in the database currently in use, and their members may change when the data is updated.
Best for: maintaining regional or service categories, with a few specific domains added as exceptions.
Describe patterns with regex
Use regexp: for patterns such as numbered hosts. Xray's regex syntax is not the same as every browser's regex features; do not copy expressions that use lookaround or backreferences without checking compatibility.
Best for: multiple hostnames that follow a consistent naming pattern, when you can test both matching and non-matching examples.
Pay attention to the input format when entering a regex. In a plain-text domain field in the GUI, enter regexp:^api[0-9]+\.example\.com$. In a JSON string, escape the backslashes one more time, as shown below. The leading ^ and trailing $ anchor the entire domain so a longer string is not mistakenly treated as a match.
{
"type": "field",
"domain": [
"regexp:^api[0-9]+\\.example\\.com$"
],
"outboundTag": "proxy"
}
Rule order: exceptions first, broader groups later
Rules are checked from top to bottom. The first matching rule determines the outbound; Xray does not continue looking for a “more specific” rule. full: does not automatically take precedence over an earlier domain: entry, and categories have no priority independent of their position in the array.
{
"routing": {
"domainStrategy": "AsIs",
"rules": [
{
"type": "field",
"domain": ["full:api.example.com"],
"outboundTag": "proxy"
},
{
"type": "field",
"domain": ["domain:example.com"],
"outboundTag": "direct"
},
{
"type": "field",
"ip": ["192.168.0.0/16"],
"outboundTag": "direct"
}
]
}
}
This is a routing snippet, not a complete client configuration, and should not be imported as a full configuration. It assumes the existing configuration defines the proxy and direct outbounds. For domain-based destinations, api.example.com matches the first rule and www.example.com matches the second. A destination accessed directly by IP, 192.168.1.20, matches the third.
- Swap the first two rules: The API domain will be caught by the broader domain group first, so the later exact-match exception will never be reached.
- Multiple values in one array: A match on any entry in
domainis enough, making it useful to group several domains that use the same outbound. - Multiple conditions in one rule: For example, if you specify both
domainandport, both conditions generally have to match; it is not an either-or choice. - No rule matches: The configuration's first outbound is generally used, which is not necessarily a direct connection. Check for fallback rules generated by the client as well.
Takeaway: move exceptions up before adding broader categories
If a specific domain uses the wrong outbound, move its exact-match rule above broader groups and check the logs. Do not immediately add another category with overlapping entries; that only creates more rules that can be shadowed.
How IP ranges and DNS strategies work together
IP rules match the destination address, not your computer's current public IP address. When the client receives a domain, whether it resolves the domain first and uses the result for routing depends on domainStrategy. This is not a complete DNS configuration: the resolver used and how DNS requests are sent are still determined by DNS and related routing settings.
| Strategy value | Behavior | What to check |
|---|---|---|
AsIs | Does not actively resolve domains to IP addresses during routing. | If a domain does not match, an IP rule will not automatically catch it; destinations that are already IP addresses can still match. |
IPIfNonMatch | If a domain does not match a rule under the current conditions, Xray tries resolving it before matching again. | If a broader domain rule has already matched, Xray will not choose another rule to find a better IP match. |
IPOnDemand | Performs the required resolution when a rule needs an IP address during matching. | The position of IP rules affects when resolution occurs. A broad IP rule placed too early can change the outcome for domain exceptions. |
For example, to send a management page on an office device at 192.168.1.20 directly, enter 192.168.1.20/32 in the IP field; there is no need to add the entire private address range. If you access the page by domain, also check whether the connection reaches the core as a domain or an IP address. The app's proxy mode, DNS cache, and sniffing settings can all affect which destination details are visible.
Create a recoverable rule set in v2rayN
The steps below follow common entry points in the Chinese desktop interface. In different versions, editing may be available from a button or a right-click menu. Look for “Routing Settings” and the currently active profile first. Do not paste a routing snippet into a server address, subscription URL, or transport parameter.
-
Check the core
Open “Settings” → “Parameter Settings” and check that the core type for the current server protocol is Xray. If your version lists core mappings on a separate settings page, confirm it there. First make sure the server itself connects successfully.
-
Back up the profile
Go to “Settings” → “Routing Settings,” note which routing profile is active, and use the interface's export or copy feature to save a backup. If you already have custom rules, save their order before making changes.
-
Add an exception
Edit the profile you plan to use and add a rule: enter
full:api.example.comfor the domain and select the proxy outbound. Leave any unused conditions blank. In the GUI form, enter only the field value; do not paste an entire JSON block. -
Adjust the order
Add another rule with
domain:example.comas the domain and direct as the outbound. Use the interface controls to move the exact-match exception above the rule for the entire site. Check for any earlier category rules that could override it. -
Enable and verify
Save the profile and set it as the active route, then reload or restart the service as prompted. Replace the example domains with actual destinations you control, close existing connections, and make new requests. If the results are unexpected, switch back to the backup profile to identify what changed.
Change one condition at a time when testing. Choose one domain that should use the proxy, one that should connect directly, and one LAN IP. Create a new connection for each and record the time and destination. Rule changes usually do not move established long-lived connections to another outbound immediately. Reused browser connections can make it look as if “the changes did not take effect.”
- Check the core logs: Look for the destination and outbound details of new connections in the main window's log area. The default log level may not show a rule number for every connection; if needed, temporarily increase the log detail and restore it after testing.
- Make sure the request reaches the core: When using the system proxy, the app must actually follow the system proxy settings. Connections that bypass the core entirely are not controlled by these routing rules.
- Check the generated configuration: If your version provides a way to view or export the runtime configuration, check the order of
routing.rulesand the actual outbound tags. Do not rely on the visual order in the editor as proof of the final configuration.
Common questions: Why aren't my rules working?
I used full. Why is the connection still direct?
First check whether an earlier domain:, geosite:, or other broad rule has already matched, then confirm the actual hostname used by the connection. A web page, its API, images, and login service may use different domains; an exception for the main page alone will not cover every request.
What should I do if adding a geosite category prevents the core from starting?
Check the logs for file-loading errors or messages that a category does not exist. Confirm that the current data file includes the category and that it is in the location the core actually reads. Remove the new entry first to restore connectivity, then update the data using the client's rule data update feature. Do not assume a category exists based on its name alone.
The domain and IP each match separately, but not when I put them together. Why?
Different conditions in the same rule generally must all match. If you mean “connect directly for either this domain or this IP range,” split them into two rules. If both conditions really should apply to the same destination, also check the DNS strategy and the address the core actually receives.
My regex test passes, but JSON reports an error. Why?
Check backslash escaping first: in JSON, a literal dot is usually written as \\.; in a plain-text input field, it is usually \.. Then remove syntax unsupported by the current regex engine and test with one domain that should match and one that should not.
Reliable custom routing usually combines a few exact-match exceptions, clearly defined group rules, and predictable fallback behavior. For every change, record the target, rule position, expected outbound, and actual outbound. This is easier to maintain than stacking similar entries; after a database update, recheck domains that previously needed exceptions.