Skip to content

Proxy Groups & Naming Convention

When a user calls updateSubscription(clientId, ...), the panel generates a full Leaf .conf document and the SDK renders it into <data>/leaf-api/cache/current_temp.conf. This page explains the naming convention the panel uses for proxies and groups so your UI can parse the output of the Runtime HTTP API and build meaningful country / node pickers without hard-coding anything.

The reference implementation is generated by the Panel — every sample project (Android, Tauri) parses its output the same way.


1. Anatomy of a rendered config

[Proxy]
RU_MOSCOW_1_2_3_4_AMUX_TROJAN_WEBSOCKET_PLAIN_443_FRAGMENT = trojan, ...
RU_MOSCOW_1_2_3_4_AMUX_TROJAN_WEBSOCKET_PLAIN_443          = trojan, ...
RU_MOSCOW_1_2_3_4_AMUX_TROJAN_WEBSOCKET_PLAIN_443_DIRECT   = trojan, ...
...

[Proxy Group]
RU_MOSCOW_1_2_3_4_CDN    = failover, RU_MOSCOW_1_2_3_4_AMUX_TROJAN_WEBSOCKET_PLAIN_443_FRAGMENT, ..., health-check=true, ...
RU_MOSCOW_1_2_3_4_DIRECT = failover, RU_MOSCOW_1_2_3_4_AMUX_TROJAN_WEBSOCKET_PLAIN_443_DIRECT, ..., health-check=true, ...
RU_MOSCOW_1_2_3_4_MPTP   = mptp, RU_MOSCOW_1_2_3_4_CDN, RU_MOSCOW_1_2_3_4_DIRECT, mptp-io-write-size=4096, ...
RU_MOSCOW_1_2_3_4        = failover, RU_MOSCOW_1_2_3_4_CDN, RU_MOSCOW_1_2_3_4_DIRECT, RU_MOSCOW_1_2_3_4_MPTP, ...
...

RU_AUTO = failover, RU_MOSCOW_1_2_3_4, RU_SPB_5_6_7_8, ...
RU      = select,   RU_AUTO, RU_MOSCOW_1_2_3_4, RU_SPB_5_6_7_8, ...

AUTO = failover, RU, DE, NL, ...
OUT  = select,   AUTO, RU, DE, NL, ...

This produces a clean three-level hierarchy:

OUT                    (select: AUTO + every country)
├── AUTO               (failover across all countries)
└── <COUNTRY>          (select: <COUNTRY>_AUTO + each node)
    ├── <COUNTRY>_AUTO (failover across nodes)
    └── <NODE>         (failover / speedtest / tryall per user preference)
        ├── <NODE>_CDN          (failover across CDN-reachable proxies)
        ├── <NODE>_DIRECT       (failover across direct-IP proxies)
        └── <NODE>_MPTP         (mptp bonding the above)

2. Naming rules

Node tag

<COUNTRY>_<REGION_with_underscores>_<IP_with_dots_replaced_by_underscores>

Example: RU_MOSCOW_1_2_3_4.

Proxy tag (individual actor)

<NODE>_<INBOUND_TYPE>_<PORT>[_<QUALIFIER>]

Qualifiers emitted by the Panel:

Qualifier Meaning
(none) Baseline proxy, uses the node's external domain + the public TLS port.
_FRAGMENT Same as baseline but with TLS / host-fragmentation enabled.
_CDN Targets a CDN-fronted entry (external domain + CDN port).
_DIRECT Connects to the node IP directly, bypassing the CDN.
_ADDITIONAL_<port> Baseline proxy on an alternate public port.
_FRAGMENT_ADDITIONAL_<port> Fragmented version on an alternate public port.
_CDN_ADDITIONAL_<port> CDN version on an alternate public port.

Inbound types correspond to protocol+transport combinations. A non-exhaustive list (versions noted indicate the minimum Leaf core version that supports the type):

INBOUND_TYPE Protocol Transport
AMUX_TROJAN_HTTPUPGRADE_PLAIN Trojan HTTPUpgrade + AMux
AMUX_TROJAN_WEBSOCKET_PLAIN Trojan WebSocket + AMux
AMUX_TROJAN_WEBSOCKET_ED_PLAIN Trojan WebSocket early-data + AMux (≥ 0.3.5)
AMUX_VMESS_HTTPUPGRADE_PLAIN VMess HTTPUpgrade + AMux (≥ 0.4.3)
AMUX_VMESS_WEBSOCKET_PLAIN VMess WebSocket + AMux (≥ 0.4.3)
AMUX_VMESS_WEBSOCKET_ED_PLAIN VMess WebSocket early-data + AMux (≥ 0.4.3)
H2_TROJAN_PLAIN Trojan HTTP/2
H2_VMESS_PLAIN VMess HTTP/2 (≥ 0.4.3)
GRPC_TROJAN_PLAIN Trojan gRPC (≥ 0.2.8)
GRPC_VMESS_PLAIN VMess gRPC (≥ 0.4.3)
XHTTP_TROJAN_PLAIN Trojan xHTTP (≥ 0.2.9)
XHTTP_VMESS_PLAIN VMess xHTTP (≥ 0.4.3)
REALITY_TROJAN Trojan REALITY
REALITY_VLESS_TLS VLESS REALITY
REALITY_XHTTP VLESS REALITY + xHTTP
FAKETCP_TROJAN_TLS Trojan FakeTCP
QUIC_TROJAN_TLS Trojan QUIC

Group tags

Pattern Strategy Description
<NODE>_CDN failover All CDN-reachable proxies for the node.
<NODE>_DIRECT failover All direct-IP proxies for the node.
<NODE>_MPTP mptp Multi-path bonding of CDN + DIRECT sub-groups (when isMptpSupported).
<NODE> failover / speedtest / tryall Top-level node group; strategy depends on useTryAll and useSpeedtest.
<COUNTRY>_AUTO failover All nodes in a country auto-sorted by health.
<COUNTRY> select Country level — <COUNTRY>_AUTO + each node inside.
AUTO failover Global fallback across all countries.
OUT select Root groupAUTO + every country. This is what the routing FINAL rule targets.

3. Parsing recipe (client-side)

Given those conventions, a typical UI only needs to query three endpoints to build a full outbound picker:

GET /api/v1/app/outbound/list                        → high-level groups & types
GET /api/v1/app/outbound/selects?outbound=OUT        → list of countries (+ AUTO)
GET /api/v1/app/outbound/selects?outbound=<COUNTRY>  → nodes in the country (+ <COUNTRY>_AUTO)

TypeScript example

const api = new ApiClient(port);

// 1. Ask for the currently-selected country/subgroup.
const { selected: currentCountry } = await api.getCurrentSelectOutboundItem('OUT');

// 2. Pull the top list (countries).
const countries = (await api.getSelectOutboundItems('OUT')).outbounds
    .filter((o) => o.name !== 'AUTO');

// 3. Pull nodes for the selected country.
const { outbounds: nodes } = await api.getSelectOutboundItems(currentCountry);

// 4. Let the user pick.
await api.setSelectOutboundItem(currentCountry, 'RU_MOSCOW_1_2_3_4');
// Also reflect the change at the top level so OUT points at the new country.
await api.setSelectOutboundItem('OUT', currentCountry);

Kotlin example

fun changeSelectedOutbound(outbound: String) = viewModelScope.launch(Dispatchers.IO) {
    val subgroup = _selectedSubgroupTag.value ?: "OUT"
    api.setSelectOutboundItem(subgroup, outbound)
    if (subgroup != "OUT") {
        // Bubble the selection up so routing still uses the current country group.
        api.setSelectOutboundItem("OUT", subgroup)
    }
}

Displaying country flags

Because node tags start with the two-letter ISO country code, you can map them to flags:

const code = nodeTag.split('_')[0].toLowerCase();   // "RU_MOSCOW_..." -> "ru"
const flag = `https://flagcdn.com/24x18/${code}.png`;

Pinging all candidates in parallel

val pings = currentOutbounds.map { outbound ->
    async(Dispatchers.IO) {
        val h = api.getOutboundHealth(outbound.name)
        outbound.name to (h.tcpMs ?: h.udpMs)
    }
}.awaitAll().toMap()

See the open-source samples for complete implementations — leaf-desktop/src/store/outbounds.ts and leaf-android/.../LeafViewModel.kt.


4. What the subscription-flag switches change

updateSubscription(tls, fragment, clientId, enable_speedtest, enable_try_all, cb) re-runs the panel generator. The enable_speedtest / enable_try_all flags alter only the <NODE> group strategy:

Flags Group strategy for <NODE>
Both false failover, <CDN>, <DIRECT>, <MPTP>, …
enable_speedtest = true speedtest, …, check-interval=1800, test-url=https://surfshield.org/25mb_file.bin, test-timeout=5
enable_try_all = true tryall, …

<COUNTRY>_AUTO, AUTO, OUT are unchanged — so you can always count on OUT existing as a select group with countries as members.


5. Priority tagging (per region)

The Panel's generator re-orders actors inside every group based on the user's country so that the most resilient protocols for that market are tried first. You do not need to reproduce this on the client — it is baked into the generated config. For reference:

Country Preferred order (first → last)
RU FAKETCP_TROJAN_TLS, QUIC_TROJAN_TLS, REALITY_VLESS_TLS, REALITY_TROJAN, REALITY_XHTTP, XHTTP_TROJAN_PLAIN.*FRAGMENT, GRPC_TROJAN_PLAIN.*FRAGMENT, GRPC_VMESS_PLAIN.*FRAGMENT
IR FAKETCP_TROJAN_TLS, QUIC_TROJAN_TLS, REALITY_VLESS_TLS, REALITY_TROJAN, REALITY_XHTTP, XHTTP_TROJAN_PLAIN.*FRAGMENT, AMUX_TROJAN_HTTPUPGRADE_PLAIN.*FRAGMENT, AMUX_TROJAN_WEBSOCKET_PLAIN.*FRAGMENT
default FAKETCP_TROJAN_TLS, QUIC_TROJAN_TLS, REALITY_VLESS_TLS, REALITY_TROJAN, REALITY, XHTTP_TROJAN_PLAIN.*DIRECT, XHTTP_VMESS_PLAIN.*DIRECT, AMUX_TROJAN_HTTPUPGRADE_PLAIN.*DIRECT

6. Minimal safety rules for client code

  • Treat every tag as opaque except for the OUT and AUTO constants — do not parse internals to drive business logic.
  • The only guaranteed group is OUT (select). Everything else depends on what the user's country produced.
  • selectOutboundItem may return 404 if the group was renamed by a subscription refresh; handle it by re-fetching outbound/list.
  • When the user picks a node inside a country, also set OUT → <COUNTRY> so the top-level FINAL rule uses the right country path. Both samples do this.