For users who already have a working proxy connection and want to improve DNS resolution quality. After reading, you'll be able to write a split dns section for domestic and foreign domains, understand what expectIPs validation does, and map it to v2rayN's settings.
Two Problems Split DNS Solves
On mainland China networks, direct queries for foreign domains are frequently poisoned: the returned IP doesn't belong to the target site, so connections time out or land on an error page. Encrypted remote resolution wraps the query in a TLS tunnel, so the poisoning source can't tamper with the response — that's DoH's core role in split DNS.
Domestic domains are the opposite. Remote resolution still returns a result, but the query path adds an extra proxy hop, and some Chinese CDNs return different nodes depending on where the query comes from, so remote resolution often gets out-of-region nodes. Handing domestic domains to the local DNS resolves them nearby and makes CDN routing more accurate.
| Comparison | Local DNS | Remote DoH |
|---|---|---|
| Query path | Direct to ISP or public DNS | Encrypted query via proxy outbound |
| Domains | Domestic domains (geosite:cn) | Foreign domains (geosite:geolocation-!cn) |
| Anti-pollution | Weak — plaintext queries | Strong — tamper-proof inside TLS |
| Latency | Low — resolves nearby | Higher — extra proxy hop |
The goal of split DNS isn't to mechanically split queries, but to give each domain type its own resolution channel while making sure the routing rules and the dns section use the same domain set.
Basic Structure of the dns Section and Split DNS Syntax
Xray's dns section is a servers array matched in order: when a domain matches an entry's domains list, that entry is used; entries without domains match everything and go last as a fallback. Just paste the snippet below into the root of your config.
"dns": {
"servers": [
{
"address": "https://1.1.1.1/dns-query",
"domains": ["geosite:geolocation-!cn"],
"tag": "proxy"
},
{
"address": "223.5.5.5",
"domains": ["geosite:cn"],
"expectIPs": ["geoip:cn"]
},
"223.5.5.5"
]
}
The first entry handles foreign domains, sending queries through the proxy outbound; the second handles domestic domains and uses expectIPs to validate the response; the third is a bare-address fallback covering domains not in the geosite data. In v2rayN-generated configs, the proxy outbound tag is proxy and direct is direct — keep them consistent when referencing. Use the proxy server's IP address directly instead of its hostname to skip an extra bootstrap resolution step.
Local DNS
- Address
- 223.5.5.5 or 119.29.29.29
- Match
- geosite:cn
- Validation
- expectIPs: geoip:cn
- Outbound
- direct
Domestic domains resolve nearby, keeping CDN routing accurate.
Remote DoH
- Address
- https://1.1.1.1/dns-query
- Match
- geosite:geolocation-!cn
- Outbound
- tag: proxy
- Validation
- Optional: geoip:!cn
Queries go through the proxy, so responses can't be poisoned.
Domains that need fixed resolution can go into dns.hosts, which takes priority over the servers array and never sends queries outbound. Internal hostnames and self-hosted service domains belong here.
Pairing routing Rules with domainStrategy
The dns section decides who resolves a domain; routing decides which outbound the connection takes. Configuring only the dns section without routing can still send all traffic through the proxy; configuring only routing without dns leaves domestic domains exposed to remote-resolution poisoning. The two must be set up together.
"routing": {
"domainStrategy": "IPIfNonMatch",
"rules": [
{
"type": "field",
"domain": ["geosite:cn"],
"outboundTag": "direct"
},
{
"type": "field",
"domain": ["geosite:geolocation-!cn"],
"outboundTag": "proxy"
}
]
}
When to Use Each of the Three domainStrategy Values
- AsIs: matches rules using only the domain string without resolving proactively — best when all rules are written by domain and you don't depend on IP rules.
- IPIfNonMatch: resolves the domain only when no domain rule matches, then tries IP rules — the usual choice for split DNS setups.
- IPOnDemand: resolves whenever a rule contains an IP condition, causing more DNS queries — usually unnecessary.
Conclusion: Share One Domain Set Between dns and routing
geosite:cn appears on both sides, and the foreign side uses geosite:geolocation-!cn as its complement, leaving no gap between them — otherwise you get mismatches where resolution goes through DoH but the connection goes direct.
expectIPs Anti-Pollution Semantics and Verification
expectIPs isn't a routing rule — it only validates the resolution result: if the result isn't in the given geoip list, it's discarded and the next server is tried in order. The most common form for domestic entries is expectIPs: ["geoip:cn"], which ensures the local DNS only accepts IPs from mainland China.
{
"address": "223.5.5.5",
"domains": ["geosite:cn"],
"expectIPs": ["geoip:cn", "geoip:private"]
}
expectIPs supports negation: foreign entries can add geoip:!cn to reject results that resolve to Chinese CDN nodes. If you'd rather have a validation failure raise an error than fall back, add skipFallback: true to the entry. geoip:private allows private address ranges so internal domains aren't wrongly discarded.
Verify That Split DNS Is Working
- Go to Settings → Parameter Settings → Basic Settings, set the log level to debug, and reconnect the node.
- Visit a foreign site and confirm in the log that the domain is resolved by the first DoH entry and the query went through the proxy.
- Visit a domestic site and confirm the query hits 223.5.5.5 and the response passes the geoip:cn check.
- Run
nslookup www.qq.com 223.5.5.5in the terminal to confirm the local DNS itself is working.
Conclusion: expectIPs Is a Validator, Not a Router
It only discards unacceptable resolution results; it doesn't change traffic direction. Which outbound the traffic takes is decided by routing rules — don't expect expectIPs to do split DNS for you.
Common Issues and Troubleshooting
If you run into problems after configuring split DNS, work through these four checks in order.
Domestic sites got slower after enabling split DNS?
Check the log first to see which server handled the query. The usual cause is outdated geosite:cn data — new domains aren't included and fall through to remote DoH. Update the geosite and geoip data files, or add the domain to dns.hosts to pin it to local resolution.
Remote DoH keeps timing out?
Make sure the DoH entry's tag points to the proxy outbound, and in v2rayN check the corresponding 'Remote DNS via proxy' option. If 1.1.1.1 isn't reachable directly, switch the address to https://dns.google/dns-query or https://8.8.8.8/dns-query.
Foreign domains still resolve to poisoned IPs?
The domain probably isn't covered by geosite:geolocation-!cn. Check that the first dns entry's domains and the routing rules for foreign traffic use the same domain set, then set the log level to debug and confirm the query actually lands on the DoH entry.
Manually edited config.json gets reset when selecting a node?
v2rayN regenerates the config from its built-in template whenever you switch servers, overwriting manual dns edits. Configure split DNS under Settings → Parameter Settings → DNS Settings instead of editing the generated config file directly.