Skip to content

AlchemyLink/Raven-subscribe

Raven Subscribe

Languages: English | Русский

Built for Xray-core Test Security Scan Go Report Card License: MIT Stars Forks Hits

Subscription server for XTLS/Xray-core. Reads your Xray server configs, auto-discovers users, and gives each one a personal subscription URL — so their VPN client always has the latest connection settings.


Table of Contents


What it does

When you run an Xray proxy server, each user needs a client config file with the correct server address, port, UUID, and transport settings. Keeping those configs up to date manually is tedious.

Raven Subscribe solves this:

  1. It reads your Xray server configs from /etc/xray/config.d
  2. Discovers all users (clients) defined in those configs automatically
  3. Generates a ready-to-use Xray client config for each user
  4. Serves it via a unique subscription URL

Users just add their subscription URL to any Xray-compatible client (V2RayNG, NekoBox, V2Box, Hiddify, etc.) — and the client fetches fresh settings automatically.


Features

For users

  • Personal subscription URL — one link that always returns the latest config
  • Multiple formats — full Xray JSON, plain share links, Base64-encoded links
  • Protocol-specific links — get only VLESS, VMess, Trojan, or Shadowsocks links
  • Mobile-optimized configs — auto-detected from User-Agent (Android, iPhone, NekoBox, V2RayNG) or via ?profile=mobile
  • Custom routing rules — per-user rules to route specific sites directly, through proxy, or block them

For administrators

  • Database as source of truth — when api_user_inbound_tag is set, add/remove/enable/disable users via API and they sync to Xray immediately
  • Zero-touch user sync — users can also be discovered from Xray config email fields
  • File watcher — detects config changes instantly (fsnotify + periodic polling)
  • Full REST API — manage users, tokens, routing rules, and balancer settings
  • Per-user client control — enable/disable a user's access to specific inbounds
  • Token rotation — regenerate any user's subscription token without downtime
  • Balancer support — automatic load balancing across multiple outbounds (leastPing, leastLoad, random)
  • Global routing rules — apply routing rules to all users at once

Protocols & transports

  • VLESS, VMess, Trojan, Shadowsocks, SOCKS
  • TCP, WebSocket, gRPC, HTTP/2, KCP, QUIC, HTTPUpgrade, XHTTP (SplitHTTP)
  • TLS and REALITY security layers with automatic key derivation

How it works

/etc/xray/config.d/
    ├── vless-reality.json   ← Xray server inbound configs
    ├── vmess-ws.json
    └── trojan-tls.json
           │
           ▼
    Raven Subscribe
    (watches for changes)
           │
           ├─ Parses inbounds, discovers users
           ├─ Stores in SQLite
           ├─ Serves subscription URLs
           └─ API-created users → Xray (config files or gRPC API)
                      │
                      ▼
    https://your-server.com/sub/{token}
                      │
                      ▼
    V2RayNG / NekoBox / Hiddify / V2Box
    (fetches config automatically)

Each user gets a unique token. When their client fetches the subscription URL, Raven Subscribe builds a complete Xray client config on the fly — with all their enabled inbounds, routing rules, DNS settings, and balancer config.


Quick Start

1. Install

From binary:

curl -Lo xray-subscription https://github.com/AlchemyLink/Raven-subscribe/releases/latest/download/xray-subscription-linux-amd64
chmod +x xray-subscription
sudo mv xray-subscription /usr/local/bin/

From source:

git clone https://github.com/AlchemyLink/Raven-subscribe.git
cd Raven-subscribe
make build
sudo cp build/xray-subscription /usr/local/bin/

2. Configure

sudo mkdir -p /etc/xray-subscription
sudo cp config.json.example /etc/xray-subscription/config.json
sudo nano /etc/xray-subscription/config.json

Minimum required settings:

{
  "server_host": "your-server-ip-or-domain",
  "admin_token": "your-secret-admin-token",
  "base_url": "http://your-server-ip-or-domain:8080"
}

3. Run

xray-subscription -config /etc/xray-subscription/config.json

As a systemd service:

sudo cp xray-subscription.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now xray-subscription

The service runs as User=xray so Raven and Xray share ownership of config files. When api_user_inbound_tag is set, Raven writes to config_dir; Xray must read those files. Ensure:

# Create xray user/group if missing (Xray package usually does this)
sudo useradd -r -s /usr/sbin/nologin xray 2>/dev/null || true

# Let xray own config_dir and data dir when using file-based sync
sudo chown -R xray:xray /etc/xray/config.d /var/lib/xray-subscription

4. Get user subscription URLs

# List all users and their subscription URLs
curl -H "X-Admin-Token: your-secret-admin-token" http://localhost:8080/api/users

Response:

[
  {
    "user": {
      "id": 1,
      "username": "alice",
      "token": "a3f8c2...",
      "enabled": true
    },
    "sub_url": "http://your-server:8080/sub/a3f8c2...",
    "sub_urls": {
      "full":        "http://your-server:8080/sub/a3f8c2...",
      "links_txt":   "http://your-server:8080/sub/a3f8c2.../links.txt",
      "links_b64":   "http://your-server:8080/sub/a3f8c2.../links.b64",
      "compact":     "http://your-server:8080/c/a3f8c2...",
      "compact_txt": "http://your-server:8080/c/a3f8c2.../links.txt",
      "compact_b64": "http://your-server:8080/c/a3f8c2.../links.b64"
    }
  }
]

Give each user their sub_urls.compact URL — they add it to their VPN client and are ready to go.


Configuration

Configuration is loaded from a JSON file (default: config.json in the current directory). Use -config flag to specify a path:

xray-subscription -config /etc/xray-subscription/config.json

Full config reference

{
  "listen_addr": ":8080",
  "server_host": "your-server.com",
  "config_dir": "/etc/xray/config.d",
  "db_path": "/var/lib/xray-subscription/db.sqlite",
  "sync_interval_seconds": 60,
  "base_url": "http://your-server.com:8080",
  "admin_token": "your-secret-token",
  "balancer_strategy": "leastPing",
  "balancer_probe_url": "https://www.gstatic.com/generate_204",
  "balancer_probe_interval": "30s",
  "socks_inbound_port": 2080,
  "http_inbound_port": 1081,
  "rate_limit_sub_per_min": 60,
  "rate_limit_admin_per_min": 30,
  "api_user_inbound_tag": "vless-reality",
  "xray_api_addr": ""
}

Parameter reference

Server

Field Default Description
listen_addr :8080 Address and port to bind. Use :8080 for all interfaces, or 127.0.0.1:8080 for localhost only (e.g. behind nginx).
server_host Required. Your server IP or domain. Used as the outbound address in generated client configs. Must match what clients will connect to.
base_url http://localhost:8080 Full base URL for subscription links. Shown to users in API responses. Use https:// if behind TLS reverse proxy.

Storage & sync

Field Default Description
config_dir /etc/xray/config.d Directory containing Xray server inbound JSON configs. Raven watches this for changes (fsnotify + periodic scan).
db_path /var/lib/xray-subscription/db.sqlite SQLite database path. Stores users, tokens, routing rules, and synced client data.
sync_interval_seconds 60 Interval (seconds) for re-scanning config_dir. Also triggered on file changes.

Admin API

Field Default Description
admin_token Required. Secret token for Admin API. Pass in X-Admin-Token header. Use a long random string; generate with openssl rand -hex 32.

Load balancer

Used when your Xray config has multiple outbounds (e.g. several proxy nodes). Controls how the client chooses between them.

Field Default Description
balancer_strategy leastPing Strategy: leastPing (lowest latency), leastLoad (least connections), random, roundRobin.
balancer_probe_url https://www.gstatic.com/generate_204 URL for latency probes (used by leastPing). Must be reachable from the server.
balancer_probe_interval 30s How often to probe outbounds. Go duration: 30s, 1m, etc.

Client config generation

Field Default Description
socks_inbound_port 2080 Local SOCKS5 proxy port in generated client configs. Clients use this for system/app proxy.
http_inbound_port 1081 Local HTTP proxy port in generated client configs.

Rate limiting

Limits requests per IP per minute. 0 = disabled. Helps prevent abuse.

Field Default Description
rate_limit_sub_per_min 0 Max requests/min per IP for /sub/* and /c/*. Recommended: 60 for production.
rate_limit_admin_per_min 0 Max requests/min per IP for /api/*. Recommended: 30.
api_user_inbound_tag "" When set, the database is the source of truth: users created via API are added to this Xray inbound; deleted users are removed; enable/disable syncs to Xray. Uses config_dir file write or Xray API (if xray_api_addr is set).
xray_api_addr "" When set, users are synced via Xray gRPC API instead of config files. E.g. 127.0.0.1:8080. Requires api_user_inbound_tag. Xray must have API enabled with HandlerService.
api_user_inbound_protocol "" Fallback when config_dir has no inbounds: protocol (vless, vmess, trojan, shadowsocks) to create the inbound in DB. Use when Xray configs are elsewhere.
api_user_inbound_port 443 Port for the inbound when using api_user_inbound_protocol fallback.
xray_config_file_mode (omit) Octal mode for JSON files Raven writes under config_dir (e.g. "0644" so another local user can read configs for testing). Default 0600. Only permission bits 07 (max 0777).

DB ↔ Xray sync (when api_user_inbound_tag is set): The database is the source of truth. All changes propagate to Xray immediately:

Action DB Xray
Create user (POST /api/users) Add Add to inbound
Delete user (DELETE /api/users/{id}) Remove Remove from inbound
Disable user (PUT /api/users/{id}/disable) enabled=false Remove from inbound
Enable user (PUT /api/users/{id}/enable) enabled=true Add to inbound

Xray API mode (when xray_api_addr is set): Users are synced via gRPC instead of config files. Xray must have API enabled with HandlerService in services.

  • Restore on startup: Raven restores all users from the database to Xray via API (survives Xray restarts).
  • Periodic DB→config sync: Raven periodically writes users to config files, so they persist across both Raven and Xray restarts.

Example: minimal config

{
  "server_host": "vpn.example.com",
  "admin_token": "your-secret-token",
  "base_url": "https://vpn.example.com"
}

All other parameters use defaults.

Example: production with rate limits

{
  "listen_addr": "127.0.0.1:8080",
  "server_host": "vpn.example.com",
  "base_url": "https://vpn.example.com",
  "admin_token": "your-secret-token",
  "rate_limit_sub_per_min": 60,
  "rate_limit_admin_per_min": 30
}

Use 127.0.0.1 when running behind nginx/caddy as reverse proxy.


Subscription URLs

Each user has two subscription endpoints:

Endpoint Description
/c/{token} Primary. Lightweight config — geosite:/geoip: selectors stripped. Works great on all devices.
/sub/{token} Full config with all routing rules including geo databases.

/c/{token} — primary endpoint (recommended)

The compact endpoint is the recommended URL to give to users. It returns a complete Xray client config with routing rules optimized for lower memory usage — geosite: and geoip: selectors are stripped, keeping only explicit domain and IP rules.

Works on all clients: V2RayNG, NekoBox, V2Box, Hiddify, and desktop clients.

What you want URL
Full Xray JSON config /c/{token}
All share links (plain text) /c/{token}/links.txt
All share links (Base64) /c/{token}/links.b64

/sub/{token} — full endpoint

Returns the complete config including geosite: and geoip: routing rules. Use this if your client supports geo databases and you want full routing control.

What you want URL
Full Xray JSON config /sub/{token}
All share links (plain text) /sub/{token}/links.txt
All share links (Base64) /sub/{token}/links.b64
VLESS links only /sub/{token}/vless
VMess links only /sub/{token}/vmess
Trojan links only /sub/{token}/trojan
Shadowsocks links only /sub/{token}/ss
Specific inbound only /sub/{token}/inbound/{tag}
Lightweight config (explicit) /sub/{token}?profile=mobile

Example: import into V2RayNG

  1. Open V2RayNG → tap +Import config from URL
  2. Paste: http://your-server:8080/c/YOUR_TOKEN
  3. Tap OK — done. The app fetches and imports all your connections.

Example: import into NekoBox / Hiddify

Use the same /c/{token} URL. These clients support Xray JSON format natively.

Example: get plain share links

curl http://your-server:8080/c/YOUR_TOKEN/links.txt

Output:

vless://uuid@your-server:443?type=ws&security=tls&...#vless-ws-tls
vmess://eyJ2IjoiMiIsInBzIjoidm1lc3MtdGNwIiwiYWRkIjoieW91ci1zZXJ2ZXIiLCJwb3J0IjoiODA4MCIsImlkIjoiLi4uIn0=
trojan://password@your-server:443?security=tls&...#trojan-tls

Auto-detection

When a mobile client fetches /sub/{token}, Raven Subscribe automatically detects it from the User-Agent header (Android, iPhone, iPad, V2RayNG, NekoBox, V2Box) and applies the lightweight profile automatically. The /c/{token} endpoint always uses the lightweight profile regardless of User-Agent.


Admin API

All admin endpoints require authentication. Pass your admin_token as a header or query parameter:

# As header (recommended)
curl -H "X-Admin-Token: your-secret-token" http://localhost:8080/api/users

# As query parameter
curl "http://localhost:8080/api/users?admin_token=your-secret-token"

Users

List all users

GET /api/users
curl -H "X-Admin-Token: secret" http://localhost:8080/api/users
[
  {
    "user": {"id": 1, "username": "alice@example.com", "token": "abc123", "enabled": true},
    "sub_url": "http://your-server:8080/sub/abc123",
    "sub_urls": {
      "full":        "http://your-server:8080/sub/abc123",
      "links_txt":   "http://your-server:8080/sub/abc123/links.txt",
      "links_b64":   "http://your-server:8080/sub/abc123/links.b64",
      "compact":     "http://your-server:8080/c/abc123",
      "compact_txt": "http://your-server:8080/c/abc123/links.txt",
      "compact_b64": "http://your-server:8080/c/abc123/links.b64"
    }
  }
]

Create a user

POST /api/users
Content-Type: application/json

{"username": "bob"}

On create, email is not accepted; internally it matches username for Xray. API JSON does not include email (use username).

curl -X POST -H "X-Admin-Token: secret" -H "Content-Type: application/json" \
  -d '{"username":"bob"}' http://localhost:8080/api/users
{
  "user": {"id": 2, "username": "bob", "token": "xyz789", "enabled": true},
  "sub_url": "http://your-server:8080/sub/xyz789",
  "sub_urls": {
    "full":        "http://your-server:8080/sub/xyz789",
    "links_txt":   "http://your-server:8080/sub/xyz789/links.txt",
    "links_b64":   "http://your-server:8080/sub/xyz789/links.b64",
    "compact":     "http://your-server:8080/c/xyz789",
    "compact_txt": "http://your-server:8080/c/xyz789/links.txt",
    "compact_b64": "http://your-server:8080/c/xyz789/links.b64"
  }
}

When api_user_inbound_tag is set, the user is also added to Xray (config file or API).

Get a user

GET /api/users/{id}

Delete a user

DELETE /api/users/{id}

{id} accepts a numeric id or a username (including email format like alice@example.com). Applies to GET, DELETE, enable, disable, token, routes, and clients routes.

When api_user_inbound_tag is set, the user is also removed from Xray.

Example: create and delete (bash)

HOST="http://localhost:8080"
ADMIN="your-secret-admin-token"

# 1) Create user
CREATE_JSON=$(curl -sS -X POST "$HOST/api/users" \
  -H "X-Admin-Token: $ADMIN" \
  -H "Content-Type: application/json" \
  -d '{"username":"alice@example.com"}')
echo "$CREATE_JSON"

# 2) Delete by username (no jq needed)
curl -sS -X DELETE "$HOST/api/users/alice@example.com" \
  -H "X-Admin-Token: $ADMIN"
# {"status":"deleted"}

# — or by numeric id (needs jq)
USER_ID=$(echo "$CREATE_JSON" | jq -r '.user.id')
curl -sS -X DELETE "$HOST/api/users/$USER_ID" \
  -H "X-Admin-Token: $ADMIN"

# 3) Confirm gone
curl -sS -H "X-Admin-Token: $ADMIN" "$HOST/api/users/alice@example.com"
# {"error":"user not found"}

Enable / disable a user

PUT /api/users/{id}/enable
PUT /api/users/{id}/disable

When api_user_inbound_tag is set, the user is added to or removed from Xray accordingly.

Regenerate subscription token

POST /api/users/{id}/token

Returns new {token, sub_url}. The old URL stops working immediately.

List user's inbound connections

GET /api/users/{id}/clients

Shows which inbounds the user is enrolled in and whether each is enabled.

Add one inbound connection for an existing user

POST /api/users/{id}/clients
Content-Type: application/json

{
  "tag": "vless-xhttp-in",
  "protocol": "vless"
}

Example:

curl -H "X-Admin-Token: <admin-token>" \
  -X POST http://<host>:8080/api/users/16/clients \
  -d '{"tag":"vless-xhttp-in"}'
  • tag is required.
  • protocol is optional. If omitted, it is resolved by tag from synced inbounds, then falls back to api_user_inbound_protocol.
  • If the user is already enrolled in this inbound, the existing client record is returned (idempotent behavior).

Enable / disable a specific connection

PUT /api/users/{userId}/clients/{inboundId}/enable
PUT /api/users/{userId}/clients/{inboundId}/disable

Use this to give a user access to only certain servers/protocols.

Inbounds

List all synced inbounds

GET /api/inbounds
[
  {
    "id": 1,
    "tag": "vless-reality",
    "protocol": "vless",
    "port": 443,
    "config_file": "/etc/xray/config.d/vless-reality.json"
  }
]

Trigger manual sync

POST /api/sync

Forces an immediate re-scan of config_dir. Useful after editing Xray configs.

Balancer

Get balancer config

GET /api/config/balancer

Update balancer settings at runtime

PUT /api/config/balancer
Content-Type: application/json

{
  "strategy": "leastPing",
  "probe_url": "https://www.gstatic.com/generate_204",
  "probe_interval": "30s"
}

Reset to config file defaults

PUT /api/config/balancer
Content-Type: application/json

{"reset": true}

Health check

GET /health
{"status": "ok"}

No authentication required. Use this for uptime monitoring.


Routing Rules

Raven Subscribe generates Xray client configs with a three-tier routing system:

User rules  →  Global rules  →  Built-in defaults
(highest priority)              (lowest priority)

Built-in defaults

Every generated config includes these rules automatically:

  • Direct: Russian services (Yandex, VK, Lamoda, etc.), private IPs, geoip:ru
  • Proxy: geosite:ru-blocked, geoip:ru-blocked
  • Block: Ads and public torrent trackers

Add a global rule (applies to all users)

POST /api/routes/global
Content-Type: application/json

{
  "type": "field",
  "outboundTag": "direct",
  "domain": ["example.com", "geosite:cn"]
}

Add a per-user rule

POST /api/users/{id}/routes
Content-Type: application/json

{
  "type": "field",
  "outboundTag": "block",
  "domain": ["ads.example.com"]
}

Rule schema

{
  "id": "optional-id",
  "type": "field",
  "outboundTag": "direct | proxy | block",
  "domain": ["example.com", "geosite:ru-blocked"],
  "ip": ["1.1.1.1/32", "geoip:ru"],
  "network": "tcp | udp",
  "port": "443",
  "protocol": ["http", "tls"],
  "inboundTag": ["socks"]
}

outboundTag must be one of: direct, proxy, block.


Supported Protocols & Transports

Protocols

Protocol Share link format Notes
VLESS vless://uuid@host:port?...#tag Supports REALITY, TLS, plain
VMess vmess://base64(json)
Trojan trojan://password@host:port?...#tag
Shadowsocks ss://base64(method:pass)@host:port#tag Single and multi-user
SOCKS No share link format

Transport layers

Transport Description
TCP Raw TCP, with optional HTTP header obfuscation
WebSocket WS with path and host headers
gRPC gRPC with serviceName
HTTP/2 H2 with host and path
mKCP UDP-based, with header types
QUIC QUIC transport
HTTPUpgrade HTTP upgrade handshake
XHTTP / SplitHTTP Split HTTP for CDN-friendly connections

Security layers

Security Notes
TLS Strips server certificates, sets fingerprint: chrome by default
REALITY Auto-derives publicKey from server's privateKey, picks first serverName and shortId

Docker

Run with Docker Compose

# docker-compose.yml
services:
  raven-subscribe:
    image: ghcr.io/alchemylink/raven-subscribe:latest
    ports:
      - "8080:8080"
    volumes:
      - ./config.json:/etc/xray-subscription/config.json:ro
      - /etc/xray/config.d:/etc/xray/config.d:ro
      - raven-data:/var/lib/xray-subscription
    restart: unless-stopped

volumes:
  raven-data:
docker compose up -d

Build from source

docker build -t raven-subscribe .
docker run -p 8080:8080 \
  -v ./config.json:/etc/xray-subscription/config.json:ro \
  -v /etc/xray/config.d:/etc/xray/config.d:ro \
  raven-subscribe

Contributing

See CONTRIBUTING.md for guidelines.

Before submitting a PR

go test ./... -race
golangci-lint run --timeout=5m

Release

make release VERSION=v1.2.3

License

MIT © AlchemyLink

About

Subscription server for Xray-core — generates per-user client configs, unique sub URLs, and supports VLESS, VMess, Trojan, Shadowsocks across all transports.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages