Skip to content

Routing — sing-box

sing-box's route block holds rules, rule-sets, and a handful of default-interface / process-lookup toggles. Rules are polymorphic (default + logical) and carry an explicit action selecting one of seven behaviors.

Top-level options

FieldTypeDefaultAllowed valuesDescription
geoip*GeoIPOptions(legacy)GeoIPOptionsLegacy GeoIP database location and download settings. Deprecated in favor of rule-sets.
geosite*GeositeOptions(legacy)GeositeOptionsLegacy GeoSite database. Deprecated in favor of rule-sets.
rules[]Rule[][Rule]Routing rules, evaluated in order.
rule_set[]RuleSet[][RuleSet]Named rule-sets — referenced from rules via `rule_set` match keys. Three types: `inline`, `local`, `remote`.
finalstring(unset)<outbound tag>Default outbound when no rule matches. When unset, the first outbound in `outbounds` is used.
find_processboolfalsetrue | falseLook up the originating process for every connection. Required by `process_name` / `process_path` rules.
auto_detect_interfaceboolfalsetrue | falseAuto-discover the system's default outbound interface — used by Direct outbounds without `bind_interface`.
override_android_vpnboolfalsetrue | falseAndroid only — bypass the system VPN service for outbound dials.
default_interfacestring(auto)<interface>Override the default outbound interface. Overrides `auto_detect_interface`.
default_markFwMark0<uint32>Linux SO_MARK applied to outbound sockets.
default_domain_resolver*DomainResolveOptions(none)DomainResolveOptionsDefault resolver for destination domains when no rule specifies one.
default_network_strategy*NetworkStrategy(unset)NetworkStrategyDefault network-strategy used by Happy-Eyeballs / cellular-vs-wifi dial races.
default_network_typebadoption.Listable[InterfaceType][]<InterfaceType>Preferred network types for the default outbound.
default_fallback_network_typebadoption.Listable[InterfaceType][]<InterfaceType>Fallback network types when the preferred one fails.
default_fallback_delaybadoption.Duration0<duration>Delay before switching to a fallback network.

Source: option/route.go:5-21 · pinned at v1.13.11 (553cfa1)

Rules

Each entry in rules[] is a polymorphic Rule object. The type field decides the shape:

  • type: "default" (or omitted) — flat RawDefaultRule with match fields + a RuleAction.
  • type: "logical" — boolean combinator over nested rules.

Default rule — match fields

RawDefaultRule has 41 fields, all optional and AND-combined when multiple are set. The most-used:

FieldTypeDescription
inbound[]stringMatch by inbound tag.
network[]stringtcp, udp, or tcp,udp.
protocol[]stringSniffed application protocol.
domain / domain_suffix / domain_keyword / domain_regex[]stringDomain matchers.
geosite[]stringGeoSite category — read from rule-set in modern configs.
geoip / source_geoip[]stringGeoIP category.
ip_cidr / source_ip_cidr[]stringCIDR match.
ip_is_private / source_ip_is_privateboolMatch RFC1918 / link-local / loopback.
port / source_port[]uint16Discrete-port match.
port_range / source_port_range[]stringPort-range match (80:90, 1024:, …).
process_name / process_path / process_path_regex[]stringProcess match (needs find_process: true).
package_name[]stringAndroid package name (UID resolved via the system API).
user / user_id[]string / []int32Local user / UID.
clash_modestringMatch only when the runtime mode (controlled via the Clash API) matches this string.
wifi_ssid / wifi_bssid[]stringWifi-aware routing (mobile-only).
network_is_expensive / network_is_constrainedbooliOS/macOS network-type flags.
rule_set[]stringMatch if any of the named rule-sets matches.
invertboolInvert the entire rule's match result.

Logical rule

json
{
  "type": "logical",
  "mode": "and",
  "rules": [ <Rule>, <Rule>,  ],
  "invert": false,
  "action": "..."
}

mode is and (default) or or. Nested rules can themselves be logical.

Rule action

Every rule carries an action that decides what happens on match. Eight action values:

ActionMeaning
route (default)Send to outbound. Extra fields: override_address, override_port, network_strategy, udp_*, tls_fragment*.
route-optionsApply route options to subsequent matching without leaving the rule chain.
directDirect dial — bypass outbounds entirely (uses default_interface etc.).
bypassSame shape as route, deliberately distinct for logging.
rejectDrop the connection. With method: "drop" or method: "default".
hijack-dnsHijack the connection as if it were DNS, routing to the DNS engine.
sniffRun protocol sniffing on the connection.
resolveResolve the destination domain via the named DNS server before further rules run.

Rule-sets

FieldTypeDefaultAllowed valuesDescription
typestringinlineinline | local | remoteWhere the rule set lives. `inline` holds the rules right in this config; `local` reads from a file path; `remote` downloads from a URL.
tagstring(required)<string>Reference name used by rules' `rule_set` match key.
formatstring(inferred)source | binaryFile format. `source` is JSON; `binary` is the compiled `.srs` format. Inferred from the file extension when unset.

Source: option/rule_set.go:20-27 · pinned at v1.13.11 (553cfa1)

Local rule-set

json
{ "type": "local", "tag": "cn", "format": "binary", "path": "geosite-cn.srs" }

Remote rule-set

json
{
  "type": "remote",
  "tag": "cn",
  "format": "binary",
  "url": "https://example.com/geosite-cn.srs",
  "download_detour": "direct",
  "update_interval": "168h"
}

Inline rule-set

json
{
  "type": "inline",
  "tag": "block",
  "rules": [
    { "domain_keyword": ["ads", "tracker"] }
  ]
}

The inline form's rules are HeadlessRule — structurally identical to routing rules but without the action field (the action is whatever the referencing rule does).

Examples

CN-direct + everything else through proxy:

json
{
  "route": {
    "rule_set": [
      { "type": "remote", "tag": "geoip-cn", "format": "binary",
        "url": "https://github.com/SagerNet/sing-geoip/raw/rule-set/geoip-cn.srs" },
      { "type": "remote", "tag": "geosite-cn", "format": "binary",
        "url": "https://github.com/SagerNet/sing-geosite/raw/rule-set/geosite-cn.srs" }
    ],
    "rules": [
      { "ip_is_private": true, "outbound": "direct" },
      { "rule_set": ["geoip-cn", "geosite-cn"], "outbound": "direct" },
      { "action": "sniff" },
      { "protocol": "dns", "action": "hijack-dns" }
    ],
    "final": "proxy",
    "find_process": false,
    "auto_detect_interface": true
  }
}

Reject ads with an inline rule-set:

json
{
  "route": {
    "rule_set": [
      {
        "type": "inline",
        "tag": "ads",
        "rules": [
          { "domain_keyword": ["doubleclick", "googlesyndication"] }
        ]
      }
    ],
    "rules": [
      { "rule_set": "ads", "action": "reject" }
    ]
  }
}

Logical OR rule:

json
{
  "type": "logical",
  "mode": "or",
  "rules": [
    { "domain_suffix": [".onion"] },
    { "geoip": ["tor-exit"] }
  ],
  "outbound": "tor"
}

Notes

  • geoip and geosite top-level options are legacy. The modern workflow is to load equivalent data via rule_set (remote .srs files) and reference it through the rule's rule_set match field.
  • rule_set_ip_cidr_match_source (snake_case in the struct) controls whether a rule-set's IP-CIDR rules match source or destination. The older spelling rule_set_ipcidr_match_source is deprecated.
  • The sniff and resolve rule actions are typically used at the head of the rule list to ensure later rules see useful metadata. The DNS engine relies on hijack-dns to capture DNS queries the routing engine wants to handle.
  • clash_mode only takes effect if the Clash API is enabled — that's where the mode comes from.

Cross-core notes

  • Xray-core uses a single polymorphic rule shape with camelCase field names and a much smaller match-key set. There is no action enum — every rule routes to an outboundTag or balancerTag. See Routing — Xray-core.
  • mihomo uses compact one-line string rules (DOMAIN-SUFFIX,example.com,proxy) and a separate rule-providers: mechanism for remote rule lists. See Routing — mihomo.

Source: option/route.go:5-21 · v1.13.11 (553cfa1)

Core Tutorial by Argsment