3x-ui3x-ui

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:

SettingDefaultMeaning
subPort2096Listen 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.
subEncrypttrueBase64-encode the raw subscription body.
subUpdates12Suggested 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.

Base64 subscription URL
https://sub.example.com:2096/your-sub-path/user-1
JSON subscription URL
https://sub.example.com:2096/your-json-path/user-1

Clients in this subscription

Subscription links (decoded body)
vless://11111111-2222-3333-4444-555555555555@a.example.com:443?type=tcp&security=reality&sni=www.microsoft.com#HK-01
Base64 body
dmxlc3M6Ly8xMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTVAYS5leGFtcGxlLmNvbTo0NDM/dHlwZT10Y3Amc2VjdXJpdHk9cmVhbGl0eSZzbmk9d3d3Lm1pY3Jvc29mdC5jb20jSEstMDE=
JSON subscription (preview)
{
  "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:

FormatPathEnabled byOutput
Raw linkssubPathalways (if on)A list of vless://, vmess://, … links (base64-encoded when subEncrypt is on).
JSONsubJsonPathsubJsonEnableFull Xray client config(s).
Clash / MihomosubClashPathsubClashEnableFull Mihomo-compatible YAML profile.
Mihomo (explicit)/mihomo/subClashEnableAlias for the full subClashPath profile.
Clash for Windows (legacy)/clash-legacy/subClashEnableYAML 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.

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.

Put the subscription server behind TLS (set subCertFile/subKeyFile, or a reverse proxy) so subscription contents aren't exposed in transit.

On this page