Xray routing JSON: ordinary custom rules
Import through Connect → traffic method → Custom Routing → Normal configuration, not ordinary Servers import. Select the imported profile and choose existing servers on Connect. This chapter describes the ordinary form-backed format only. For DNS or sniffing beyond the ordinary controls, and for additional inbounds, use the separate Advanced JSON template. Both types share three saved profiles; editing does not convert between them.
Accepted document
| Field | Contract |
|---|---|
name |
Include a unique, nonempty display name, at most 32 characters |
outbounds |
Required: 1–3 leading empty entry slots, optional freedom/fragment, then the controlled dnsOut helper for explicit direct/empty-answer DNS handling |
routing.domainStrategy |
Write IPIfNonMatch; the App normalizes this value |
routing.rules |
Ordered list of rules below; an empty list uses the default outbound behavior |
dns |
Proxy/direct primary and ordered fallback addresses, global cache/same-exit options, optional FakeDNS |
inbounds |
Only a tunIn sniffing placeholder; no actual listener |
routing.balancers |
Only the proxy roundRobin/leastPing strategy |
observatory |
Only probeURL, probeInterval and enableConcurrency |
geodata.assets |
Optional import-only custom file dependencies, each with file and HTTPS url |
Ordinary configuration additionally accepts the limited DNS, managed sniffing placeholder, proxy selection and Observatory fields below. Real nodes, system outbounds, actual listeners, logs/metrics and arbitrary balancers remain outside this format.
Rule fields and actions
Supported conditions: domain (string array), ip (string array), port (for example "443", "8000-8080", "80,443"), network ("tcp", "udp" or "tcp,udp"), protocol (string array) and localOS (string array). ruleTag names the rule. The editor groups protocol and OS under “More matching conditions”.
Select exactly one action:
| Desired action | JSON |
|---|---|
| VPN | "balancerTag": "proxy" |
| Direct | "outboundTag": "direct" |
| Block | "outboundTag": "block" |
Do not write outboundTag: proxy. Do not write type, enabled, sourceIP, sourcePort, attrs, inboundTag or process conditions. Unsupported fields are rejected rather than silently removed.
Different condition types in one rule are AND. Multiple ordinary values within one condition are alternatives. Prefer separate rules when the intent is OR across domain and IP. Core-specific negated IP lists retain Core semantics; do not apply the simple OR explanation to a negated list without checking it.
Domain forms: full:host.example.com for one hostname, domain:example.com for that domain and subdomains, geosite:CN for a verified default category, ext:other.dat:category for a verified custom category. A bare string is a keyword match, not an exact-host match. Do not silently convert user intent to a broad substring or regular expression.
IP forms: an address, CIDR, geoip:CN, or ext:other.dat:category. Use verified Geodata.
Order and fallback
Rules are checked in order. With IPIfNonMatch, a domain destination is resolved for another IP-aware matching pass only when the first pass fails. For round robin, avoid an unconditional final proxy rule: it can prevent the IP pass.
Round robin is the default: unmatched traffic uses the first outbound and the balancer fallback is the first complete proxy chain. Optional leastPing instead appends a managed-ingress proxy catch-all after user rules and falls back to the first complete proxy chain. Existing sessions are not moved; see selection behavior.
Normal mode supplies the real nodes and system outbounds. Their tags are implementation details; only the three action references above belong in a Custom file.
DNS, sniffing and selection
The legacy single app-dns-direct address remains importable. The extended format accepts repeated app-dns-proxy/app-dns-direct entries in primary/fallback order and optional app-dns-fake; resolver objects contain only address/tag. Root disableCache and disableFallbackIfMatch store global caching and same-exit fallback. Omitted resolver groups use the default 8.8.8.8. App derives domain matching, finalQuery and IPv6 query policy; do not export them.
Complete example
This recipe includes ad blocking, GitHub through VPN, and combined Apple/Microsoft/Bing/China/private direct rules. It needs both default DAT files and two eligible entry nodes. It has no credentials or unresolved placeholders.
{
"name": "Mainland China direct",
"outbounds": [
{},
{}
],
"dns": {
"servers": [
{
"tag": "app-dns-direct",
"address": "8.8.8.8"
}
]
},
"routing": {
"domainStrategy": "IPIfNonMatch",
"rules": [
{
"ruleTag": "Block ads",
"domain": [
"geosite:CATEGORY-ADS-ALL"
],
"outboundTag": "block"
},
{
"ruleTag": "GitHub through VPN",
"domain": [
"geosite:GITHUB"
],
"balancerTag": "proxy"
},
{
"ruleTag": "Direct domains",
"domain": [
"geosite:PRIVATE",
"geosite:APPLE",
"geosite:MICROSOFT",
"geosite:BING",
"geosite:CN"
],
"outboundTag": "direct"
},
{
"ruleTag": "Direct IPs",
"ip": [
"geoip:PRIVATE",
"geoip:CN"
],
"outboundTag": "direct"
}
]
}
}Protocol and operating-system conditions
Use protocol (http, tls, quic, bittorrent) and localOS (ios, android, darwin, windows, linux), both arrays. Protocol means sniffed application traffic, not VLESS/VMess. OS means the OS running Xray, not a remote server or an individual Android app.
{
"name": "Block QUIC on mobile",
"outbounds": [
{}
],
"dns": {
"servers": [
{
"tag": "app-dns-direct",
"address": "8.8.8.8"
}
]
},
"routing": {
"domainStrategy": "IPIfNonMatch",
"rules": [
{
"ruleTag": "Mobile QUIC",
"protocol": [
"quic"
],
"localOS": [
"ios",
"android"
],
"outboundTag": "block"
}
]
}
}FakeDNS
FakeDNS is off by default and is represented by an additional tagged DNS server, not a boolean root field. Include the direct server too; do not export generated pools. Explicit supported sniffing settings are retained.
{
"name": "FakeDNS routing",
"outbounds": [
{}
],
"dns": {
"servers": [
{
"tag": "app-dns-direct",
"address": "8.8.8.8"
},
{
"tag": "app-dns-fake",
"address": "fakedns"
}
]
},
"routing": {
"domainStrategy": "IPIfNonMatch",
"rules": [
{
"ruleTag": "Private networks",
"ip": [
"geoip:PRIVATE"
],
"outboundTag": "direct"
}
]
}
}Compatibility · FakeDNS limitations · import dependencies
Fragment
Optional Fragment is off by default. Each Smart or ordinary Custom route stores its own setting; see the defaults and template rules in Fragment routing.
For non-A/AAAA handling, an optional controlled dnsOut helper may follow the empty slots and Fragment. See other DNS queries for its exact boundary; arbitrary helper outbounds remain unsupported.