Skip to content

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

FieldTypeDefaultAllowed valuesDescription
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.
finalstring(unset)<server tag>Default DNS server when no rule matches. Empty falls back to the first server in `servers[]`.
reverse_mappingboolfalsetrue | falseMaintain 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:

FieldTypeDefaultAllowed valuesDescription
strategyDomainStrategy(prefer_ipv4)prefer_ipv4 | prefer_ipv6 | ipv4_only | ipv6_onlyDefault address-family preference.
disable_cacheboolfalsetrue | falseDisable the in-memory answer cache.
disable_expireboolfalsetrue | falseDon't evict cached entries by TTL — keep them indefinitely.
independent_cacheboolfalsetrue | falseUse a separate cache per server tag (instead of one global cache).
cache_capacityuint32(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.

FieldTypeDefaultAllowed valuesDescription
prefer_goboolfalsetrue | falseUse 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

json
{ "type": "udp", "tag": "local-udp", "server": "8.8.8.8", "server_port": 53 }
FieldTypeDefaultAllowed valuesDescription
serverstring(required)<host>Server hostname or IP.
server_portuint1653 (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

FieldTypeDefaultAllowed valuesDescription
pathstring/dns-query/<path>DoH endpoint path.
methodstringPOSTPOST | GETHTTP method used for DoH queries.
headersbadoption.HTTPHeader{}{<header>: <value>}Extra HTTP headers.

Source: option/dns.go:394-399 · pinned at v1.13.11 (553cfa1)

type: "hosts" — local hosts file

FieldTypeDefaultAllowed valuesDescription
pathbadoption.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"

FieldTypeDefaultAllowed valuesDescription
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"

FieldTypeDefaultAllowed valuesDescription
interfacestring(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 actionMeaning
route (default)Send the query to server: "<tag>". Optional fields: strategy, disable_cache, rewrite_ttl, client_subnet.
route-optionsApply DNS-route options to subsequent rule matches.
rejectDrop the query. With method: "default" (NXDOMAIN) or method: "drop" (no response).
predefinedReturn a hard-coded answer (record set or RCODE).

Examples

Two-server split — domestic over local DoH, everything else over Cloudflare DoH:

json
{
  "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:

json
{
  "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 a type: "fakeip" server entry instead. sing-box auto-migrates legacy configs at load time but emits a deprecation warning.
  • reverse_mapping: true lets 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: true is 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 from systemd-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 with servers[] carrying URL strings (or NameServerConfig objects) — no type field, the URL scheme decides the transport. See DNS — Xray-core.
  • mihomo has a 25-field dns: block with separate nameserver / fallback lists and a nameserver-policy map. See DNS — mihomo.

Source: option/dns.go:21-409 · v1.13.11 (553cfa1)

Core Tutorial by Argsment