For custom routing, check rule order first, then match conditions, and finally DNS policy. This guide is for users with a working connection who need site-specific exceptions. It covers common V2Ray and Xray field rules, how to configure them in v2rayN, and how to verify the results. Examples use reserved documentation domains and addresses; they do not represent real site categories.
1. Routing rules match conditions, then select an outbound
Routing is separate from node connection settings. Protocols such as VMess and VLESS connect to a server; routing decides which outbound handles a request that has reached the core. v2rayN provides the configuration interface, but the active core applies the rules. The JSON syntax below is for V2Ray/Xray and cannot be pasted unchanged into editors for other configuration formats.
A typical rule includes type: "field", match fields, and outboundTag. The tag must match an existing outbound's tag. Setting the tag to proxy does not create a proxy node, and direct does not create a direct outbound. Clients may generate different tags, so check the actual configuration before editing.
These are destination ports, not the client's local listening ports. For example, the rule below sends only requests to port 443 for hosts under example.com through the proxy outbound; it does not cover every connection to that domain.
{
"type": "field",
"domain": ["domain:example.com"],
"port": "443",
"outboundTag": "proxy"
}
- Multiple values in one field are usually ORed: any matching entry in the
domainarray satisfies the domain condition. - Different fields are ANDed: if both
domainandportare set, both conditions must match. - Rules are checked in order: the first rule that fully matches selects the outbound, and later, more specific rules are not considered.
2. Domain, full, and regexp: where domain matching applies
Use domain: to match a base domain and all its subdomains; use full: to match one exact hostname. Don't treat them as interchangeable just because both refer to domains. The domain field takes a hostname, not a URL: leave out the scheme, path, and query string.
| Rule syntax | What it matches | Common boundary confusion |
|---|---|---|
domain:example.com | example.com and its subdomains, such as api.example.com | Does not match notexample.com or example.com.test |
full:example.com | Only example.com | Does not match www.example.com |
example | A string appearing anywhere in the domain | May match unrelated-example.test; unsuitable for a precise site exception |
regexp:^api[0-9]+\.example\.com$ | For example, api1.example.com and api25.example.com | Requires start and end anchors; backslashes must also be escaped in JSON |
Use regular expressions only when you need pattern matching. In the example below, the backslash is doubled in the JSON string so the core receives \. to match a literal period. If the interface accepts raw rule text rather than a JSON string, this extra JSON escaping is unnecessary.
{
"type": "field",
"domain": [
"full:login.example.com",
"regexp:^api[0-9]+\\.example\\.com$"
],
"outboundTag": "proxy"
}
Bottom line: use full for a single-host exception
For a single login host, start with an exact hostname rule and test it. Expand to a domain rule only if all subdomains need the same outbound, so static assets and other services aren't affected.
3. IP, GeoIP, and GeoSite: keep address and domain sets separate
ip is a JSON field; geoip: is a set reference that goes in its array. Put geosite: in the domain array instead. Both types of set require the corresponding data files to be available to the core. The data files define the set names and contents; the core does not generate results dynamically from a name.
{
"type": "field",
"domain": ["geosite:cn"],
"outboundTag": "direct"
}
{
"type": "field",
"ip": [
"192.0.2.15/32",
"2001:db8::15/128",
"geoip:private",
"geoip:cn"
],
"outboundTag": "direct"
}
The 192.0.2.15 and 2001:db8::15 addresses above are reserved for documentation; replace them with the target service's address when applying the example. IPv4 /32 and IPv6 /128 prefixes each represent a single address. Shorter prefixes generally cover larger ranges. Don't add a large network range just to fix access to one address.
- geosite:cn: Matches domain categories in the data set; it does not guarantee that every IP address a domain resolves to is in the same region.
- geoip:cn: Matches against the set containing the destination IP; it does not guarantee that the site hosted at that address belongs to a particular domain category.
- geoip:private: Matches private and reserved address ranges defined in the data set. A common use is to send local resources directly.
- Missing data sets: If startup logs report that GeoSite or GeoIP could not be loaded, check the data files and entry names first; this is not a node connection issue.
4. Match priority: put exceptions before broad rules
The core does not automatically rank an exact hostname above a domain set. Rules are evaluated from top to bottom in the rules array. If the first rule sends domain:example.com direct, a later proxy rule for full:api.example.com cannot override it. Put exceptions first, broader sets next, and the fallback last.
This is a routing snippet to merge into an existing configuration, not a complete client configuration. It assumes that outbounds already contains tags named proxy and direct, and that the referenced data sets are available. If your tags differ, update them to match; copying only the rules is not enough.
{
"routing": {
"domainStrategy": "IPOnDemand",
"rules": [
{
"type": "field",
"domain": ["full:api.example.com"],
"outboundTag": "proxy"
},
{
"type": "field",
"domain": ["domain:example.com", "geosite:cn"],
"outboundTag": "direct"
},
{
"type": "field",
"ip": ["geoip:private", "geoip:cn"],
"outboundTag": "direct"
},
{
"type": "field",
"network": "tcp,udp",
"outboundTag": "proxy"
}
]
}
}
| Test target | First matching rule | Expected outbound |
|---|---|---|
| api.example.com:443 | Rule 1: exact hostname exception | proxy |
| www.example.com:443 | Rule 2: base domain and subdomains | direct |
| 192.168.1.20:80 | Rule 3: private address set | direct |
| Public destination not matched by the sets above | Rule 4: TCP/UDP fallback | proxy |
This table shows how the configuration evaluates; it is not a network speed test. The example domains may not host real services, so use a known working target to verify a website. If you remove the final fallback rule, unmatched requests usually use the first outbound in the configuration. You can't generally say that unmatched traffic will always use the proxy or always go direct without seeing the full configuration.
5. domainStrategy: why IP rules may not match
When a request reaches the core, its destination may be a domain or an IP address. If only the IP is available, adding a domain rule will not restore the original hostname. Conversely, a request with only a domain usually needs the routing resolution strategy to provide an address before it can match an ip condition. That's why domainStrategy needs to be checked separately.
| Strategy | What happens during routing | Configuration notes |
|---|---|---|
AsIs | Does not resolve the destination domain to an IP for routing matches | IP rules can still match when the destination is already an IP; this does not mean the outbound never resolves domains |
IPIfNonMatch | If no rule matches the domain in the first pass, attempts to resolve it and match again | If an unconditional fallback matches in the first pass, this additional resolution is not triggered |
IPOnDemand | Attempts to resolve the destination domain when routing encounters a rule that requires an IP | May add DNS queries; consider both resolution latency and the DNS egress route |
The previous section uses IPOnDemand so the third address-set rule can be checked before the final fallback. If you switch directly to IPIfNonMatch, a domain request may match the broad fourth rule first, preventing the intended address-based routing. This is not a GeoIP syntax error; the difference is when resolution is triggered.
Bottom line: identify what information the rule needs
For site-based routing, make sure requests include the hostname; for IP-based routing, choose an appropriate DNS resolution strategy. Check the DNS server, DNS query routing, and sniffing coverage separately—changing domainStrategy alone won't address them all.
- When an app explicitly connects through an HTTP or SOCKS proxy and sends the hostname, the core can usually match it directly against domain rules.
- If an app resolves the hostname itself and sends only the IP, the domain information may be lost. Sniffing can try to identify the hostname in supported traffic, but it cannot recover every request.
- Routing's
domainStrategyand similar resolution options for outbound connections have different roles. Check the configuration level before changing either one.
6. Configure and verify rules in v2rayN
For everyday use, manage rules in the routing settings interface. v2rayN generates the runtime configuration, so direct edits to temporary JSON files may be overwritten when you restart the service or switch nodes. The steps below use a desktop interface with a 「Settings」→「Routing Settings」 menu; button locations may vary across versions, but you still need to check rule order and outbound mappings.
-
Keep a copy of the existing configuration
Open 「Settings」→「Routing Settings」 and note the active rule set and domain resolution strategy. Back up the current configuration before making changes; don't clear the default rule set.
-
Add one rule
In the target rule set, add an exact hostname condition such as
full:api.example.com, then select the proxy outbound. Enter only the rule in the domain field; don't paste the entire routing object. -
Reorder the rules
Move this exception above the relevant
domain:,geosite:, or other broad rules. After saving, confirm that the main window has the edited rule set selected. -
Reconnect
Restart the service or reload the configuration, then close the target page's existing connections and visit it again. Existing connections generally won't switch to a different outbound just because you changed a rule.
-
Check the runtime logs
In the core logs, check the destination hostname, destination port, and outbound tag. Start with a known working website before broadening the rule. If the log level doesn't show enough detail, temporarily set it to info or debug, then restore the previous level when you're done.
Test at least three types of requests: the exception hostname, a regular hostname under the same base domain, and a local network resource. The first two test the boundaries of the domain rule; the third checks whether a broad proxy rule is overriding direct access to private addresses. If no request appears in the logs, first check whether the app is using the current proxy entry point instead of changing the routing expression again.
7. Quick answers to common rule conflicts
Why does traffic still go direct when I added full?
Check whether a preceding domain:, geosite:, or catch-all rule already matches the hostname. Move the exact-host exception above broader rules, save, and test with a new connection. Also verify the outbound tag selected for that rule.
Why doesn't a rule with both domain and ip match?
In one field rule, both are required conditions. If either the domain or the address should match, split them into two rules with the same outbound. Keep them together only when you specifically need both conditions to apply.
Why do some sites still use the proxy with geosite:cn enabled?
First check the hostname used by the actual request; a page may load resources from different domains. Then check earlier rules, whether the data files loaded, and whether the set includes that domain. Try an explicit full: rule to verify the route, then confirm the actual outbound in the access log.
Does setting port 443 match every website?
No. It only restricts the destination port; traditional HTTP may use port 80, and other services may use custom ports. HTTPS can also use TCP or UDP. If you add network: "tcp", UDP requests won't match.
Will custom rules still work after a subscription update?
Node subscriptions and routing rules are separate parts of the configuration. After an update, check that your rule set is still selected and that the proxy outbound still points to a valid node. Don't use a subscription URL as routing configuration, or paste routing JSON into the subscription URL field.
Follow the same troubleshooting order each time: confirm the request reaches the core, confirm the hostname or address is available, find the first matching rule, then check whether the outbound can connect. Change one type of condition at a time to tell whether the result comes from rule boundaries, DNS policy, or the node's network.