| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132 |
- ---
- 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 (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.
- ### 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.
- ### 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>
|