迁移 sing-box 的 rule-set,不要从旧规则文件开始改后缀。正确顺序是三段:在 route.rule_set 定义规则集,确认 formatsource 还是 binary,再在 route.rules 里用 rule_set 引用对应 tag。这三段任何一段错位,表现都可能是启动失败、规则不命中,或者 remote 规则集一直下载失败。

如果你用 GUI 客户端导入 配套订阅线路,也要先确认客户端导出的 sing-box JSON 是否允许你手动维护 route.rule_set;有些客户端会把规则和出站分组重新生成,手改内容下次更新就被覆盖。

迁移前先分清两层 route

sing-box 的 route 里有两个容易混淆的位置。

第一层是 route.rule_set,它是规则集来源清单。这里写 typetagformatpathurlupdate_interval 等字段。官方文档写明 rule-set 从 sing-box 1.8.0 开始出现,localremote 都要有 tagformatsourcebinary

第二层是 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-providersroute.rule_set重建为 remotelocalinline rule-settag 能被 route.rules 引用
Clash 规则里的 RULE-SET,xxx,Proxyroute.rules[].rule_set + outboundxxx 改为 sing-box rule-set tag,把策略改成 outbound tagdebug 日志能看到匹配后走指定出站
geosite / geoip 直引route.rule_set + route.rules[].rule_set优先迁到 source JSON 或 .srs,减少继续依赖旧字段配置里不再保留已移除的顶层 route.geoiproute.geosite
文本域名列表source JSON 的 domain_suffixdomain_keyword 等 headless rule先转成 JSON,再按需编译 .srssing-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 文档写了 versionrules 两个核心字段,并提供 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 最少需要 typetagformaturlupdate_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.srsbinary
remote 下载失败URL 返回 HTML、证书错误、路径不可达或缓存目录不可写curl -Icurl -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-ipai-servicesstreaming-mediadev-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 自身配置和客户端日志。