This guide is for users who can already connect to a node and want to adjust DNS and routing rules. The examples use the traditional Xray JSON configuration and separate resolver selection, response filtering, and outbound routing. You'll also find configuration snippets you can merge and steps to verify the results. Check field support against your actual V2Fly core version; client names alone don't guarantee compatibility.
Separate DNS Resolution from Outbound Routing
DNS routing determines which resolver receives a domain lookup; routing rules determine which outbound handles the connection. They’re related, but they aren’t the same setting. Assigning a domestic resolver to a domain doesn’t mean the connection will go direct. Likewise, routing a connection through a proxy doesn’t mean the app’s earlier DNS lookup went through that proxy.
This flow matches the routing.domainStrategy: "IPIfNonMatch" used in this guide: the core checks domain rules first and resolves the address for IP rules only if no domain rule matches. If a domain rule matches, the routing stage may not query the built-in DNS at all. Whether resolution happens later depends on how the selected outbound handles the domain. So, no DNS log entry doesn’t necessarily mean the configuration isn’t working.
- The app passes a domain to the core: Domain rules can match it directly, and the core may use its built-in resolver.
- The app passes an IP address to the core: The original domain may already be lost;
geositealone can’t recover it. - The app uses encrypted DNS on its own: Its queries may bypass the core’s
dnssettings, so check the app separately. - Only the system proxy is enabled: This usually routes traffic from proxy-aware apps through the proxy, but doesn’t take over all system DNS traffic.
servers, domains, and expectIPs
dns.servers is a resolver list. It can contain server addresses or objects with matching conditions. An object’s domains field selects queries by domain, while expectIPs checks whether the server’s response contains addresses in the expected range. One selects the query target; the other filters the result. They aren’t interchangeable.
Resolver for Domains in Mainland China
- address
223.5.5.5- domains
geosite:cn- expectIPs
geoip:cn- skipFallback
true
Prioritize domains in mainland China and exclude this resolver from the general fallback pool for other domains.
General Fallback Resolver
- address
https://1.1.1.1/dns-query- Transport
- HTTPS
- domains
- Any
- Query Type
- Inherit global
UseIPv4
Handles queries that don’t match a dedicated rule, or whose dedicated resolver response fails the filter.
skipFallback: true means “exclude this server from general fallback,” not “never fall back after this server is tried.” If a response for a domain in mainland China fails expectIPs, the example can still try another fallback resolver. Confusing these two behaviors can lead to incorrect assumptions about where a query goes.
geosite:cn uses domain classification data; geoip:cn uses IP classification data. Neither is a real-time geolocation service, and neither determines a region from a website’s display language. If a service in mainland China uses an overseas IP, a cross-region CDN, or outdated classification data, strict address filtering may reject valid results.
- Use
full:example.comto match a single domain exactly. domain:example.commatches the domain and its subdomains.- Before using
geosite:cnorgeoip:cn, confirm that the running core can read the corresponding data files. expectIPsfilters by classification; it doesn’t prove that a response is authentic. An address belonging to a particular region alone doesn’t make the result correct.
Combine DNS and Routing Snippets
The JSON below is not a complete node configuration. It assumes your existing outbounds already contain the direct and proxy tags, for direct and verified proxy connections respectively. When merging, update the existing dns and routing objects. Don’t declare the same keys twice in one object.
{
"dns": {
"queryStrategy": "UseIPv4",
"servers": [
{
"address": "223.5.5.5",
"domains": [
"geosite:cn"
],
"expectIPs": [
"geoip:cn"
],
"skipFallback": true
},
"https://1.1.1.1/dns-query"
]
},
"routing": {
"domainStrategy": "IPIfNonMatch",
"rules": [
{
"type": "field",
"ip": [
"223.5.5.5/32"
],
"port": "53",
"outboundTag": "direct"
},
{
"type": "field",
"ip": [
"1.1.1.1/32"
],
"port": "443",
"outboundTag": "proxy"
},
{
"type": "field",
"domain": [
"geosite:cn"
],
"outboundTag": "direct"
},
{
"type": "field",
"ip": [
"geoip:private",
"geoip:cn"
],
"outboundTag": "direct"
},
{
"type": "field",
"network": "tcp,udp",
"outboundTag": "proxy"
}
]
}
}
The first two rules control how resolver connections are routed: queries to 223.5.5.5:53 go direct, while connections to 1.1.1.1:443 use the proxy. Standard https:// DoH endpoints use the core’s routing system; don’t replace them directly with the +local form, which follows a different connection path. These IP-and-port rules also match other traffic to the same destinations, not just DNS traffic.
Keep the rules in order. Resolver outbound rules come first to prevent the DoH address from being rerouted unexpectedly by later IP classification rules. The final rule explicitly sends remaining TCP and UDP traffic through proxy, rather than relying on the default order of the outbound array. If you have existing LAN, blocking, or application-specific rules, integrate them individually. Replacing the whole section won’t preserve their behavior.
Check: Set Resolver Routes Before Comparing Responses
First confirm that 1.1.1.1:443 actually matches proxy, then inspect the domain’s resolution result. Changing the DNS address without checking the outbound route can’t confirm that the remote query was sent as intended.
Check the Direct Outbound’s DNS Strategy Too
When a domain in mainland China matches a domain rule, its connection goes through direct. If that outbound uses the Freedom protocol with its default resolution strategy, the final connection may still use the system resolver instead of this guide’s built-in DNS. To make the direct outbound explicitly use the built-in resolver, check the existing outbound and adjust settings.domainStrategy.
{
"tag": "direct",
"protocol": "freedom",
"settings": {
"domainStrategy": "UseIPv4"
}
}
This is one object in the outbounds array. Update the existing direct outbound instead of adding another with the same tag. Here, UseIPv4 and the DNS query strategy above provide an IPv4 baseline that’s easier to troubleshoot; they don’t mean IPv6 must be disabled.
- For dual-stack connections: Consider changing both the DNS query strategy and the Freedom resolution strategy to
UseIP, and check local IPv6 connectivity and routing. - When a proxy outbound receives a domain: The remote server may resolve it, so the local DNS result may not be the one used remotely.
- When the node address is a domain: The proxy server’s own name resolution also matters. Don’t make the node’s only resolution path depend on a proxy connection that hasn’t been established yet.
Verify the Effective Configuration in Your Client
v2rayN is a desktop client, v2rayNG uses the Xray core, and v2flyNG uses the V2Fly core. The client interface generates the runtime configuration, so a field appearing in an editor doesn’t mean the active core is using it. This guide isn’t tied to a particular interface version: always verify the active node, running core, and final generated JSON.
- Save a baseline: Back up the working configuration and note the node, core version, system proxy status, and whether a virtual network adapter is enabled. Change only DNS and related routing settings at a time; don’t also switch nodes or transport options.
- Inspect the runtime file: Confirm the final JSON contains both
dns.serversentries, the expected outbound tags, and the rules in the correct order. If the client generates the configuration, edit its persistent configuration source—not a temporary file that will be overwritten at the next launch. - Enable diagnostic logging: Temporarily set
log.logleveltodebug, restart the core, and make the request again. Log details vary by version; look for the resolver destination, matched outbound, and any errors. - Test separately: Choose one domain confirmed to be in
geosite:cnand another that isn’t. Close duplicate connections and restart the app if needed, so reused connections don’t hide new DNS behavior. - Restore normal logging: When testing is done, set the log level back to
warningor its original value. Before sharing logs, remove node addresses, subscription links, visited domains, and other sensitive information.
If the relevant core command is available on your device, run a syntax and load check from the configuration file’s directory. The commands below are common command-line forms for Xray and V2Fly; run only the one for your core. A successful configuration test doesn’t mean the upstream resolver is reachable.
xray run -test -config config.json
v2ray test -c config.json
Don’t use the result of nslookup example.com 223.5.5.5 as proof that the core’s DNS routing works. This command queries the specified server directly. It’s useful for checking whether that query path is available, but it doesn’t use this guide’s domain classification, fallback, or proxy routing rules. Test and record system DNS and core-generated queries separately.
DNS Spoofing Protection: Limits and Common Issues
Here, “DNS spoofing protection” means reducing incorrect results by encrypting remote queries, controlling the outbound route, and filtering unexpected addresses. No single field guarantees this. DoH protects queries in transit to the resolver, but the resolver’s trustworthiness, queries made separately by apps, and system DNS caching can all affect the final connection.
Mainland China websites are suddenly slow too. What should I change first?
Temporarily remove expectIPs from the mainland China resolver and retest without changing anything else. If latency improves, check whether the domain’s actual response matches the geoip:cn classification. Don’t add more fallback resolvers to mask the cause.
Why am I still seeing local DNS queries with remote DNS configured?
First identify whether the query comes from the browser, the operating system, node-domain resolution, or the core. Check the app’s secure DNS settings, the scope of the system proxy, and Freedom’s domainStrategy. The core’s DNS settings don’t automatically stop other processes from sending queries.
The core won’t start after I save the configuration. How do I troubleshoot it?
Check whether the first error points to invalid JSON, an unknown field, or a problem loading classification data. Confirm the core can read geosite.dat and geoip.dat, and that direct and proxy are existing outbound tags.
DoH keeps timing out, but the proxy node connects. What should I check?
Confirm that requests to 1.1.1.1:443 use the proxy outbound, then check connectivity from the node to the resolver. Check the system clock and look for certificate errors; don’t bypass the failure by disabling certificate validation. If needed, test a different DoH endpoint that you can verify.
Why are websites still connecting to the old IP after I changed the settings?
Exit and reopen the app, restart the core, and test a new connection. The browser, operating system, core, and remote resolver may all cache results. Refreshing a page may reuse an existing connection, so it doesn’t prove the new rules failed.
Keep the Configuration Minimal and Easy to Explain
- First test two resolvers and two outbounds, then add custom domain classifications gradually.
- If traffic is routed incorrectly, find the first rule that matched instead of just checking whether the intended rule appears in the list.
- To keep a specific domain on a direct connection, check DNS matching, routing priority, and the direct outbound’s resolution strategy.
- To restrict outbound DNS traffic, design system-level DNS interception and network policies separately. Don’t treat this snippet as a complete traffic-control solution.
A maintainable split-DNS setup should answer three questions: Why did the domain select this resolver? Why did the resolver connection use this outbound? Why did the final destination go direct or through a proxy? Answer these with logs and the runtime configuration before adding more rules; it’s easier to troubleshoot than simply stacking resolver addresses.