迁移 sing-box 的 rule-set,不要从旧规则文件开始改后缀。正确顺序是三段:在 route.rule_set 定义规则集,确认 format 是 source 还是 binary,再在 route.rules 里用 rule_set 引用对应 tag。这三段任何一段错位,表现都可能是启动失败、规则不命中,或者 remote 规则集一直下载失败。
如果你用 GUI 客户端导入 配套订阅线路,也要先确认客户端导出的 sing-box JSON 是否允许你手动维护 route.rule_set;有些客户端会把规则和出站分组重新生成,手改内容下次更新就被覆盖。
迁移前先分清两层 route
sing-box 的 route 里有两个容易混淆的位置。
第一层是 route.rule_set,它是规则集来源清单。这里写 type、tag、format、path、url、update_interval 等字段。官方文档写明 rule-set 从 sing-box 1.8.0 开始出现,local 和 remote 都要有 tag,format 是 source 或 binary。
第二层是 route.rules,它是真正的路由规则。你可以在某条 route rule 里写 rule_set: ["geosite-openai"],意思是匹配前面已定义的规则集 tag,然后执行 action 或送到某个 outbound。
最常见的误迁移是只写了 route.rules[].rule_set,却没有在 route.rule_set 里定义来源。JSON 语法可能没问题,但 sing-box 启动时会找不到对应 tag。
迁移表:旧写法应该落到哪里
| 旧配置或旧习惯 | sing-box 里对应位置 | 迁移动作 | 验证点 |
|---|---|---|---|
Clash / Mihomo 的 rule-providers | route.rule_set | 重建为 remote、local 或 inline rule-set | tag 能被 route.rules 引用 |
Clash 规则里的 RULE-SET,xxx,Proxy | route.rules[].rule_set + outbound | 把 xxx 改为 sing-box rule-set tag,把策略改成 outbound tag | debug 日志能看到匹配后走指定出站 |
旧 geosite / geoip 直引 | route.rule_set + route.rules[].rule_set | 优先迁到 source JSON 或 .srs,减少继续依赖旧字段 | 配置里不再保留已移除的顶层 route.geoip、route.geosite |
| 文本域名列表 | source JSON 的 domain_suffix、domain_keyword 等 headless rule | 先转成 JSON,再按需编译 .srs | sing-box rule-set compile 能成功 |
| IP CIDR 列表 | source JSON 的 ip_cidr | 和域名规则拆成不同文件或不同 tag | 测试 IP 流量时能命中对应 route rule |
.srs 文件 | format: "binary" | 保留 binary 格式,不要当 source JSON 编辑 | 本地 check 不报 rule-set 读取错误 |
这张表的关键点是:迁移不是一条旧规则对应一行新 JSON。sing-box 把“规则集文件在哪里”和“命中后走哪个出站”分开写,Mihomo/Clash 用户最容易漏掉这个层级差异。
最小 route 示例:先让一个规则集命中
下面是一个可读性优先的最小片段。它假设你已有一个本地 source JSON 规则集,文件路径是 ./rules/ai-services.json,命中后走 proxy,其余走 direct。
{
"route": {
"rule_set": [
{
"type": "local",
"tag": "ai-services",
"format": "source",
"path": "./rules/ai-services.json"
}
],
"rules": [
{
"rule_set": [
"ai-services"
],
"action": "route",
"outbound": "proxy"
}
],
"final": "direct"
}
}
如果你的 sing-box 版本还停在较旧配置风格,可能见到不写 action、只写 outbound 的 route rule。迁移时不要混着照抄网上片段,先看当前 core 版本支持的 route rule action;新版文档里已经把 action: "route" 和 outbound 放在同一条规则里。
source JSON 和 .srs binary 差在哪
source 是可编辑 JSON,适合放在 Git 仓库里审查差异;.srs 是 binary rule-set,适合客户端读取和分发。官方 source format 文档写了 version 和 rules 两个核心字段,并提供 sing-box rule-set compile --output <file-name>.srs <file-name>.json 作为编译命令。
一个最小 source JSON 可以长这样:
{
"version": 3,
"rules": [
{
"domain_suffix": [
"example.com",
"example.net"
]
},
{
"ip_cidr": [
"203.0.113.0/24"
]
}
]
}
这里的 rules 是 headless rule,不写 outbound。它只描述“匹配什么”,不描述“走哪里”。走哪里由外层 route.rules 决定。把 outbound 写进 source JSON,是从 Clash 迁移时常见的思维残留。
编译后引用 .srs 时,format 要改成 binary:
{
"route": {
"rule_set": [
{
"type": "local",
"tag": "ai-services",
"format": "binary",
"path": "./rules/ai-services.srs"
}
]
}
}
如果你用 remote rule-set,也按同样逻辑判断:URL 指向 .json 就写 source,指向 .srs 就写 binary。不要只相信 URL 名字,先下载看响应内容;有些订阅面板会在鉴权失败时返回 HTML,sing-box 读到的就不是规则集。
remote rule-set 的下载字段怎么处理
remote rule-set 最少需要 type、tag、format 和 url。update_interval 为空时官方文档写的是默认使用 1d。sing-box 1.14.0 文档还把 remote 下载相关能力迁到 http_client,并标注 download_detour 已废弃、计划在 1.16.0 移除。
一个 remote 示例:
{
"route": {
"rule_set": [
{
"type": "remote",
"tag": "private-ip",
"format": "source",
"url": "https://example.com/rules/private-ip.json",
"update_interval": "12h"
}
],
"rules": [
{
"rule_set": [
"private-ip"
],
"action": "route",
"outbound": "direct"
}
],
"final": "proxy"
}
}
如果你正在从旧配置迁到 1.14.x 之后的文档口径,看到 download_detour 不要继续扩写新模板。更稳妥的处理是:先让不带额外下载出站的 remote 规则集跑通,再按官方 http_client 字段补下载路径。客户端内置 core 没跟上时,GUI 可能还不认新字段。
本地验证命令按这个顺序跑
先确认 core 版本。不同 GUI 客户端打包的 sing-box core 可能落后于官网文档,命令行看到的版本才是你这次验证的基准。
sing-box version
再验证主配置能被解析。这里用 -c 指向你的完整配置文件。
sing-box check -c ./config.json
如果你维护 source JSON,先单独编译规则集,避免把 source 写错和主配置写错混在一起。
sing-box rule-set compile --output ./rules/ai-services.srs ./rules/ai-services.json
编译后再把主配置里的 format 改成 binary,跑一次完整检查。
sing-box check -c ./config.json
最后用 debug 日志看命中。不同启动方式参数可能略有差异,命令行场景通常可以先把 log.level 调到 debug,再启动一次。
sing-box run -c ./config.json
验证时只测两个样本:一个应该命中 rule-set 的域名或 IP,一个应该落到 final 的域名或 IP。样本太多会让日志很乱,也不利于确认是哪条规则生效。
迁移后常见错误怎么定位
| 现象 | 高概率原因 | 快速处理 |
|---|---|---|
check 报 unknown field | 抄了高版本字段,但本地 core 不支持 | 看 sing-box version,降回当前版本文档支持的字段 |
check 报 rule-set 读取失败 | format 和文件内容不一致 | JSON 用 source,.srs 用 binary |
| remote 下载失败 | URL 返回 HTML、证书错误、路径不可达或缓存目录不可写 | 用 curl -I 和 curl -L 看状态码与正文开头 |
| 规则集能加载但不命中 | route.rules[].rule_set 的 tag 拼错,或规则类型与流量类型不一致 | 对照 route.rule_set[].tag,再换一个最小域名样本 |
| IP 规则不命中 | DNS 结果、目标 IP 和 ip_cidr 样本不一致 | 用 dig 或客户端日志确认实际连接目标 |
| 从 Mihomo 搬来的 classical 规则乱了 | 规则里混有 domain、ipcidr、process 等多种类型 | 拆成多个 sing-box headless rule 文件再迁移 |
这里不要急着把 TUN、DNS、outbound 和 rule-set 一起改。rule-set 迁移本身只需要证明三件事:文件能读,tag 能引用,样本能命中。
哪些配置不要在这次迁移里顺手改
不要顺手重写 DNS。DNS rule、route rule 和 rule-set 都能影响分流结果,但它们不是同一个问题。先让 route.rule_set 跑通,再决定是否迁 DNS。
不要把所有历史规则合成一个巨大 .srs。域名、IP、进程、平台包名混在一起后,排错成本会变高。更可维护的做法是按用途拆 tag,例如 private-ip、ai-services、streaming-media、dev-tools。
不要把 GUI 自动生成的 JSON 当长期手写模板。SFA、Karing、Hiddify 这类客户端可能会在订阅更新时重写部分配置。需要长期维护 rule-set 的用户,最好保留一份独立的命令行配置或客户端支持的覆写文件。
迁移后可以继续看的三篇
如果错误集中在规则集缓存和 remote 下载,继续看 sing-box rule-set 缓存下载失败怎么排查。如果你同时在改 DNS、TUN 和 route,先用 sing-box TUN/DNS/route 日志排查 把日志样本缩小。还在从订阅格式迁到 sing-box JSON 的用户,可以把 sing-box 订阅格式迁移 放在 rule-set 迁移前面读。
迁移之外还要单独排的部分
本文没有测试每个 GUI 客户端对 http_client、remote rule-set 缓存和 route rule action 的封装差异。不同客户端内置 core 版本不同,界面能导入不代表所有字段都会被保留。
也不处理订阅转换器输出的配置质量。转换器生成的 JSON 可能能启动,但规则命名、出站 tag、DNS 和 TUN 仍要逐项看日志确认。
最后,rule-set 迁移不能代替服务端或节点连通性测试。route 命中正确只说明本地分流规则生效;出站能不能用,看 outbound 自身配置和客户端日志。