-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Subscription JSON Template
Settings → Subscription → JSON
This template controls what …/sub/<client name>?format=json returns — the sing-box
configuration your users' clients download.
The template is not a sing-box configuration. It is a settings object that the panel merges with the outbounds it generates for each client. The result is a sing-box configuration; the template is the part you supply.
That distinction has a practical consequence: the template is flatter than a sing-box
config. Routing options are written at the top level, and the panel assembles the
route section itself.
Pasting a complete sing-box config into the template is the most common mistake. Keys the panel does not recognize are ignored silently, so the result is a config that is missing exactly the parts you cared about — typically rule-sets, which then produce:
initialize dns router: dns rule[2]: rule-set not found: direct-geosite
The panel's form is the intended way to edit the template and always produces the right shape. The raw JSON editor is there for options the form does not cover, and expects that same shape.
Anything not in these two tables is ignored.
| Key | Notes |
|---|---|
log |
sing-box log options |
dns |
The complete DNS section: servers, rules, final, strategy
|
inbounds |
Replaces the default TUN + mixed inbounds entirely |
experimental |
Clash API, cache file, and so on |
http_clients |
Named HTTP clients for downloading remote rule-sets |
| Key | Notes |
|---|---|
rules |
Replaces the default rules entirely, in the order you write them |
rule_set |
Rule-set definitions, local or remote |
final |
Outbound for unmatched traffic. Defaults to proxy
|
default_domain_resolver |
DNS server that resolves outbound server domains |
default_http_client |
HTTP client used by remote rule-sets |
override_android_vpn |
Passed through as-is |
You do not write these, and writing them has no effect:
outbounds — always built from the client's inbounds and any external links, plus
three entries the panel adds at the front:
| Tag | Type | Contents |
|---|---|---|
proxy |
selector |
auto, direct, then every node |
auto |
urltest |
Every node, tested against http://www.gstatic.com/generate_204 every 10m |
direct |
direct |
— |
Refer to proxy, auto and direct from your rules; they are always present. Node tags
come from the inbound tags, with -2, -3 suffixes only if two would otherwise collide.
route.auto_detect_interface — always enabled.
route.rules — when the template defines no rules, the panel supplies a default set:
sniffing, then Clash Direct mode → direct, then Clash Global mode → proxy. As soon
as you define rules yourself, none of those defaults are added. Include what you want
explicitly — {"action": "sniff"} in particular is easy to forget and changes how
domain-based rules behave.
route.default_domain_resolver — when you leave it unset and the template has more than
one DNS server, the panel fills it in with dns.final (or the first server) so sing-box
does not have to guess and log a deprecation warning. Set it explicitly to control which
server resolves outbound server domains.
http_clients — when a remote rule-set does not name an HTTP client and the template
declares none, the panel adds a client tagged default and points route.default_http_client
at it. This only avoids a deprecation warning from sing-box 1.14; if you declare your own
http_clients, the panel leaves the decision entirely to you.
Everything omitted, so every default applies: default inbounds, default rules, no DNS
section, final: proxy.
{}The scenario from the top of this page, written correctly. Note that rules and rule_set
sit next to log, not inside a route object.
{
"log": { "level": "info", "timestamp": true },
"rules": [
{ "action": "sniff" },
{ "protocol": "dns", "action": "hijack-dns" },
{ "ip_is_private": true, "action": "route", "outbound": "direct" },
{ "rule_set": "geosite-ir", "action": "route", "outbound": "direct" },
{ "rule_set": "geoip-ir", "action": "route", "outbound": "direct" },
{ "rule_set": "category-ads", "action": "reject" }
],
"rule_set": [
{
"type": "remote",
"tag": "geosite-ir",
"format": "binary",
"url": "https://raw.githubusercontent.com/Chocolate4U/Iran-sing-box-rules/rule-set/geosite-ir.srs",
"download_detour": "direct"
},
{
"type": "remote",
"tag": "geoip-ir",
"format": "binary",
"url": "https://raw.githubusercontent.com/Chocolate4U/Iran-sing-box-rules/rule-set/geoip-ir.srs",
"download_detour": "direct"
},
{
"type": "remote",
"tag": "category-ads",
"format": "binary",
"url": "https://raw.githubusercontent.com/Chocolate4U/Iran-sing-box-rules/rule-set/geosite-category-ads-all.srs",
"download_detour": "direct"
}
],
"final": "proxy"
}A split-DNS setup: domains routed to direct resolve locally, everything else resolves
through the proxy. The rule_set tags referenced from dns.rules are the ones defined at
the top level — the same definitions the routing rules use.
{
"dns": {
"servers": [
{ "tag": "proxy-dns", "type": "tcp", "server": "8.8.8.8", "server_port": 53, "detour": "proxy", "domain_resolver": "local-dns" },
{ "tag": "direct-dns", "type": "local" },
{ "tag": "local-dns", "type": "local" }
],
"rules": [
{ "clash_mode": "Direct", "action": "route", "server": "direct-dns" },
{ "clash_mode": "Global", "action": "route", "server": "proxy-dns" },
{ "rule_set": "geosite-ir", "action": "route", "server": "direct-dns" }
],
"final": "proxy-dns",
"strategy": "prefer_ipv4"
},
"rules": [
{ "action": "sniff" },
{ "protocol": "dns", "action": "hijack-dns" },
{ "rule_set": "geosite-ir", "action": "route", "outbound": "direct" }
],
"rule_set": [
{
"type": "remote",
"tag": "geosite-ir",
"format": "binary",
"url": "https://raw.githubusercontent.com/Chocolate4U/Iran-sing-box-rules/rule-set/geosite-ir.srs",
"download_detour": "direct"
}
],
"default_domain_resolver": "local-dns",
"final": "proxy"
}default_domain_resolver is set explicitly here because proxy-dns resolves through the
proxy, and resolving the proxy's own server address through the proxy cannot work.
Replacing the defaults — a TUN interface tuned for a router, and a mixed proxy on the LAN.
Note that inbounds replaces the default list wholesale; there is no merging.
{
"inbounds": [
{
"type": "tun",
"address": ["172.19.0.1/30"],
"mtu": 1500,
"auto_route": true,
"strict_route": true,
"stack": "mixed"
},
{
"type": "mixed",
"listen": "0.0.0.0",
"listen_port": 2080
}
],
"final": "proxy"
}{
"experimental": {
"clash_api": {
"external_controller": "127.0.0.1:9090",
"external_ui": "ui",
"secret": "",
"default_mode": "rule"
},
"cache_file": { "enabled": true, "store_fakeip": false }
}
}The panel does not validate the template against the sing-box schema. sing-box check does
that correctly and always matches the version you are running:
curl -o config.json "http://<your server>:2096/sub/<client name>?format=json"
sing-box check -c config.jsonIt reports the exact field and rule index that is wrong. A message like:
initialize dns router: dns rule[2]: rule-set not found: direct-geosite
means something references a rule-set that is not in the generated config — almost always
because the definition was written somewhere the panel does not read it. Check that
rule_set is at the top level of your template, then run check again.
To see what the panel actually produced, compare the downloaded config.json with your
template: keys that vanished were not recognized.