DNS — sing-box
sing-box's DNS engine is a list of typed servers plus a structured rule chain. Each server has a type selecting its transport (UDP, TLS, HTTPS, hosts file, fake-ip pool, DHCP-provided, systemd-resolved, Tailscale embedded). Rules use the same match-field set as routing rules but with DNS-specific actions.
Top-level options
| Field | Type | Default | Allowed values | Description |
|---|---|---|---|---|
servers | []DNSServerOptions | [] | [DNSServerOptions] | DNS server list. Each entry has a `type` (local, udp, tls, https, h3, dhcp, fakeip, hosts, resolved, tailscale) and the matching set of options. |
rules | []DNSRule | [] | [DNSRule] | DNS-level routing rules. Same shape as routing rules but with DNS-specific action set. |
final | string | (unset) | <server tag> | Default DNS server when no rule matches. Empty falls back to the first server in `servers[]`. |
reverse_mapping | bool | false | true | false | Maintain a reverse map (IP → domain) so rules can match on the original domain after a connection resolves the destination to an IP. |
Source: option/dns.go:21-27 · pinned at v1.13.11 (553cfa1)
Plus the embedded DNSClientOptions:
| Field | Type | Default | Allowed values | Description |
|---|---|---|---|---|
strategy | DomainStrategy | (prefer_ipv4) | prefer_ipv4 | prefer_ipv6 | ipv4_only | ipv6_only | Default address-family preference. |
disable_cache | bool | false | true | false | Disable the in-memory answer cache. |
disable_expire | bool | false | true | false | Don't evict cached entries by TTL — keep them indefinitely. |
independent_cache | bool | false | true | false | Use a separate cache per server tag (instead of one global cache). |
cache_capacity | uint32 | (unbounded) | <int> | Maximum cached entries. 0 disables the cap. |
client_subnet | *badoption.Prefixable | (unset) | <CIDR> | ECS (EDNS Client Subnet) advertised on outgoing queries. |
Source: option/dns.go:105-112 · pinned at v1.13.11 (553cfa1)
Server types
Every entry in servers[] has a type field. The matching fields:
type: "local"
Resolve via the OS resolver (handy on macOS/iOS where the system resolver is the authoritative path). Adds prefer_go to opt into the CGO-free Go resolver.
| Field | Type | Default | Allowed values | Description |
|---|---|---|---|---|
prefer_go | bool | false | true | false | Use Go's net.Resolver (CGO-free) instead of the platform-native resolver. Useful on systems where the native resolver is broken or rate-limited. |
Source: option/dns.go:376-379 · pinned at v1.13.11 (553cfa1)
type: "udp" and type: "tcp" — RemoteDNSServerOptions
{ "type": "udp", "tag": "local-udp", "server": "8.8.8.8", "server_port": 53 }| Field | Type | Default | Allowed values | Description |
|---|---|---|---|---|
server | string | (required) | <host> | Server hostname or IP. |
server_port | uint16 | 53 (udp/tcp), 853 (tls), 443 (https/h3) | <port> | Server port. |
Source: option/dns.go:332-335 · pinned at v1.13.11 (553cfa1)
type: "tls" — DNS-over-TLS
Same fields as udp/tcp plus the standard tls: block.
type: "https" and type: "h3" — DNS-over-HTTPS / -H3
| Field | Type | Default | Allowed values | Description |
|---|---|---|---|---|
path | string | /dns-query | /<path> | DoH endpoint path. |
method | string | POST | POST | GET | HTTP method used for DoH queries. |
headers | badoption.HTTPHeader | {} | {<header>: <value>} | Extra HTTP headers. |
Source: option/dns.go:394-399 · pinned at v1.13.11 (553cfa1)
type: "hosts" — local hosts file
| Field | Type | Default | Allowed values | Description |
|---|---|---|---|---|
path | badoption.Listable[string] | [/etc/hosts] | [<file path>] | List of hosts-file paths to merge. |
predefined | *badjson.TypedMap[string, badoption.Listable[netip.Addr]] | {} | {<domain>: [<IP>]} | Inline hosts table. Higher priority than `path`. |
Source: option/dns.go:363-366 · pinned at v1.13.11 (553cfa1)
type: "fakeip"
| Field | Type | Default | Allowed values | Description |
|---|---|---|---|---|
inet4_range | *badoption.Prefix | (required for v4) | <CIDR> | IPv4 CIDR allocated for fake IPs. |
inet6_range | *badoption.Prefix | (required for v6) | <CIDR> | IPv6 CIDR allocated for fake IPs. |
Source: option/dns.go:401-404 · pinned at v1.13.11 (553cfa1)
type: "dhcp"
| Field | Type | Default | Allowed values | Description |
|---|---|---|---|---|
interface | string | (auto) | <interface> | Interface whose DHCP-provided resolvers are used. |
Source: option/dns.go:406-409 · pinned at v1.13.11 (553cfa1)
Other types
type: "resolved"— read systemd-resolved's per-link nameservers (Linux only).type: "tailscale"— use the Tailscale daemon's MagicDNS resolver.type: "predefined"— return a hard-coded answer set.type: "rcode"— return a fixed RCODE (NOERROR / NXDOMAIN / SERVFAIL …).
DNS rules
DNS rules use the same polymorphic _Rule shape as routing rules (type: "default" or type: "logical") and the same 43 match-key set in RawDefaultDNSRule. The differences are at the action level:
| DNS action | Meaning |
|---|---|
route (default) | Send the query to server: "<tag>". Optional fields: strategy, disable_cache, rewrite_ttl, client_subnet. |
route-options | Apply DNS-route options to subsequent rule matches. |
reject | Drop the query. With method: "default" (NXDOMAIN) or method: "drop" (no response). |
predefined | Return a hard-coded answer (record set or RCODE). |
Examples
Two-server split — domestic over local DoH, everything else over Cloudflare DoH:
{
"dns": {
"servers": [
{ "type": "https", "tag": "local",
"server": "doh.pub", "path": "/dns-query" },
{ "type": "https", "tag": "remote",
"server": "cloudflare-dns.com", "path": "/dns-query",
"detour": "proxy" },
{ "type": "fakeip", "tag": "fakeip",
"inet4_range": "198.18.0.0/15",
"inet6_range": "fc00::/18" }
],
"rules": [
{ "rule_set": ["geosite-cn"], "server": "local" },
{ "outbound": "any", "server": "remote" }
],
"final": "remote",
"strategy": "prefer_ipv4"
}
}Block ads via DNS:
{
"dns": {
"servers": [
{ "type": "https", "tag": "main",
"server": "cloudflare-dns.com" }
],
"rules": [
{
"domain_keyword": ["ads", "doubleclick", "googlesyndication"],
"action": "reject",
"method": "default"
}
],
"final": "main"
}
}Notes
- The legacy
fakeip: {...}top-level option is deprecated — use atype: "fakeip"server entry instead. sing-box auto-migrates legacy configs at load time but emits a deprecation warning. reverse_mapping: truelets later routing rules match on the domain name even after sniffing has resolved it to an IP. Essential when using fake-ip alongside complex domain rules.independent_cache: trueis useful when you have servers that return legitimately different answers for the same domain (split-horizon resolvers).- DNS rules and routing rules share the same match-key vocabulary but operate at different times: DNS rules run on the resolver query; routing rules run on the resulting connection.
type: "resolved"reads fromsystemd-resolved's D-Bus API — works only on Linux systems where resolved is actually the running resolver.
Cross-core notes
- Xray-core uses a flat
dns:block withservers[]carrying URL strings (or NameServerConfig objects) — notypefield, the URL scheme decides the transport. See DNS — Xray-core. - mihomo has a 25-field
dns:block with separatenameserver/fallbacklists and anameserver-policymap. See DNS — mihomo.
Source: option/dns.go:21-409 · v1.13.11 (553cfa1)
