Files
3x-ui/docs/content/docs/en/config/subscription.mdx
T
Hamed 136c1c4d50 feat(hosts): add drag-and-drop and global subscription ordering (#6895)
Hosts already had one saved order on the Hosts page, but subscriptions honoured
it only inside each inbound, so a host of a later inbound could never come before
one of an earlier inbound (#6894). A new opt-in setting makes raw/base64,
Clash/Mihomo and JSON subscriptions follow the Hosts page order across inbounds;
the default keeps today's inbound-first output unchanged. The Hosts table gains
drag-and-drop reordering next to the Up/Down buttons, on a pointer-reorder hook
the Routing Rules table now shares.
2026-10-24 07:27:47 +02:00

188 lines
10 KiB
Plaintext

---
title: Subscription
description: Run the 3x-ui subscription server — base64/JSON/Clash formats, ports and paths, TLS, response headers, and custom templates.
icon: Rss
---
A **subscription** is a single URL that returns all of a client's
configurations. Client apps refresh it periodically, so when you change an
inbound, clients pick up the change automatically. The subscription server runs
as a **separate** server from the panel.
## Enable and configure
The subscription server is **on by default** (`subEnable`). Configure it in the
panel's subscription settings:
| Setting | Default | Meaning |
| ------------- | ------- | --------------------------------------------------------------- |
| `subPort` | `2096` | Listen port (separate from the panel). |
| `subListen` | _(all)_ | Bind address. |
| `subPath` | _(random per panel)_ | Base path for raw subscription URLs. |
| `subDomain` | _(none)_| Public host; if set, the server only answers for that Host. |
| `subCertFile` / `subKeyFile` | _(none)_ | TLS cert + key — when set, the server serves **HTTPS**. |
| `subEncrypt` | `true` | Base64-encode the raw subscription body. |
| `subUpdates` | `12` | Suggested refresh interval (hours) sent to clients. |
A subscription URL looks like:
```text
https://<sub-host>:<sub-port>/<sub-path>/<sub-id>
```
where `<sub-id>` is the client's **Sub ID**.
The same Sub ID is served in several formats on different paths — the **Base64**
list at `subPath` and the **JSON** (Xray-json) config at the JSON path. Build the
URLs and preview both bodies here:
<SubscriptionBuilder />
## Output formats
The **format is chosen by path**, each with its own enable toggle:
| Format | Path | Enabled by | Output |
| ------------------------------ | ---------------- | ---------------- | --------------------------------------------------- |
| **Raw links** | `subPath` | always (if on) | A list of `vless://`, `vmess://`, … links (base64-encoded when `subEncrypt` is on). |
| **JSON** | `subJsonPath` | `subJsonEnable` | Full Xray client config(s). |
| **Clash / Mihomo** | `subClashPath` | `subClashEnable` | Full Mihomo-compatible YAML profile. |
| **Mihomo (explicit)** | `/mihomo/` | `subClashEnable` | Alias for the full `subClashPath` profile. |
| **Clash for Windows (legacy)** | `/clash-legacy/` | `subClashEnable` | YAML limited to proxy types, transports, and ciphers supported by the legacy Clash core. |
Only enabled inbounds using **VLESS, VMess, Trojan, Shadowsocks, WireGuard, AmneziaWG, MTProto, TUIC, or Hysteria2**
appear in a subscription, ordered by their sub-sort index by default (TUIC and AmneziaWG are included in raw links and Clash/Mihomo profiles, but omitted from JSON endpoints; MTProto is included in raw links). Requesting `subPath`
with an `Accept: text/html` header (or `?html=1`) returns a human-readable info
page instead of the raw body.
Use `/mihomo/<sub-id>` for Clash Verge Rev, Mihomo, and other maintained
Mihomo-based clients. Use `/clash-legacy/<sub-id>` only for the discontinued
Clash for Windows client. The legacy endpoint keeps compatible VMess, Trojan,
and Shadowsocks nodes and excludes VLESS, Hysteria2, Reality, XHTTP,
HTTPUpgrade, and Shadowsocks 2022. If no compatible node exists, it returns an
explicit `422` response instead of a YAML profile the client cannot import.
To avoid Mihomo-only syntax entering the legacy profile, this endpoint always
uses its minimal `PROXY` group and `MATCH,PROXY` rule and ignores custom Clash
routing settings.
If an administrator has already assigned `/mihomo/` or `/clash-legacy/` to a
different configurable subscription path, that existing path is preserved and
the conflicting alias is skipped with a warning at startup.
Automatic Clash format detection keeps the existing `(?i)(clash|mihomo)`
default matcher so existing subscription URLs continue returning YAML.
It does not distinguish legacy clients from Mihomo-based clients; Clash for
Windows users must use `/clash-legacy/<sub-id>` for a compatible profile.
### Subscription node ordering
In **Settings → Subscription → Panel settings**, select **Subscription Node Ordering**
(`subHostSortMode`). **Inbound order (default)** preserves the existing subscription
sort index, then inbound ID, with each inbound's Hosts ordered inside that block.
**Hosts order** (`hosts-first`) uses the global order on **Hosts**, including Host
groups attached to different inbounds. Drag a group's grip above or below another
row to save its position, or use the existing Up/Down buttons. A failed save restores
the previous displayed order. The next subscription fetch uses the saved order;
no inbound edit or Xray restart is needed.
For example, these rows start with Host A (Inbound #5) above Host B (Inbound #20):
![Hosts before reordering, with Host A above Host B](/images/hosts-order-before.png)
Dragging Host B above Host A saves the order across both inbounds:
![Hosts after reordering, with Host B above Host A](/images/hosts-order-after.png)
These screenshots use example Hosts and synthetic API responses.
Host groups sort by their persisted sort order. Ties use the group remark and group
ID, matching the Hosts table. Within a group, inbound subscription sort index,
inbound ID and Host row ID provide deterministic ordering for attachments and
addresses. Inbounds without applicable Hosts and legacy externalProxy entries
follow all Host entries in their previous relative order. External client links
remain appended, and the subscription info node remains first.
This applies to Raw/Base64, Clash/Mihomo and JSON subscriptions. JSON balancer
documents keep their slots from the existing numeric balancer/inbound sort; only
the endpoint slots are rearranged, preserving the relative order of Host entries.
Eligibility, exclusions, credentials and protocol settings are unchanged.
Standalone share-link and QR previews retain their existing behavior. Client apps
may apply their own sorting after importing a subscription.
### Base64 vs JSON
The **Base64** body is just the newline-joined share links, standard-base64
encoded (toggle with `subEncrypt`). The **JSON** body wraps each client in a
complete Xray client config — a fixed skeleton (local SOCKS/HTTP inbounds bound to 127.0.0.1, DNS,
routing, policy) plus a `proxy` outbound pointing at the inbound. 3x-ui emits a
**single config object for one client and an array for several**, uses the flat
outbound `settings` form (`address`/`port`/`id`, `level: 8`), and strips
`sockopt` from `streamSettings`.
## Response headers
Subscriptions return standard headers that compatible apps read:
- **`Subscription-Userinfo`** — `upload`, `download`, `total` (bytes; `total=0`
means unlimited) and `expire` (Unix seconds).
- **`Profile-Update-Interval`** — refresh interval in hours (`subUpdates`).
- **`Profile-Title`**, **`Support-Url`**, **`Profile-Web-Page-Url`**,
**`Announce`** — optional branding shown by some clients.
### Profile page links and upgrades
In **Subscription → Profile → Profile page**, choose `subProfileMode` for all
subscription clients:
- **No link** (`none`, default): omit `Profile-Web-Page-Url`.
- **Built-in subscription page** (`builtin`): link to the client's built-in page.
- **Custom website** (`custom`): use `subProfileUrl`; a blank URL omits the header.
**Upgrade note:** previously, an empty `subProfileUrl` automatically linked to
the built-in page. After upgrading, an unset mode with an empty or whitespace-only
URL becomes **No link**; an existing nonempty URL remains a **Custom website**.
To restore the built-in link, select **Built-in subscription page** above and
save the settings.
The built-in page exposes subscription URLs and node configurations, including
for Happ encrypted subscriptions. Enable it only if you intend to provide that
access.
### Optional month-end expiry display
Under **Subscription → Information**, **Month-end subscription expiry display**
(`subCalendarExpireInclusive`, default `false`) reports the last valid second
of the month in `Subscription-Userinfo` instead of the next month's midnight.
It applies only when every client contributing to the subscription has calendar
renewal day `1`, shares the same fixed expiry, and that expiry is exactly day `1`
at `00:00:00` in the configured panel timezone, immediately after the previous
month's last second. A later repeated midnight during a DST rollback is not
converted. Raw, JSON, Mihomo, and legacy
Clash subscriptions use the same conversion.
For example, the real cutoff `2030-10-01 00:00:00` is presented as
`2030-09-30 23:59:59`. The stored expiry, access cutoff, traffic accounting,
renewal schedule, remark expiry variables, and HTML/JSON info-page cutoff stay
unchanged. Arbitrary times, other renewal days, interval renewal, first-use
durations, unlimited expiries, mixed renewal modes, and different cutoffs are
not converted.
This is an opt-in compatibility tradeoff, not a change to expiry semantics by
default: apps receive a timestamp one second before the real cutoff and may
consider the subscription expired one second early. Apps format it in their own
timezone; matching the panel timezone is needed to display the same month-end
date. Cached subscription information changes only after the app refreshes it.
## Custom page templates
Point `subThemeDir` at a folder containing a custom info-page template to brand
the HTML subscription page. The per-client remark on each link is fully
templated — see [Share links → remark variables](/docs/config/share-links#remark-template-variables).
<Callout type="info">
Put the subscription server behind TLS (set `subCertFile`/`subKeyFile`, or a
[reverse proxy](/docs/operations/reverse-proxy)) so subscription contents
aren't exposed in transit.
</Callout>