在我的软路由和各台设备上,订阅转换是一个每天都在默默工作的工具。
原版 subconverter 很成熟,我用了好几年。但最近这两年,在日常维护网络时,有几个痛点一直没有得到彻底解决:
- 新协议跟不上。现在的节点大量使用 Hysteria 2、TUIC v5 和 VLESS Reality。老工具对这些新协议支持不全,转换出来的配置要么缺少字段报错,要么直接把节点吞了。
- 内核生态演进。Clash 停止维护后,内核全面转向了 Clash.Meta(mihomo)和 sing-box。老工具生成的配置无法直接利用 rule-providers 等现代特性。
- 软路由部署太繁琐。原版基于 C++ 开发,依赖 Boost、libcurl、QuickJS 等一整套库。家里有几台 MIPS / ARM 架构的老路由器,想要交叉编译一份静态二进制非常折腾。
- 订阅投毒导致全家断网。客户端通常设置了定时拉取。某天上游机场接口波动或规则文件下载超时,转换器返回了一个 HTTP 200,内容却是一段 HTML 报错文本。客户端误以为更新成功,直接冲掉了本地正在正常使用的配置,整个局域网瞬间断网。
我不想在老旧的代码库里反复打补丁。花了一段时间,我用 Go 把它彻底重写了一遍:subconverter-ng。
subconverter-ng 的核心特性
重做工具最重要的是平稳过渡,不能破坏现有的工作习惯。
1. 100% 保持老接口不变(无缝平替)
转换接口沿用原版 URL 参数规范:
/sub?target=clash&url=<订阅地址>&config=<规则地址>
无论你在 Clash、Surge 还是自建面板里配置了什么规则参数(如 target=clash、config=ACL4SSR...、insert=true 等),直接把服务地址切到 subconverter-ng,老客户端无需改动任何配置。
2. 原生支持现代协议与 mihomo / sing-box 标准
解析层和生成层全部重构,默认支持所有主流新协议:
- 输入协议:Hysteria(v1 / v2)、TUIC v5、VLESS(Reality / XTLS-Vision)、WireGuard、AnyTLS;
- 传统协议:Shadowsocks、ShadowsocksR、VMess、Trojan、SOCKS5 完整兼容;
- 输出目标:
clash(对齐 mihomo 最新规则规范)、singbox、surge、shadowrocket、quanx、loon、v2ray/mixed; - 规则集兼容:完整支持 subconverter 外部 INI 规则(
ruleset=、custom_proxy_group=),兼容 ACL4SSR 规则集并支持 rule-providers 模式(expand=false)。
3. 单静态二进制与 OpenWrt 软路由适配
得益于 Go 的交叉编译能力,程序是一个没有动态链接库依赖的纯静态文件:
- 提供 macOS(Apple Silicon / Intel)、Linux(amd64 / arm64)、Windows 构建包;
- 针对低算力软路由(如 MT7621 芯片的 OpenWrt 路由器),提供
linux_mips_softfloat、linux_mipsle_softfloat与linux_armv7架构文件; - 丢进路由器就能跑,常驻内存占用通常在 20MB 左右。
生产级网络韧性与安全加固
除了协议转换本身,我在代码里加固了几个日常踩过坑的底层细节:
避免订阅投毒:失败严格返回 502/422
订阅更新最怕“假成功”。
在 subconverter-ng 中,只要上游机场拉取失败、DNS 超时或配置解析异常,服务严禁返回 HTTP 200,统一返回 502 Bad Gateway 或 422 Unprocessable Entity。
客户端识别到非 200 状态码时,会自动保留本地现有的有效配置继续运行,避免了配置被空文件冲掉而断网。
敏感 Token 脱敏保护
机场的订阅 URL 里带有个人专属 Token。如果程序报错或打印日志时把完整 URL 暴露出来,很容易在查日志或排障时泄露凭据。
服务内置了凭据脱敏机制:所有进入 log/slog 日志和 HTTP 错误回显的 URL,Query 参数与路径中的 Token 会自动被替换为 ***。
物理连接层防御 SSRF 与 DNS 重绑定
转换服务需要主动请求外部链接。为了避免恶意请求探测内网,程序在 TCP DialContext 连接握手的物理层校验目标 IP:凡是解析到私有局域网网段(10.0.0.0/8、172.16.0.0/12、192.168.0.0/16)、回环地址或 Link-Local 地址,直接连接熔断阻断。
SingleFlight 跨请求并发折叠
家里多台设备如果碰巧在同一秒触发订阅更新,同时向上游发起请求,很容易触发机场的并发风控。
程序基于 SingleFlight 实现了请求合并:针对相同的订阅 URL,并发到来的多个请求会自动折叠为一次实际网络抓取,结果共享返回,大幅减少上游压力。
部署与使用指南
方式一:下载预编译二进制
前往 GitHub Releases 页面 下载对应平台的二进制文件:
# 启动守护进程,默认监听 25500 端口
./subconverter-ng serve --listen :25500
启动后,浏览器打开 http://127.0.0.1:25500/,可以使用内置的可视化 Web 页面快速生成订阅链接。
方式二:Docker 容器与 FlareSolverr 联动
官方提供多架构 Docker 镜像。如果部分机场设置了 Cloudflare 5 秒盾,可通过 docker-compose.yml 配合 FlareSolverr 自动绕过:
version: '3.8'
services:
subconverter-ng:
image: ghcr.io/jungley8/subconverter-ng:latest
container_name: subconverter-ng
restart: unless-stopped
ports:
- "25500:25500"
environment:
- SUBNG_LISTEN=:25500
- SUBNG_FLARESOLVERR_URL=http://flaresolverr:8191/v1
flaresolverr:
image: ghcr.io/flaresolverr/flaresolverr:latest
container_name: flaresolverr
restart: unless-stopped
方式三:CLI 单次离线转换
如果你不希望跑后台常驻进程,只想在本地脚本中处理节点,直接使用 CLI 命令即可:
# 离线将本地节点文件转为 sing-box 配置文件
./subconverter-ng convert --url ./nodes.txt --target singbox -o config.json
常见问题与解答 (FAQ)
Q1: 切换到 subconverter-ng 需要修改客户端原有的订阅链接吗?
不需要。subconverter-ng 完全兼容原版 subconverter 的 Query 参数格式(如 target=clash、url=...、config=... 等)。你只需要把原本指向旧服务的域名或端口替换为当前运行的地址即可。
Q2: 为什么上游拉取失败时返回 502 能防止断网?
大部分客户端(如 Clash、Surge、Shadowrocket)都有本地配置缓存机制。当拉取更新遇到非 200 HTTP 状态码时,客户端会判定“本次更新失败”,从而继续沿用本地现存的配置文件;如果服务端返回 200 但内容是报错信息,客户端就会将报错内容当作新配置保存,导致原本正常的网络中断。
Q3: 支持哪些低算力软路由设备?
Release 预编译包直接提供了 linux_mips_softfloat、linux_mipsle_softfloat(如联发科 MT7621 系列 OpenWrt 路由器)以及 linux_armv7 架构,下载后赋予执行权限即可在软路由后台直接启动,无需在路由器上安装 Go 环境或配置复杂编译链。
项目开源地址与文档
代码与配套文档均已开源:
- GitHub 仓库:https://github.com/Jungley8/subconverter-ng
- 官方文档站:https://sub.jungley.net
- 站点作品收录:在做的事 - subconverter-ng
如果你也在寻找一款轻量、安全、且支持现代新协议的订阅转换工具,欢迎尝试体验。遇到问题可以在 GitHub 提 issue。