Subscription
Subscriptions fetch external config fragments at load time and merge them into the current config.
Currently, there are three types:
full: reserved, not implemented yet (logs a warning and ignored)outbound: implemented, fetches and mergesoutboundsdatabase: implemented, downloads and overwrites geodata database files (geoip.dat/geosite.dat)
SubscriptionObject
{
"subscription": {
"subscription-inbound": "sub-in",
"outbound": [
{
"name": "mySub",
"url": "https://example.com/sub",
"format": "auto",
"tagPrefix": "sub-",
"insert": "tail",
"interval": "30m",
"observatory": {
"enabled": true,
"testUrl": "https://connectivitycheck.gstatic.com/generate_204"
},
"enabled": true
}
],
"database": [
{
"type": "geoip",
"url": "https://example.com/geoip.dat",
"interval": "24h",
"enabled": true
},
{
"type": "geosite",
"url": "https://example.com/geosite.dat",
"cron": "0 3 * * *",
"enabled": true
}
]
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
TIP
subscription.outbound, subscription.full, and subscription.database accept either a single object or an array.
subscription-inbound: string
(Optional) A virtual inboundTag for subscription fetch/update traffic. When set, subscription HTTP requests are sent through Xray's internal router:
- routing rules can match this traffic via
inboundTagand select an outbound (e.g. force subscriptions through a proxy) - when unset: subscription HTTP fetch uses the system
net/httpclient directly (not routed through Xray)
OutboundSubscriptionObject
subscription.outbound parses the subscription payload into a list of outbounds and merges them into the current config.
name: string
Subscription name (used for default tags and log identification).
url: string
Subscription source:
- Supports
http:///https:// - Supports local file paths (absolute or relative)
stdin:is not allowed
Relative path resolution priority:
- The directory passed by
xray run -confdir <dir> - The config directory (single file: that file's directory; multiple files: only when all files share the same directory)
cwd(startup working directory)
format: string
Payload format:
auto(default): infer by extension (.json/.jsonc-> json,.yaml/.yml-> yaml), otherwise defaults to json; if parsing fails, Xray will fall back to standard share subscriptions automaticallyjson: JSON / JSONCyaml/yml: YAML (converted to JSON before parsing; field names must still follow the JSON schema)xray/share/sharelink/base64: standard share subscription (see below)
WARNING
Subscriptions do not support TOML yet. Even with a .toml extension, format=auto cannot parse it correctly.
tagPrefix: string
Prefix added to the tag of every outbound generated from this subscription (recommended to avoid tag collisions).
insert: "head" | "tail"
Merge position:
tail(default, also acceptsappend): append to the end of existingoutboundshead(also acceptsprepend): insert before existingoutbounds
enabled: true | false
Whether this subscription is enabled. Default true.
observatory: true | false | object
(Optional) Enable observatory integration for this subscription.
- When enabled: after each successful runtime refresh, tags generated by this subscription are injected into observatory targets (equivalent to dynamically adding them into
subjectSelector) trueis equivalent to{ "enabled": true }- Object form:
enabled: true | falsetestUrl: string (optional, per-subscription probe URL)testURL/probeURL: compatibility aliases oftestUrl
- If subscription-level
testUrlis not set: Xray uses globalobservatory.testUrl(and then observatory's built-in default if global is also unset)
TIP
subscription.outbound.observatory only wires subscription nodes into observatory. If there is no top-level observatory {}, no actual probing is performed.
interval: string
(Optional) Runtime refresh interval (e.g. "10m" / "1h"). Mutually exclusive with cron/crontab.
cron: string
(Optional) Runtime refresh schedule (standard 5-field crontab: min hour day month dow, e.g. "0 3 * * *"). Descriptors like @daily / @weekly / @every 1h are also supported.
Alias: crontab.
TIP
When subscription.subscription-inbound is set, interval/cron/crontab is configured, or subscription.outbound.observatory is enabled, this subscription is fetched/applied at runtime; otherwise it is fetched/merged during config load.
DatabaseSubscriptionObject
subscription.database downloads and overwrites geodata database files:
geoip.dat(used bygeoip:rules)geosite.dat(used bygeosite:rules)
type: "geoip" | "geosite"
Database type:
geoip: overwritesgeoip.datgeosite: overwritesgeosite.dat
url: string
Subscription source. Supports http:// / https:// / local file paths (relative path resolution is the same as subscription.outbound.url).
enabled: true | false
Whether this subscription is enabled. Default true.
interval: string
(Optional) Runtime refresh interval (e.g. "24h"). Mutually exclusive with cron/crontab.
cron: string
(Optional) Runtime refresh schedule. Alias: crontab.
Install location:
- On success, the downloaded file is written into the asset directory and overwrites the existing file. The asset directory is controlled by
xray.location.asset/XRAY_LOCATION_ASSET.
WARNING
Overwriting .dat files does not automatically rebuild already-running routing rules. You usually need to restart the core or reload the config to use the new geodata.
Payload Formats
JSON / JSONC
Two structures are supported:
- An outbound list:
[
{ "protocol": "freedom", "tag": "direct" },
{ "protocol": "blackhole", "tag": "block" }
]2
3
4
- A wrapper object (reads only
outbounds):
{
"outbounds": [{ "protocol": "freedom", "tag": "direct" }]
}2
3
JSON Response Example (With streamSettings)
[
{
"tag": "sub-vless-ws-tls",
"protocol": "vless",
"settings": {
"address": "vless.example.com",
"port": 443,
"id": "d4b9ad8a-1c7f-4b9c-b1f7-7b12e3e2c0a1",
"encryption": "none"
},
"streamSettings": {
"network": "ws",
"security": "tls",
"tlsSettings": { "serverName": "vless.example.com" },
"wsSettings": { "host": "cdn.example.com", "path": "/xrayws" }
}
},
{
"tag": "sub-trojan-grpc-tls",
"protocol": "trojan",
"settings": {
"address": "trojan.example.com",
"port": 443,
"password": "password123"
},
"streamSettings": {
"network": "grpc",
"security": "tls",
"tlsSettings": { "serverName": "trojan.example.com" },
"grpcSettings": { "serviceName": "grpc_service" }
}
}
]2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
YAML
YAML is converted to JSON before parsing; field names must match the JSON schema (e.g. streamSettings, tlsSettings).
Example (outbound list):
- protocol: freedom
tag: direct
- protocol: blackhole
tag: block2
3
4
YAML Response Example (With streamSettings)
- tag: sub-vless-ws-tls
protocol: vless
settings:
address: vless.example.com
port: 443
id: "d4b9ad8a-1c7f-4b9c-b1f7-7b12e3e2c0a1"
encryption: none
streamSettings:
network: ws
security: tls
tlsSettings:
serverName: vless.example.com
wsSettings:
host: cdn.example.com
path: /xrayws
- tag: sub-trojan-grpc-tls
protocol: trojan
settings:
address: trojan.example.com
port: 443
password: "password123"
streamSettings:
network: grpc
security: tls
tlsSettings:
serverName: trojan.example.com
grpcSettings:
serviceName: grpc_service2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Standard Share Subscriptions (Base64)
Many subscription servers return a Base64-encoded text payload. After decoding, it becomes multiple share links, e.g. (dummy values for format demonstration only):
vless://d4b9ad8a-1c7f-4b9c-b1f7-7b12e3e2c0a1@vless.example.com:443?encryption=none&security=tls&sni=vless.example.com&type=ws&host=cdn.example.com&path=%2Fxrayws#VLESS%20WS%20TLS
vmess://eyJwcyI6IlZNZXNzIFdTIFRMUyIsImFkZCI6InZtZXNzLmV4YW1wbGUuY29tIiwicG9ydCI6IjQ0MyIsImlkIjoiYTM0ODJlODgtNjg2YS00YTU4LTgxMjYtOTljOWRmNjRiN2JmIiwic2N5IjoiYXV0byIsIm5ldCI6IndzIiwiaG9zdCI6ImNkbi5leGFtcGxlLmNvbSIsInBhdGgiOiIvdm1lc3N3cyIsInRscyI6InRscyIsInNuaSI6InZtZXNzLmV4YW1wbGUuY29tIiwiYWxwbiI6ImgyLGh0dHAvMS4xIiwiZnAiOiJjaHJvbWUifQ==
trojan://password123@trojan.example.com:443?security=tls&sni=trojan.example.com&type=grpc&serviceName=grpc_service#Trojan%20gRPC%20TLS
ss://YWVzLTEyOC1nY206cGFzc3cwcmQ@ss.example.com:8388#SS%20AES-128-GCM2
3
4
Base64 Payload Example (HTTP Response Body)
dmxlc3M6Ly9kNGI5YWQ4YS0xYzdmLTRiOWMtYjFmNy03YjEyZTNlMmMwYTFAdmxlc3MuZXhhbXBsZS5jb206NDQzP2VuY3J5cHRpb249bm9uZSZzZWN1cml0eT10bHMmc25pPXZsZXNzLmV4YW1wbGUuY29tJnR5cGU9d3MmaG9zdD1jZG4uZXhhbXBsZS5jb20mcGF0aD0lMkZ4cmF5d3MjVkxFU1MlMjBXUyUyMFRMUwp2bWVzczovL2V5SndjeUk2SWxaTlpYTnpJRmRUSUZSTVV5SXNJbUZrWkNJNkluWnRaWE56TG1WNFlXMXdiR1V1WTI5dElpd2ljRzl5ZENJNklqUTBNeUlzSW1sa0lqb2lZVE0wT0RKbE9EZ3ROamcyWVMwMFlUVTRMVGd4TWpZdE9UbGpPV1JtTmpSaU4ySm1JaXdpYzJONUlqb2lZWFYwYnlJc0ltNWxkQ0k2SW5keklpd2lhRzl6ZENJNkltTmtiaTVsZUdGdGNHeGxMbU52YlNJc0luQmhkR2dpT2lJdmRtMWxjM04zY3lJc0luUnNjeUk2SW5Sc2N5SXNJbk51YVNJNkluWnRaWE56TG1WNFlXMXdiR1V1WTI5dElpd2lZV3h3YmlJNkltZ3lMR2gwZEhBdk1TNHhJaXdpWm5BaU9pSmphSEp2YldVaWZRPT0KdHJvamFuOi8vcGFzc3dvcmQxMjNAdHJvamFuLmV4YW1wbGUuY29tOjQ0Mz9zZWN1cml0eT10bHMmc25pPXRyb2phbi5leGFtcGxlLmNvbSZ0eXBlPWdycGMmc2VydmljZU5hbWU9Z3JwY19zZXJ2aWNlI1Ryb2phbiUyMGdSUEMlMjBUTFMKc3M6Ly9ZV1Z6TFRFeU9DMW5ZMjA2Y0dGemMzY3djbVFAc3MuZXhhbXBsZS5jb206ODM4OCNTUyUyMEFFUy0xMjgtR0NNXray automatically:
- tries Base64 decoding (supports Base64(JSON/YAML) and Base64(share links))
- parses recognized share links and converts them into
outbounds
Supported share schemes:
vless://vmess://(the common base64(json) form)trojan://ss://(pluginquery is ignored for now)
WARNING
Unparseable lines are skipped with warnings. If no supported share links are found, Xray fails to start.
Base64 URL
If your url itself is Base64-encoded, you can use:
base64:<BASE64>b64:<BASE64>base64://<BASE64>b64://<BASE64>
Example: b64:aHR0cHM6Ly9leGFtcGxlLmNvbS9zdWI is equivalent to https://example.com/sub.
Xray will decode it into the real URL/path first, then fetch it.
Merge And Tag Rules
- If a generated outbound has an empty
tag, Xray auto-generates"<name>-<index>"(whennameis empty, it usessubscriptionas the default name). - If
tagPrefixis set, it is prepended to the final tag. - Merge is tag-based: outbounds with the same tag overwrite existing ones; new tags are inserted at head/tail depending on
insert. - For subscription management, every outbound generated from a subscription will have a
managedfield set to the subscription name (and also asubscriptionfield as an equivalent alias). The value comes fromsubscription.outbound.name(auto-filled when omitted). On subsequent config loads, Xray removes outbounds with the samemanaged/subscriptionvalue before merging the new subscription result, so subscription deletions/additions/updates take effect.
TIP
Always use tagPrefix to avoid tag collisions between subscription content and local outbounds.
-dump Behavior
xray run -dump fetches/merges subscriptions:
- When subscriptions are expanded during config load, the
subscription {}block is removed in the output (expanded intooutbounds). - When runtime refresh is enabled (
subscription-inbound/interval/cron), thesubscription {}block is kept, and the output will not include runtime-updated outbounds.