Subscription
Run the 3x-ui subscription server — base64/JSON/Clash formats, ports and paths, TLS, response headers, and custom templates.
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:
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:
Subscription & sub-JSON builder
Build the subscription URLs and preview both body formats — the Base64 link list and the JSON (Xray-json) config.
https://sub.example.com:2096/your-sub-path/user-1https://sub.example.com:2096/your-json-path/user-1Clients in this subscription
vless://11111111-2222-3333-4444-555555555555@a.example.com:443?type=tcp&security=reality&sni=www.microsoft.com#HK-01dmxlc3M6Ly8xMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTVAYS5leGFtcGxlLmNvbTo0NDM/dHlwZT10Y3Amc2VjdXJpdHk9cmVhbGl0eSZzbmk9d3d3Lm1pY3Jvc29mdC5jb20jSEstMDE={
"remarks": "HK-01",
"dns": {
"tag": "dns_out",
"queryStrategy": "UseIP",
"servers": [
{
"address": "8.8.8.8",
"skipFallback": false
}
]
},
"inbounds": [
{
"listen": "127.0.0.1",
"port": 10808,
"protocol": "socks",
"settings": {
"auth": "noauth",
"udp": true,
"userLevel": 8
},
"sniffing": {
"destOverride": [
"http",
"tls",
"quic",
"fakedns"
],
"enabled": true
},
"tag": "mixed"
},
{
"listen": "127.0.0.1",
"port": 10809,
"protocol": "http",
"settings": {
"userLevel": 8
},
"tag": "http"
}
],
"log": {
"loglevel": "warning"
},
"policy": {
"levels": {
"8": {
"connIdle": 300,
"downlinkOnly": 1,
"handshake": 4,
"uplinkOnly": 1
}
},
"system": {
"statsOutboundUplink": true,
"statsOutboundDownlink": true
}
},
"routing": {
"domainStrategy": "AsIs",
"rules": [
{
"type": "field",
"network": "tcp,udp",
"outboundTag": "proxy"
}
]
},
"stats": {},
"outbounds": [
{
"protocol": "vless",
"tag": "proxy",
"streamSettings": {
"network": "tcp",
"security": "reality",
"tcpSettings": {
"header": {
"type": "none"
}
},
"realitySettings": {
"fingerprint": "chrome",
"serverName": "www.microsoft.com"
}
},
"settings": {
"address": "a.example.com",
"port": 443,
"id": "11111111-2222-3333-4444-555555555555",
"encryption": "none",
"level": 8
}
},
{
"tag": "direct",
"protocol": "freedom",
"settings": {
"domainStrategy": "AsIs",
"redirect": "",
"noises": []
}
},
{
"tag": "block",
"protocol": "blackhole",
"settings": {
"response": {
"type": "http"
}
}
}
]
}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=0means unlimited) andexpire(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): omitProfile-Web-Page-Url. - Built-in subscription page (
builtin): link to the client's built-in page. - Custom website (
custom): usesubProfileUrl; 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.
Put the subscription server behind TLS (set subCertFile/subKeyFile, or a
reverse proxy) so subscription contents
aren't exposed in transit.

3x-ui