↓ 跳过正文
  1. 网络与运维/

新时代之sing-box:模块配置与工作原理详解

·24 分钟·
目录

sing-box 是一个用 Go 编写的通用代理平台:一个二进制既能当客户端(TUN 透明代理、本地 SOCKS/HTTP 代理),也能当服务端(VLESS、Hysteria2、Trojan 等),配置只有一份 JSON。它强大,但配置项多、版本迭代快,网上大量教程的写法在新版本里已经跑不起来了。

这篇文章按模块把 sing-box 拆开讲:每个模块负责什么、怎么配、流量经过它时发生了什么,最后给出一份完整可用的客户端和服务端配置。

版本说明:本文基于 sing-box 1.14.2(2026-09 的最新稳定版)。文中所有完整配置都用 sing-box check 校验过,并在本机实际运行、抓日志验证了分流行为。文中贴出的日志均来自实测。

一、先看清楚:旧教程里的这些写法已经失效
#

如果你是从旧教程(包括这篇文章的旧版本)过来的,先对照一下。下面这些写法在 1.12~1.14 陆续被删除,写了会直接启动失败:

旧写法新写法删除版本
路由/DNS 规则里的 geosite / geoip远程规则集 rule_set(.srs 文件)1.12
{"type": "dns"} 出站 + "outbound": "dns-out"路由动作 "action": "hijack-dns"1.13
{"type": "block"} 出站路由动作 "action": "reject"1.13
入站上的 sniff、sniff_override_destination路由动作 "action": "sniff"1.13
DNS 服务器 "address": "tls://8.8.8.8"、rcode://success"type": "tls", "server": "8.8.8.8";拦截用 reject / predefined 动作1.14
DNS 规则 "outbound": [...]、服务器 address_resolver拨号字段 domain_resolver / route.default_domain_resolver1.14
WireGuard 出站WireGuard 端点(endpoints)1.13

另外 1.14 还废弃(仍可用,1.16 删除)了几项:规则集的 download_detour(改用 http_clients)、DNS 的 independent_cache(缓存已默认按服务器隔离)、不带 match_response 的 DNS ip_cidr 规则(改用 evaluate + match_response,第九节详讲)。

二、整体架构:一条连接的旅程
#

理解 sing-box 最好的方式,是跟着一条连接走一遍:

flowchart LR
    A[应用程序] -->|系统流量| TUN[TUN 入站]
    A -->|SOCKS/HTTP| MIX[mixed 入站]
    TUN --> R{路由 route}
    MIX --> R
    R -->|sniff 嗅探| R
    R -->|DNS 查询 hijack-dns| D{DNS 模块}
    D --> DS1[国内 DNS]
    D --> DS2[国外 DNS 经代理]
    R -->|route| O1[direct 直连]
    R -->|route| O2[selector 代理组]
    R -->|reject| X[拒绝]
    O2 --> P1[VLESS+REALITY]
    O2 --> P2[Hysteria2]
    RS[(规则集 rule_set)] -.供匹配.-> R
    RS -.供匹配.-> D
  1. 入站(inbounds) 接住流量:TUN 从虚拟网卡截获整个系统的流量,mixed 以本地 SOCKS5/HTTP 代理的形式接收应用主动发来的流量。
  2. 路由(route) 按规则从上到下匹配,每条规则命中后执行一个动作:先嗅探协议(拿到域名),DNS 查询交给 DNS 模块,其余连接决定走哪个出站或直接拒绝。
  3. DNS 模块 有自己独立的一套服务器和规则,负责回答 DNS 查询。国内域名问国内 DNS,国外域名经代理问国外 DNS。
  4. 出站(outbounds) 真正把连接发出去:直连、某个代理节点,或者一个"代理组"(手动选择 / 自动测速)。
  5. 规则集(rule_set) 是路由和 DNS 共用的"域名/IP 名单",比如"所有中国大陆域名"。

配置文件骨架
#

1.14 的顶层结构如下,后面每节对应其中一块:

{
  "log": {},            // 日志
  "dns": {},            // DNS 服务器与 DNS 规则
  "ntp": {},            // 内置时间同步(可选)
  "certificate": {},    // 信任的 CA 证书(可选)
  "http_clients": [],   // 共享 HTTP 客户端,用于下载规则集等(1.14 新增)
  "endpoints": [],      // 端点:WireGuard、Tailscale 这类既能进又能出的
  "inbounds": [],       // 入站
  "outbounds": [],      // 出站
  "route": {},          // 路由规则、规则集
  "services": [],       // 独立服务(DERP、resolved 等,可选)
  "experimental": {}    // 缓存文件、Clash API 等
}

sing-box 的配置解析器支持 // 和 /* */ 注释,所以本文配置里的注释可以原样保留。

三个命令贯穿整个使用过程:

sing-box check -c config.json          # 校验配置(也会读取证书等文件)
sing-box format -w -c config.json      # 格式化并写回
sing-box run -c config.json            # 运行

三、日志 log
#

最简单的模块,但排错全靠它:

{
  "log": {
    "level": "info",      // 日志级别,见下
    "timestamp": true,    // 每行加时间
    "output": ""          // 写入文件;留空则输出到控制台
  }
}
  • 级别从低到高:trace debug info warn error fatal panic。
  • 日常用 info:每条连接会打印"从哪个入站进来、走了哪个出站"。
  • 排查分流问题时临时切到 debug:会打印每条连接命中了第几条规则、DNS 查询发给了谁。本文后面贴的日志都是 debug 级别。

四、入站 inbounds:流量从哪里进来
#

每个入站都有 type 和 tag,tag 用于在路由规则里引用(比如 "inbound": ["tun-in"])。监听类入站共享一组监听字段:listen、listen_port、tcp_fast_open、udp_timeout 等。

4.1 mixed:本地 SOCKS5 + HTTP 代理
#

{
  "type": "mixed",
  "tag": "mixed-in",
  "listen": "127.0.0.1",   // 只监听本机;改成 0.0.0.0 才能给局域网其他设备用
  "listen_port": 2080,
  "set_system_proxy": false  // true 时启动后自动设置系统代理
}

工作方式:应用主动把请求发给 127.0.0.1:2080,请求里带着目标域名(比如 www.google.com:443)。sing-box 拿到的是域名而不是 IP,所以通常不需要本地解析,代理出站会把域名原样交给服务端去解析,天然避免了 DNS 污染。

局限是只有"愿意走代理"的应用才会经过它:终端命令、游戏、很多 UDP 应用都不认系统代理。

如果要对外开放,务必加认证:"users": [{"username": "u", "password": "p"}]。

4.2 tun:接管整个系统的流量
#

{
  "type": "tun",
  "tag": "tun-in",
  "address": [
    "172.19.0.1/30",          // 虚拟网卡的 IPv4 地址
    "fdfe:dcba:9876::1/126"   // 虚拟网卡的 IPv6 地址
  ],
  "auto_route": true,         // 自动把系统默认路由指向 TUN
  "strict_route": true,       // 严格路由,防止流量绕过 TUN 泄漏
  "stack": "mixed"            // TCP/IP 栈:system / gvisor / mixed
}

工作原理:

  1. sing-box 创建一块虚拟网卡,auto_route 把系统默认路由改成指向它,于是所有应用发出的 IP 包都进了 sing-box。
  2. 虚拟网卡上收到的是 L3 的 IP 包,stack 负责把它们重新组装成 TCP/UDP 连接:system 借用操作系统的网络栈,gvisor 用 gVisor 实现的用户态网络栈,mixed 是 TCP 用 system、UDP 用 gVisor(有 gVisor 构建标记时默认就是它),一般选 mixed。
  3. 这时 sing-box 只知道目标 IP,不知道域名,所以路由规则里的第一步通常是 sniff(嗅探 TLS 的 SNI、HTTP 的 Host)把域名找回来,见第七节。
  4. DNS 查询同样被截获:1.14 的 dns_mode 默认 hijack,会把系统 DNS 设为 TUN 上的地址(默认是 address 第一个地址的下一个 IP,比如 172.19.0.2),并劫持 53 端口的 DNS 流量交给 sing-box 的 DNS 模块。

防回环:sing-box 自己发出去的流量(连接代理服务器)如果又被路由回 TUN,就成了死循环。所以用 TUN 时必须在 route 里开 auto_detect_interface: true,让出站连接绑定到真实的物理网卡。

TUN 需要管理员权限:Linux/macOS 用 root 运行,Windows 以管理员身份运行。

还有些常用字段:route_exclude_address(这些网段不进 TUN,比如公司内网)、include_package / exclude_package(Android 分应用代理)、platform.http_proxy(顺便给系统设一个 HTTP 代理)。

4.3 其他入站
#

类型用途
socks / http单一协议的本地代理,mixed 已经覆盖
redirect / tproxyLinux 透明代理(配合 iptables/nftables),软路由常用
direct原样转发端口,比如把本地 53 端口当 DNS 服务
vless / hysteria2 / trojan / shadowsocks / tuic / anytls …服务端入站,见第十三节

五、出站 outbounds:流量从哪里出去
#

出站分三类:代理协议出站(连接你的服务器)、direct 直连、代理组(selector / urltest,本身不连接,只是在其他出站之间做选择)。

5.1 代理协议出站:以 VLESS + REALITY 为例
#

{
  "type": "vless",
  "tag": "vless-reality",
  "server": "vps.example.com",     // 服务器地址(域名或 IP)
  "server_port": 443,
  "uuid": "7189a32f-1c95-480e-adfd-3f96b7d2060e",
  "flow": "xtls-rprx-vision",      // Vision 流控,避免"TLS 套 TLS"的特征
  "tls": {
    "enabled": true,
    "server_name": "www.microsoft.com",   // 伪装的目标站点(SNI)
    "utls": {
      "enabled": true,
      "fingerprint": "chrome"            // 模拟 Chrome 的 TLS 指纹
    },
    "reality": {
      "enabled": true,
      "public_key": "0qUcqyWeX_XGs3sbsowB6OgMI8vCiIoI1OuK9c4wvyo",
      "short_id": "14191e8010846d49"
    }
  }
}

REALITY 是怎么工作的:客户端发出的 TLS 握手看起来就是在访问 www.microsoft.com(SNI 和 Chrome 指纹都对得上)。服务端用私钥识别出这是自己人,就接管连接;如果不是(比如审查方的主动探测),服务端就把握手原样转发给真的 microsoft.com,探测者看到的是一个货真价实的微软证书。所以它不需要你自己的域名和证书,也极难被主动探测识别。

public_key / short_id 与服务端的 private_key / short_id 成对,生成方法见第十三节。

5.2 Hysteria2:基于 QUIC,弱网利器
#

{
  "type": "hysteria2",
  "tag": "hy2",
  "server": "vps.example.com",
  "server_port": 8443,
  "password": "ENywjWixW3adFk7teiqxOQ==",
  "tls": {
    "enabled": true,
    "server_name": "vps.example.com",   // 需要服务端有该域名的真实证书
    "alpn": ["h3"]
  }
}

Hysteria2 跑在 UDP/QUIC 上,自带拥塞控制,在高丢包、高延迟的跨国线路上比 TCP 类协议稳得多。可选字段:up_mbps / down_mbps(填了就用 Brutal 拥塞控制按带宽猛发,不填用 BBR)、obfs(Salamander 混淆,让流量不像 QUIC)、server_ports(端口跳跃)。

缺点是部分运营商会对大流量 UDP 限速或阻断,所以常和一个 TCP 协议搭配,放进代理组里互为备份。

5.3 公共的"拨号字段"
#

所有出站(以及 DNS 服务器、HTTP 客户端)都可以带一组拨号字段,控制"这个连接本身怎么建立"。最常用的几个:

字段作用
detour链式代理:先连到另一个出站,再从那里连出去。比如 DNS 服务器 "detour": "proxy" 表示经代理查询
domain_resolverserver 是域名时用哪个 DNS 服务器解析它。不填就用 route.default_domain_resolver
bind_interface绑定到指定网卡
routing_markLinux 下给连接打 fwmark
tcp_fast_open / tcp_multi_pathTFO / MPTCP
connect_timeout连接超时

domain_resolver 在 1.14 是必须理解的一项:代理服务器地址 vps.example.com 是个域名,连接它之前总得先解析。旧版本靠 DNS 规则里的 outbound 条件来处理,1.14 起改为在出站上(或全局 default_domain_resolver)直接指定一个 DNS 服务器。注意这种解析不经过 DNS 规则,直接发给指定的服务器。

5.4 direct:直连
#

{ "type": "direct", "tag": "direct" }

直连出站拿到域名目标时,会用 domain_resolver(或全局默认)在本地解析,然后直接连接。

5.5 代理组:selector 与 urltest
#

[
  {
    "type": "selector",           // 手动选择
    "tag": "proxy",
    "outbounds": ["auto", "vless-reality", "hy2"],
    "default": "auto"
  },
  {
    "type": "urltest",            // 自动测速选最快
    "tag": "auto",
    "outbounds": ["vless-reality", "hy2"],
    "url": "https://www.gstatic.com/generate_204",
    "interval": "3m",             // 测速间隔,默认 3m
    "tolerance": 50               // 新节点至少快 50ms 才切换,避免来回抖动
  }
]
  • selector 本身不连接任何东西,它把连接转交给当前选中的那个出站。当前选择可以通过 Clash API(第十一节)在面板或客户端里切换,开启 cache_file 后重启也能记住。
  • urltest 定期对每个成员请求一次 url,测延迟,自动选最快的。成员挂了会自动切走,相当于故障转移。
  • 两者可以嵌套:路由规则只引用 proxy,至于 proxy 具体走哪个节点,交给代理组决定。路由规则和节点选择由此解耦,换节点不用改规则。

5.6 常见代理协议怎么选
#

协议传输特点适合
VLESS + REALITY + VisionTCP无需域名证书,抗主动探测强,性能好首选主力
Hysteria2UDP/QUIC弱网表现最好,需证书跨国高丢包线路、备用
TUICUDP/QUIC与 Hysteria2 类似,更"标准 QUIC"同上
TrojanTCP + TLS伪装成普通 HTTPS 站,需域名证书配合真实网站/CDN
Shadowsocks 2022TCP/UDP简单轻量(2022-blake3-aes-128-gcm),但无 TLS 伪装中转、内网、落地
AnyTLSTCP + TLS专门对抗"TLS 套 TLS"流量特征新兴选择

VMess 与第一代 Hysteria 仍然支持,但已没有选它们的理由。

六、端点 endpoints:WireGuard 与 Tailscale
#

有些协议天然是双向的:既能作为出站把流量发进隧道,又能接收隧道里对端发来的连接。1.11 起它们被归为"端点",放在 endpoints 里,在路由规则里用法和出站一样(用 tag 引用)。

{
  "type": "wireguard",
  "tag": "wg-ep",
  "address": ["10.0.0.2/32"],
  "private_key": "<本机私钥>",
  "peers": [
    {
      "address": "wg.example.com",
      "port": 51820,
      "public_key": "<对端公钥>",
      "allowed_ips": ["0.0.0.0/0", "::/0"]
    }
  ]
}

典型用法是访问家里/公司内网:路由规则里写 {"ip_cidr": ["192.168.50.0/24"], "outbound": "wg-ep"},访问这个网段的流量就进 WireGuard 隧道。tailscale 端点同理,可以直接把 sing-box 作为一个 Tailscale 节点加入你的 tailnet。

七、路由 route:分流的核心
#

7.1 工作原理
#

每条连接进来后,路由模块从 rules 的第一条开始按顺序匹配:

  • 规则里的各个条件字段之间是且(AND)关系,同一个字段里的多个值是或(OR)关系。
  • 命中后执行该规则的动作(action,不写默认是 route)。
  • 动作分两种:最终动作(route、reject、hijack-dns、bypass)执行后匹配结束;非最终动作(sniff、resolve、route-options)执行后继续往下匹配。
  • 一条规则都没命中,走 final 指定的出站。

7.2 规则动作
#

动作类型作用
route最终走指定出站("outbound": "direct")
reject最终拒绝连接:TCP 回 RST、UDP 回 ICMP 不可达;"method": "drop" 则静默丢弃
hijack-dns最终把这个 DNS 请求交给 sing-box 的 DNS 模块回答
sniff非最终嗅探协议和域名(TLS SNI、HTTP Host、QUIC、DNS、BitTorrent 等),之后的规则就能按域名、协议匹配
resolve非最终把目标域名解析成 IP,之后的规则就能按 IP 匹配
route-options非最终调整连接参数,如 udp_timeout、tls_fragment(TLS 分片)

为什么第一条规则几乎总是 sniff:TUN 截获的连接只有目标 IP;即便是 mixed 入站,拿到的"域名"也可能是客户端自己解析后的 IP。先嗅探一遍,后面的域名规则才有东西可匹配。嗅探还会识别出 dns 协议,紧接着一条 hijack-dns 就能把 DNS 查询交给 DNS 模块。

7.3 匹配条件
#

最常用的条件字段:

类别字段
域名domain(完整匹配)、domain_suffix(后缀,google.com 同时匹配自身和所有子域)、domain_keyword、domain_regex
目标 IPip_cidr、ip_is_private(局域网/保留地址)
规则集rule_set(引用第八节的规则集,最常用)
端口 / 网络port、port_range、network(tcp/udp)
协议protocol(需要先 sniff):tls、http、quic、dns、stun、bittorrent…
来源inbound(入站 tag)、source_ip_cidr、source_port
进程process_name、process_path、package_name(Android)
环境clash_mode(Clash API 的模式)、wifi_ssid、network_type

需要复杂逻辑时用逻辑规则:

{
  "type": "logical",
  "mode": "and",                     // and / or
  "rules": [
    { "rule_set": "geoip-cn", "invert": true },   // 非中国 IP
    { "network": "udp", "port": 443 }            // 且是 QUIC
  ],
  "action": "reject"                 // 拒绝海外 QUIC,逼浏览器回落到 TCP
}

7.4 route 的其他字段
#

{
  "route": {
    "rules": [],
    "rule_set": [],                        // 规则集定义,见第八节
    "final": "proxy",                      // 兜底出站
    "auto_detect_interface": true,         // 出站绑定物理网卡,TUN 必开,防回环
    "default_domain_resolver": "local",    // 出站解析服务器域名时默认用的 DNS
    "default_http_client": "via-proxy"     // 下载远程规则集默认用的 HTTP 客户端
  }
}

7.5 本文使用的路由规则
#

{
  "rules": [
    { "action": "sniff" },                                    // 0 先嗅探
    { "protocol": "dns", "action": "hijack-dns" },            // 1 DNS 查询交给 DNS 模块
    { "ip_is_private": true, "outbound": "direct" },          // 2 局域网直连
    { "clash_mode": "direct", "outbound": "direct" },         // 3 面板切到"直连"模式
    { "clash_mode": "global", "outbound": "proxy" },          // 4 面板切到"全局"模式
    { "rule_set": "geosite-category-ads-all", "action": "reject" },       // 5 广告拒绝
    { "rule_set": ["geosite-cn", "geoip-cn"], "outbound": "direct" }      // 6 国内直连
  ]
  // 其余走 final: proxy
}

顺序的原则:先做非最终动作(嗅探),再处理特殊流量(DNS、局域网),再处理模式开关,最后是按名单分流。越具体的规则越靠前。

实测,通过 mixed 入站访问 doubleclick.net 时的日志(数字是规则下标):

router: match[0] => sniff
router: sniffed protocol: tls, domain: doubleclick.net
router: match[5] rule_set=geosite-category-ads-all => reject

访问 www.baidu.com:

router: match[0] => sniff
router: match[6] rule_set=[geosite-cn geoip-cn] => route(direct)
outbound/direct[direct]: outbound connection to www.baidu.com:443

一个细节:规则 6 里的 geoip-cn 按目标 IP 匹配。mixed 入站收到的是域名,没有 IP,所以这条对它只有 geosite-cn 在起作用;TUN 模式下目标本来就是 IP,两个名单都能生效。如果确实需要在 mixed 下按 IP 分流,可以在前面加一条 {"action": "resolve"},代价是所有域名都要先在本地解析一次。

八、规则集 rule_set:分流用的名单
#

规则集就是"一组规则打包成一个文件",由路由和 DNS 规则通过 rule_set 字段引用。它取代了旧的 geosite/geoip 数据库:按需加载(用到哪个下哪个)、支持自动更新、内存占用小得多。

8.1 三种来源
#

{
  "rule_set": [
    {
      "type": "remote",                  // 远程:定期自动下载
      "tag": "geosite-cn",
      "url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-cn.srs",
      "update_interval": "1d"            // 可选,默认 1d
    },
    {
      "type": "local",                   // 本地文件:修改后自动重新加载
      "tag": "my-rules",
      "path": "my-rules.srs"
    },
    {
      "type": "inline",                  // 内联:直接写在配置里
      "tag": "my-inline",
      "rules": [{ "domain_suffix": ["example.org"] }]
    }
  ]
}

格式有两种:source(JSON,可读)和 binary(.srs,编译后的二进制,更小更快)。URL/路径以 .json 或 .srs 结尾时 format 可以省略。

官方维护的名单在 SagerNet/sing-geosite(域名类,geosite-xxx.srs)和 SagerNet/sing-geoip(IP 类,geoip-xxx.srs)的 rule-set 分支里。常用的有:geosite-cn、geosite-geolocation-!cn(非中国站点)、geosite-category-ads-all(广告)、geoip-cn,以及按服务分的 geosite-openai、geosite-netflix、geosite-telegram 等。

开启 experimental.cache_file 后,远程规则集会被缓存,重启时不用重新下载。

8.2 自己写规则集
#

{
  "version": 3,
  "rules": [
    { "domain_suffix": ["newzhxu.com"] },
    { "ip_cidr": ["67.230.176.0/24"] }
  ]
}

规则集里的规则叫"无头规则":只有匹配条件,没有动作(动作由引用它的规则决定)。version 取能满足所用字段的最低版本即可,越低兼容的客户端越多。编译成二进制:

sing-box rule-set compile my-rules.json     # 生成 my-rules.srs

8.3 调试:某个域名/IP 在不在名单里
#

$ sing-box rule-set match -f binary geosite-cn.srs www.bilibili.com
match rules.[0]: domain/domain_suffix=<binary> ...

$ sing-box rule-set match -f binary geoip-cn.srs 220.181.111.1
match rules.[0]: ip_cidr=<binary>

$ sing-box rule-set match -f binary geoip-cn.srs 67.230.176.31
                                  # 没有输出 = 不匹配

分流不符合预期时,先用它确认名单本身,比盯着规则猜快得多。

九、DNS:最容易配错的模块
#

9.1 为什么 DNS 要单独分流
#

两个问题:

  • DNS 污染:在国内直接查询被墙的域名,会拿到错误的 IP。实测把 www.google.com 交给 223.5.5.5 查询,返回的是 104.244.42.197,这是 Twitter 的 IP,典型的污染结果。
  • DNS 泄漏与 CDN 调度:反过来,国内网站如果交给海外 DNS 查询,会被 CDN 调度到海外节点,访问变慢;而把所有查询都发给国内 DNS,你访问了哪些海外网站也就一览无余。

所以思路是:国内域名问国内 DNS,海外域名经代理问海外 DNS,不确定的域名看解析结果再决定。

9.2 DNS 服务器
#

1.14 的 DNS 服务器用 type 区分协议:

{
  "servers": [
    {
      "type": "udp",               // 传统 UDP DNS
      "tag": "local",
      "server": "223.5.5.5"
    },
    {
      "type": "https",             // DoH
      "tag": "remote",
      "server": "1.1.1.1",         // 用 IP 就不需要再解析;用域名则需要 domain_resolver
      "detour": "proxy"            // 经代理发出,出口在海外
    }
  ]
}
type说明
udp / tcp传统 DNS,端口默认 53
tlsDoT,默认 853
https / h3DoH / DoH over HTTP/3,默认路径 /dns-query
quicDoQ
local用系统的解析器
dhcp使用 DHCP 下发的 DNS
hosts读 hosts 文件或自定义静态映射
fakeip返回假 IP,见 9.5
tailscale / resolved / mdns 等特定场景

注意:新格式的 DNS 服务器默认是直连发出的(旧格式默认走默认出站)。要让它走代理必须显式写 detour。

9.3 DNS 规则与动作
#

DNS 规则和路由规则的机制一样:按顺序匹配,命中后执行动作。条件字段也大体相同(domain_suffix、rule_set、clash_mode、query_type…)。动作有:

动作作用
route发给指定服务器("server": "local"),默认动作
reject拒绝,返回 REFUSED
predefined直接返回预设记录,比如 "rcode": "NXDOMAIN",或自定义 A 记录
evaluate先查一次并暂存结果,然后继续匹配(1.14 新增)
respond直接返回之前 evaluate 暂存的结果
route-options调整参数,如 rewrite_ttl、client_subnet

9.4 本文的 DNS 配置,以及 evaluate 是怎么工作的
#

{
  "dns": {
    "servers": [
      // local 和 remote,见上
    ],
    "rules": [
      { "clash_mode": "direct", "server": "local" },                           // 0
      { "clash_mode": "global", "server": "remote" },                          // 1
      { "rule_set": "geosite-category-ads-all", "action": "reject" },          // 2 广告
      { "rule_set": "geosite-cn", "server": "local" },                         // 3 国内名单
      { "rule_set": "geosite-geolocation-!cn", "server": "remote" },           // 4 海外名单
      { "action": "evaluate", "server": "local" },                             // 5 其余:先用国内 DNS 试查
      { "match_response": true, "rule_set": "geoip-cn", "action": "respond" }  // 6 结果是国内 IP 就采用
    ],
    "final": "remote",            // 否则交给 remote 重新查
    "strategy": "prefer_ipv4"
  }
}

规则 5、6 是 1.14 的新写法,处理"两个名单都不在"的域名:

  1. evaluate 先用国内 DNS 查一次,把响应暂存起来,然后继续往下匹配;
  2. 规则 6 的 match_response: true 表示"拿暂存的响应来匹配",geoip-cn 检查响应里的 IP 是否在国内;
  3. 是国内 IP,就 respond 直接返回这个结果,这类站点大概率是国内服务,就用国内解析;
  4. 不是,就落到 final,经代理问海外 DNS 重新查一次,拿到干净的结果。

用 dig 实测四类域名,每个查询命中的 DNS 规则:

www.baidu.com       dns: match[3] rule_set=geosite-cn => route(local)
www.google.com      dns: match[4] rule_set=geosite-geolocation-!cn => route(remote)
ads.doubleclick.net dns: match[2] rule_set=geosite-category-ads-all => reject
h.newzhxu.com       dns: match[5] => evaluate(local)

最后一个是本站域名,不在任何名单里。完整日志展示了整个过程:

router: sniffed packet protocol: dns
router: match[1] protocol=dns => hijack-dns          ← 路由模块把 DNS 交给 DNS 模块
dns: exchange h.newzhxu.com. IN A
dns: match[5] => evaluate(local)                     ← 先用国内 DNS 试查
dns: exchanged A h.newzhxu.com. 58 IN A 67.230.176.31
outbound/...: outbound connection to 1.1.1.1:443     ← 不是国内 IP,改走 remote(DoH 经代理)
旧写法为什么不行了:以前常见 {"rule_set": "geoip-cn", "server": "local"} 这种直接在 DNS 规则里写 IP 规则集的写法。它语义含糊:查询还没发出去,哪来的 IP?1.14 起这种写法被废弃,引用纯 IP 规则集却不带 match_response 的 DNS 规则会在启动时被拒绝,必须改成上面的 evaluate + match_response。

9.5 FakeIP(可选)
#

FakeIP 是另一种思路:DNS 查询时不真的解析,而是从一个保留网段(如 198.18.0.0/15)里分配一个假 IP 返回,并记住"假 IP ↔ 域名"的映射。应用连接这个假 IP 时,sing-box 查表还原出域名,按域名分流,交给代理服务端去解析。

优点是 DNS 查询零延迟、完全没有污染和泄漏问题;缺点是假 IP 会被应用和系统缓存,关掉 sing-box 后可能短暂断网,某些需要真实 IP 的应用(比如部分游戏、P2P)会出问题。配置方法(在 local、remote 之后追加一个 fakeip 服务器):

{
  "servers": [
    // ...前面的 local、remote
    {
      "type": "fakeip",
      "tag": "fakeip",
      "inet4_range": "198.18.0.0/15",
      "inet6_range": "fc00::/18"
    }
  ],
  "rules": [
    { "query_type": ["A", "AAAA"], "rule_set": "geosite-geolocation-!cn", "server": "fakeip" }
  ]
}

注意 fakeip 服务器不能作为默认服务器:既不能排在 servers 第一位,也不能做 final,否则启动报 default server cannot be fakeip。只用 query_type 限定 A/AAAA 查询走它,其他类型(如 HTTPS、TXT 记录)仍走真实 DNS。配合 cache_file 的 store_fakeip: true 可以在重启后保留映射。

9.6 两条 DNS 路径
#

最后厘清一个容易混淆的点。sing-box 里其实有两条解析路径:

  1. 应用发来的 DNS 查询:经 hijack-dns 进入 DNS 模块,走 DNS 规则。
  2. sing-box 自己需要解析域名(代理服务器地址、direct 出站的目标):走 domain_resolver / default_domain_resolver 指定的服务器,不走 DNS 规则。

实测直连 www.baidu.com 时,dns: lookup domain www.baidu.com 直接用了 default_domain_resolver 指定的 local,没有任何 dns: match 日志。所以 default_domain_resolver 应当指向一个直连可用的 DNS,否则会出现"要连代理先得解析代理域名,要解析又得先连代理"的死锁。

十、HTTP 客户端 http_clients
#

1.14 新增的顶层模块:定义可复用的 HTTP 客户端,目前主要给远程规则集下载用(将来还有其他需要发 HTTP 请求的功能)。

{
  "http_clients": [
    {
      "tag": "via-proxy",
      "detour": "proxy"        // 拨号字段:经代理下载
    }
  ],
  "route": {
    "default_http_client": "via-proxy"   // 远程规则集默认用它下载
  }
}

单个规则集也可以用 "http_client": "tag" 单独指定,取代了已废弃的 download_detour。raw.githubusercontent.com 在国内直连不稳定,所以这里让它走代理。

十一、其余模块
#

11.1 experimental:缓存文件与 Clash API
#

{
  "experimental": {
    "cache_file": {
      "enabled": true,       // 缓存到 cache.db
      "store_dns": true      // 持久化 DNS 缓存(取代已废弃的 store_rdrc)
    },
    "clash_api": {
      "external_controller": "127.0.0.1:9090",
      "default_mode": "rule",
      "secret": ""           // 监听非本机地址时务必设置
    }
  }
}
  • cache_file 会记住:selector 当前选择的节点、Clash 模式、远程规则集内容、FakeIP 映射,以及开启 store_dns 后的 DNS 缓存。
  • clash_api 提供兼容 Clash 的 REST 接口,于是可以用 metacubexd、zashboard 这类 Web 面板查看实时连接、切换节点、切换模式。路由/DNS 规则里的 clash_mode 条件就是和面板上的"规则 / 全局 / 直连"开关联动的。可以用 external_ui 让 sing-box 直接托管面板静态文件。

11.2 ntp、certificate、services
#

  • ntp:内置 NTP 客户端。某些协议(VMess、REALITY 的 max_time_difference)对时间敏感,在没有系统时间同步的设备(路由器、容器)上可以打开:{"enabled": true, "server": "time.apple.com"}。
  • certificate:指定信任的 CA 证书来源,"store": "mozilla" 使用 Mozilla 的 CA 列表而不是系统证书库,或者追加自签 CA。
  • services:独立于代理的服务,比如 derp(Tailscale 中继服务器)、resolved(在 Linux 上模拟 systemd-resolved)。

十二、完整客户端配置
#

把上面的模块拼起来,就是一份完整的客户端配置。它同时提供 TUN(接管全局)和 mixed(本地 127.0.0.1:2080 代理)两个入站,两个节点由 urltest 自动选优,selector 允许手动覆盖。

{
  "log": {
    "level": "info",
    "timestamp": true
  },
  "dns": {
    "servers": [
      {
        "type": "udp",
        "tag": "local",
        "server": "223.5.5.5"
      },
      {
        "type": "https",
        "tag": "remote",
        "server": "1.1.1.1",
        "detour": "proxy"
      }
    ],
    "rules": [
      {
        "clash_mode": "direct",
        "server": "local"
      },
      {
        "clash_mode": "global",
        "server": "remote"
      },
      {
        "rule_set": "geosite-category-ads-all",
        "action": "reject"
      },
      {
        "rule_set": "geosite-cn",
        "server": "local"
      },
      {
        "rule_set": "geosite-geolocation-!cn",
        "server": "remote"
      },
      {
        "action": "evaluate",
        "server": "local"
      },
      {
        "match_response": true,
        "rule_set": "geoip-cn",
        "action": "respond"
      }
    ],
    "final": "remote",
    "strategy": "prefer_ipv4"
  },
  "http_clients": [
    {
      "tag": "via-proxy",
      "detour": "proxy"
    }
  ],
  "inbounds": [
    {
      "type": "tun",
      "tag": "tun-in",
      "address": [
        "172.19.0.1/30",
        "fdfe:dcba:9876::1/126"
      ],
      "auto_route": true,
      "strict_route": true,
      "stack": "mixed"
    },
    {
      "type": "mixed",
      "tag": "mixed-in",
      "listen": "127.0.0.1",
      "listen_port": 2080
    }
  ],
  "outbounds": [
    {
      "type": "selector",
      "tag": "proxy",
      "outbounds": [
        "auto",
        "vless-reality",
        "hy2"
      ],
      "default": "auto"
    },
    {
      "type": "urltest",
      "tag": "auto",
      "outbounds": [
        "vless-reality",
        "hy2"
      ],
      "url": "https://www.gstatic.com/generate_204",
      "interval": "3m",
      "tolerance": 50
    },
    {
      "type": "vless",
      "tag": "vless-reality",
      "server": "vps.example.com",
      "server_port": 443,
      "uuid": "7189a32f-1c95-480e-adfd-3f96b7d2060e",
      "flow": "xtls-rprx-vision",
      "tls": {
        "enabled": true,
        "server_name": "www.microsoft.com",
        "utls": {
          "enabled": true,
          "fingerprint": "chrome"
        },
        "reality": {
          "enabled": true,
          "public_key": "0qUcqyWeX_XGs3sbsowB6OgMI8vCiIoI1OuK9c4wvyo",
          "short_id": "14191e8010846d49"
        }
      }
    },
    {
      "type": "hysteria2",
      "tag": "hy2",
      "server": "vps.example.com",
      "server_port": 8443,
      "password": "ENywjWixW3adFk7teiqxOQ==",
      "tls": {
        "enabled": true,
        "server_name": "vps.example.com",
        "alpn": [
          "h3"
        ]
      }
    },
    {
      "type": "direct",
      "tag": "direct"
    }
  ],
  "route": {
    "rules": [
      {
        "action": "sniff"
      },
      {
        "protocol": "dns",
        "action": "hijack-dns"
      },
      {
        "ip_is_private": true,
        "outbound": "direct"
      },
      {
        "clash_mode": "direct",
        "outbound": "direct"
      },
      {
        "clash_mode": "global",
        "outbound": "proxy"
      },
      {
        "rule_set": "geosite-category-ads-all",
        "action": "reject"
      },
      {
        "rule_set": [
          "geosite-cn",
          "geoip-cn"
        ],
        "outbound": "direct"
      }
    ],
    "rule_set": [
      {
        "type": "remote",
        "tag": "geosite-cn",
        "url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-cn.srs"
      },
      {
        "type": "remote",
        "tag": "geosite-geolocation-!cn",
        "url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-geolocation-!cn.srs"
      },
      {
        "type": "remote",
        "tag": "geosite-category-ads-all",
        "url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-category-ads-all.srs"
      },
      {
        "type": "remote",
        "tag": "geoip-cn",
        "url": "https://raw.githubusercontent.com/SagerNet/sing-geoip/rule-set/geoip-cn.srs"
      }
    ],
    "final": "proxy",
    "auto_detect_interface": true,
    "default_domain_resolver": "local",
    "default_http_client": "via-proxy"
  },
  "experimental": {
    "cache_file": {
      "enabled": true,
      "store_dns": true
    },
    "clash_api": {
      "external_controller": "127.0.0.1:9090",
      "default_mode": "rule"
    }
  }
}

使用前替换:server(两处)、uuid、public_key、short_id、password,与服务端保持一致。运行(TUN 需要 root):

sudo sing-box run -c config.json

一个请求的完整路径
#

以在浏览器打开 https://www.google.com 为例(TUN 模式):

  1. 浏览器先查 DNS,查询包进入 TUN,路由 sniff 识别为 DNS 协议,规则 1 hijack-dns 交给 DNS 模块;
  2. DNS 规则 4 命中 geosite-geolocation-!cn,经代理向 1.1.1.1 发 DoH 查询,拿到正确的 IP;
  3. 浏览器向该 IP 的 443 端口发起 TLS 连接,进入 TUN;
  4. 路由规则 0 sniff 从 TLS ClientHello 里读出 SNI www.google.com;
  5. 规则 2~6 都不命中,走 final: proxy,selector 把连接交给 urltest 选出的节点,比如 vless-reality;
  6. VLESS 出站以 REALITY 握手连上服务器,把域名 www.google.com 交给服务端,服务端在海外解析并连接。

十三、服务端配置
#

对应上面客户端的服务端,在 VPS 上运行:

{
  "log": {
    "level": "info",
    "timestamp": true
  },
  "inbounds": [
    {
      "type": "vless",
      "tag": "vless-in",
      "listen": "::",
      "listen_port": 443,
      "users": [
        {
          "name": "me",
          "uuid": "7189a32f-1c95-480e-adfd-3f96b7d2060e",
          "flow": "xtls-rprx-vision"
        }
      ],
      "tls": {
        "enabled": true,
        "server_name": "www.microsoft.com",
        "reality": {
          "enabled": true,
          "handshake": {
            "server": "www.microsoft.com",
            "server_port": 443
          },
          "private_key": "4FqBLltJaY30k5AwdYhxEbOgyAIM-dyMTmps-E1nv3E",
          "short_id": [
            "14191e8010846d49"
          ]
        }
      }
    },
    {
      "type": "hysteria2",
      "tag": "hy2-in",
      "listen": "::",
      "listen_port": 8443,
      "users": [
        {
          "name": "me",
          "password": "ENywjWixW3adFk7teiqxOQ=="
        }
      ],
      "masquerade": "https://www.bing.com",
      "tls": {
        "enabled": true,
        "alpn": [
          "h3"
        ],
        "certificate_path": "/etc/sing-box/cert.pem",
        "key_path": "/etc/sing-box/key.pem"
      }
    }
  ],
  "outbounds": [
    {
      "type": "direct",
      "tag": "direct"
    }
  ],
  "route": {
    "rules": [
      {
        "action": "sniff"
      },
      {
        "protocol": "bittorrent",
        "action": "reject"
      },
      {
        "ip_is_private": true,
        "action": "reject"
      }
    ],
    "final": "direct"
  }
}

要点:

  • REALITY 的 handshake 是被"借用"的真实站点,选一个国外大站、支持 TLS 1.3 且与 VPS 延迟低的,server_name 与它保持一致。
  • Hysteria2 需要真实证书,server_name 用你自己的域名;masquerade 让非 Hysteria2 客户端访问时看到一个正常网站(这里反代 bing)。
  • 服务端的路由规则拒绝了 BT(避免 VPS 被投诉)和访问服务器内网(防止被当跳板)。

生成密钥和 ID:

sing-box generate reality-keypair   # PrivateKey 填服务端,PublicKey 填客户端
sing-box generate uuid              # VLESS 用户 UUID
sing-box generate rand --hex 8      # REALITY short_id
sing-box generate rand --base64 16  # Hysteria2 密码

我把这份服务端和第十二节的客户端(去掉 TUN)在本机对接跑过:两个入站都正常认证了用户 me,客户端经 VLESS+REALITY 和 Hysteria2 两条链路访问目标网站均返回 200,远程规则集也是经代理下载成功的。

十四、排错清单
#

1. 启动报 initialize rule-set ... initial rule-set: xxx: Get "https://raw.githubusercontent.com/...": ...

首次启动时本地没有规则集缓存,sing-box 必须先把远程规则集下载下来。本文配置让下载走代理,如果节点本身不通(地址、密钥写错),规则集就下载失败,整个进程直接退出。我拿一个不存在的服务器地址实测,确实会得到这个 FATAL。解决办法任选其一:

  • 先确认节点可用;
  • 给规则集加 "initial_path": "geosite-cn.srs",提前放一份文件在本地,启动不再被首次下载阻塞;
  • 把 http_clients 的 detour 改成 direct,前提是你的网络能直连 GitHub。

2. 启动报 missing route.default_domain_resolver or domain_resolver in dial fields ...

只要配置了两个及以上 DNS 服务器,1.14 就要求明确指定出站用哪个 DNS 解析服务器域名,否则拒绝启动。加上 "route": {"default_domain_resolver": "local"} 即可(只有一个 DNS 服务器时可省略)。

3. 代理服务器是域名,日志报 lookup vps.example.com ... 失败

检查 default_domain_resolver(或出站上的 domain_resolver)是否指向一个直连可用的 DNS。不能指向本身需要走代理的 DNS。

4. 开 TUN 后断网或 CPU 飙高

多半是路由回环:确认 route.auto_detect_interface: true;Linux 上如果同时有其他透明代理或 VPN,注意 route_exclude_address。

5. 分流不符合预期

  • 把 log.level 临时调成 debug,看 router: match[N] 命中的是第几条规则;
  • 用 sing-box rule-set match 确认域名/IP 在不在名单里;
  • 记得规则从上往下、命中即停,检查是不是被前面更宽泛的规则截走了;
  • mixed 入站拿到的是域名,按 IP 的规则(geoip-cn、ip_cidr)对它不生效,见 7.5 节。

6. 升级后配置报错

对照第一节的表格,或看官方的迁移指南。sing-box check 会对部分已废弃字段输出 WARN(比如 independent_cache),别忽略它们。但它并不覆盖所有废弃项:实测 download_detour 就不会提示,升级前最好对一遍废弃功能列表。

参考资料
#

叶知秋
作者
叶知秋
写后端的,日常和 Java、Go、数据库、消息队列打交道,也折腾网络和自托管。这里记录实践中搞明白的东西。

相关文章