DNS — Xray-core
The dns block configures Xray's internal resolver: a list of servers, optional static hosts, and a handful of caching / fallback toggles. The DNS engine also exposes itself as an internal inbound (tagged dns by default), so DNS traffic can be routed like any other connection.
Top-level options
| Field | Type | Default | Allowed values | Description |
|---|---|---|---|---|
servers | []*NameServerConfig | [] | [NameServerConfig | <string>] | Resolvers list. Each entry is either an `address` string (`8.8.8.8`, `https://dns.google/dns-query`, `tcp+local://`, `fakedns`, …) or a full NameServerConfig object. |
hosts | *HostsWrapper | {} | {<domain or pattern>: <IP | [IP, …]>} | Static hosts. Keys support the same `full:`, `domain:`, `regexp:`, `keyword:`, `geosite:` prefixes as routing rules. |
clientIp | *Address | (unset) | <IP> | ECS (EDNS Client Subnet) advertised on outgoing queries. Helps CDN-aware resolvers return a geographically near answer. |
tag | string | dns | <inbound tag> | Internal tag for the DNS-engine inbound. Used by routing rules that want to single out DNS traffic. |
queryStrategy | string | UseIP | UseIP | UseIPv4 | UseIPv6 | Default query strategy applied when a per-server strategy isn't set. `UseIP` queries both A and AAAA; `UseIPv4`/`UseIPv6` skips the other family. |
disableCache | bool | false | true | false | Disable the in-memory answer cache. Each query goes out fresh. |
serveStale | bool | false | true | false | Serve expired cached entries while a refresh runs in the background. Improves perceived latency. |
serveExpiredTTL | uint32 | 0 | <seconds> | Maximum seconds an expired entry is served when `serveStale` is on. 0 means unbounded. |
disableFallback | bool | false | true | false | Don't fall back to later servers when the first server doesn't return a usable answer. |
disableFallbackIfMatch | bool | false | true | false | Same, but only when the matched server itself has a `domains` list — i.e. matched-by-domain servers never trigger fallback. |
enableParallelQuery | bool | false | true | false | Query all matching servers in parallel; first valid answer wins. |
useSystemHosts | bool | false | true | false | Also consult the OS's `/etc/hosts` (or platform equivalent) before going to network. |
Source: infra/conf/dns.go:160-173 · pinned at v26.6.1 (94ffd50)
servers[] — NameServerConfig
Each entry is either a bare address string (parsed into address) or the verbose object form:
| Field | Type | Default | Allowed values | Description |
|---|---|---|---|---|
address | *Address | (required) | <DNS URL> | Resolver address. Bare IP (`8.8.8.8` = UDP/53), `tcp://`, `tls://`, `https://`, `quic://`, `tcp+local://...` (use local DNS without proxy), `localhost`, or `fakedns`. |
clientIp | *Address | (inherit) | <IP> | Per-server ECS override. |
port | uint16 | 53 | <port> | Server port (only for bare IP addresses). |
skipFallback | bool | false | true | false | When true and this server returns no answer, the fallback chain is *not* tried — the query simply fails. |
domains | StringList | [] | <domain pattern> | Domains this server should answer. Same syntax as routing rules. If non-empty, the server is consulted only for these domains. |
expectedIPs | StringList | [] | <IP / CIDR / geoip:> | Accept this server's answer only when the resolved IP matches one of these patterns. Used to filter out censored or hijacked responses. |
expectIPs | StringList | (alias) | (alias of expectedIPs) | Compatibility alias for `expectedIPs`. |
queryStrategy | string | (inherit) | UseIP | UseIPv4 | UseIPv6 | Per-server strategy override. |
tag | string | (unset) | <string> | Server tag — referenced by routing rules that route the resolver's outgoing traffic. |
timeoutMs | uint64 | 4000 | <ms> | Per-query timeout in milliseconds. |
disableCache | *bool | (inherit) | true | false | Per-server cache override. |
serveStale | *bool | (inherit) | true | false | Per-server serve-stale override. |
serveExpiredTTL | *uint32 | (inherit) | <seconds> | Per-server stale-TTL ceiling. |
finalQuery | bool | false | true | false | Mark this server as the final fallback — if it answers, no further resolution happens. |
unexpectedIPs | StringList | [] | <IP / CIDR / geoip:> | Reject this server's answer if the resolved IP matches any of these patterns. Complement of `expectedIPs`. |
Source: infra/conf/dns.go:19-35 · pinned at v26.6.1 (94ffd50)
hosts
The hosts field is a flat map. Keys support the same prefixes as routing rules; values are either a single IP or an array of IPs.
{
"dns": {
"hosts": {
"domain:example.com": "203.0.113.10",
"domain:internal.corp": ["10.0.0.1", "10.0.0.2"],
"geosite:cn": "8.8.8.8"
}
}
}A value of "fakedns" for the IP makes Xray hand back a fake address from the FakeDNS pool.
Examples
Standard split-resolver — CN domains via local DoH, everything else via Cloudflare DoH:
{
"dns": {
"servers": [
{
"address": "https://doh.pub/dns-query",
"domains": ["geosite:cn"],
"expectedIPs": ["geoip:cn"]
},
"https://1.1.1.1/dns-query",
{
"address": "tcp+local://223.5.5.5",
"domains": ["geosite:cn"]
}
],
"hosts": {
"domain:example.com": "203.0.113.10"
},
"queryStrategy": "UseIP",
"disableFallbackIfMatch": true,
"enableParallelQuery": false
}
}FakeDNS routing:
{
"dns": {
"servers": ["fakedns", "1.1.1.1"]
},
"fakeDns": { "ipPool": "198.18.0.0/15", "poolSize": 32768 },
"inbounds": [{
"port": 1080,
"protocol": "socks",
"sniffing": {
"enabled": true,
"destOverride": ["http", "tls", "fakedns"]
}
}]
}Notes
- A bare
8.8.8.8string inservers[]is shorthand for{"address": "8.8.8.8"}—NameServerConfig'sUnmarshalJSON(infra/conf/dns.go:36-43) accepts both shapes. address: "localhost"uses the OS resolver (handy on platforms with a stub resolver like systemd-resolved).address: "tcp+local://..."queries via TCP bypassing the routing engine — useful for the bootstrap resolver that the rest of the DNS setup depends on.domainspatterns: bare strings are suffix match by default; prefix withfull:for exact,regexp:for regex,keyword:for substring,geosite:for GeoSite category.expectedIPs/unexpectedIPstogether implement the "reject poisoned answers" pattern — setexpectedIPs: ["geoip:cn"]on the domestic resolver andunexpectedIPs: ["geoip:cn"]on the public one.clientIp(and per-serverclientIp) sets the EDNS Client Subnet option. Use a realistic value (e.g. one of your IPs) to get CDN-aware answers.finalQuery: truemarks a server as terminal — once it answers, no fallback runs.
dns outbound
Distinct from the dns block above, Xray ships a dns outbound ("protocol": "dns"). Route DNS traffic to it and it either forwards each query to a fixed resolver or answers it according to a rule list. Recent Xray added destination rewrite fields and a rules engine (the older nonIPQuery / blockTypes knobs are now legacy).
| Field | Type | Default | Allowed values | Description |
|---|---|---|---|---|
rewriteNetwork | Network | (unset) | tcp | udp | tcp,udp | Transport queries are rewritten to. Canonical name for the legacy `network`. |
rewriteAddress | *Address | (unset) | <host> | Resolver that intercepted DNS queries are rewritten to. Canonical name for the legacy `address`. |
rewritePort | uint16 | 0 | <port> | Port queries are rewritten to. Canonical name for the legacy `port`. |
network | Network | (alias) | (alias of rewriteNetwork) | Legacy alias for `rewriteNetwork`. |
address | *Address | (alias) | (alias of rewriteAddress) | Legacy alias for `rewriteAddress`. |
port | uint16 | (alias) | (alias of rewritePort) | Legacy alias for `rewritePort`. |
userLevel | uint32 | 0 | <uint32> | Policy level applied to this outbound's connections. |
rules | []*DNSOutboundRuleConfig | (unset) | [DNSOutboundRuleConfig] | Per-query rules evaluated in order; the first match decides how the query is handled. |
nonIPQuery | *string | (legacy) | reject | drop | skip | Legacy non-IP-query handling. Deprecated in favour of `rules`, and cannot be combined with it. |
blockTypes | *[]int32 | (legacy) | [<qType number>] | Legacy list of DNS query-type numbers to block. Deprecated in favour of `rules`. |
Source: infra/conf/dns_proxy.go:60-71 · pinned at v26.6.1 (94ffd50)
rules[]
| Field | Type | Default | Allowed values | Description |
|---|---|---|---|---|
action | string | (required) | direct | drop | return | hijack | What to do with a matching query. `direct` forwards as-is; `drop` discards it; `return` answers locally with `rCode`; `hijack` redirects it to the rewrite server. |
qType | *PortList | (any) | <DNS type number / range> | Match these DNS query types by number (1 = A, 28 = AAAA, 65 = HTTPS, …). Accepts comma/range lists. |
domain | *StringList | (any) | <domain pattern> | Match these domains (substring by default; the same pattern prefixes as routing rules). |
rCode | uint32 | 0 | 0-65535 | Response code returned when `action` is `return` (e.g. 5 = REFUSED, 3 = NXDOMAIN). |
Source: infra/conf/dns_proxy.go:13-18 · pinned at v26.6.1 (94ffd50)
Hijack A/AAAA to a fixed resolver, drop HTTPS-record (type 65) lookups, and refuse anything else:
{
"outbounds": [
{
"tag": "dns-out",
"protocol": "dns",
"settings": {
"rewriteAddress": "1.1.1.1",
"rewritePort": 53,
"rewriteNetwork": "udp",
"rules": [
{ "action": "hijack", "qType": "1,28" },
{ "action": "drop", "qType": "65" },
{ "action": "return", "rCode": 5 }
]
}
}
]
}nonIPQuery and blockTypes cannot be mixed with rules — doing so is a startup error. Migrate legacy configs to rules.
Cross-core notes
- sing-box uses a polymorphic server-by-type schema (
type: "https",type: "tls",type: "fakeip", …) plus structured DNS rules withaction: "route"|"reject"|"predefined". See DNS — sing-box. - mihomo uses a flat
dns:block with separatenameserver/fallbacklists plus a powerfulnameserver-policymap for per-domain routing. See DNS — mihomo.
Source: infra/conf/dns.go:19-173 · v26.6.1 (94ffd50)
