GhostGate is a sales and subscription management panel for 3x-ui VPN panels. It provides a Telegram bot for managing subscriptions, a web admin panel, and automatic traffic synchronization across multiple nodes.
- Multi-node support - Manage subscriptions across multiple 3x-ui servers with shared data limits
- Telegram bot - Create, edit, delete, and monitor subscriptions via bot commands
- Web admin panel - Real-time system monitoring, subscription management, node management, logs
- Auto sync - Background worker syncs traffic usage and enforces data/expiry limits
- Subscription links - Standard VLESS and VMess subscription URLs with QR codes
- External proxy support - Respects 3x-ui external proxy configurations for CDN setups
- Compiled binary - Linux amd64 and arm64 (Ubuntu 22.04+ compatible), no Python required on server
- systemd service - Automated start, restart, logging
- Auto-update - Automatic binary updates via GitHub releases; manual update via
ghostgate updateor the Settings page - Bulk operations - Bulk delete, enable, or disable multiple subscriptions at once from the web panel
- Easy installation - One-command setup script with interactive configuration
wget https://raw.githubusercontent.com/frenchtoblerone54/ghostgate/main/scripts/install.sh -O install.sh
chmod +x install.sh
sudo ./install.shSave the panel URL shown at the end — it is your admin panel access path.
/create [--comment Name] [--note X] [--data GB] [--days N] [--firstuse-days N] [--firstuse-seconds N] [--ip N] [--nodes 1,2|all|none]
/delete <id or comment>
/stats <id or comment>
/list [page]
/edit <id or comment> [--comment X] [--note X] [--data GB] [--days N] [--firstuse-days N] [--firstuse-seconds N] [--no-firstuse] [--remove-data GB] [--remove-days N] [--no-expire] [--ip N] [--enable] [--disable]
/regen <id or comment>
/reguuid <id or comment>
/resettraffic <id or comment>
/configs <id or comment>
/nodes
/addnode --name X --addr http://... --user X --pass X --inbound N [--proxy http://...] [--multiplier N]
/editnode <id> [--name X] [--addr X] [--user X] [--pass X] [--proxy X] [--enable] [--disable]
/delnode <id>
/subnodes [node_id]
/addsubnode --node N --inbound N [--name X] [--multiplier N]
/editsubnode <id> [--name X] [--inbound N] [--multiplier N] [--enable] [--disable] [--move-up] [--move-down]
/delsubnode <id>
Enabling/disabling a node or sub-node removes or recreates its clients on the 3x-ui panel. The subscription URL browser page shows individual per-node config URLs and QR codes.
All settings are stored in /opt/ghostgate/.env. They can also be edited from the Settings page in the web panel (restart required for changes to take effect).
| Variable | Default | Description |
|---|---|---|
BASE_URL |
Public URL of your server (e.g. https://your-domain.com) |
|
BOT_TOKEN |
Telegram bot token from @BotFather | |
ADMIN_ID |
Your Telegram user ID | |
PANEL_PATH |
auto-generated | Secret path for the web panel |
HOST |
127.0.0.1 |
Listen host |
PORT |
5000 |
Listen port |
SYNC_INTERVAL |
20 |
Traffic sync interval in seconds |
BOT_PROXY |
HTTP proxy for Telegram bot (optional) | |
UPDATE_PROXY |
HTTP proxy for auto-updater (optional) | |
DATA_LABEL |
Data Usage |
Label for data section on subscription page |
EXPIRE_LABEL |
Time Remaining |
Label for expiry section on subscription page |
PANEL_THREADS |
8 |
Waitress worker thread count |
DB_PATH |
/opt/ghostgate/ghostgate.db |
SQLite database path |
LOG_FILE |
/var/log/ghostgate.log |
Log file path |
AUTO_UPDATE |
false |
Enable automatic binary updates |
UPDATE_CHECK_INTERVAL |
300 |
Seconds between update checks |
GHOSTGATE_RESTART_OVERLIMIT_EXPIRED |
false |
Restart affected 3x-ui Xray services when sub-nodes are newly disabled due to overlimit or expiry |
The web panel exposes a REST API at /{panel_path}/api/. It is protected by the secret panel path — no separate authentication token is required. The same API is used by the web panel itself.
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/subscriptions |
List subscriptions. Query params: page, per_page (0 = all), search, sort_by (used_bytes or expire_at), sort_dir (asc or desc) |
GET |
/api/subscriptions/stream |
SSE stream — emits only changed/deleted subscriptions every 5s |
POST |
/api/subscriptions |
Create subscription and add to nodes. Body: comment, note, data_gb, days, ip_limit, node_ids, show_multiplier, expire_after_first_use_seconds |
GET |
/api/subscriptions/<id> |
Get subscription with node list |
PUT |
/api/subscriptions/<id> |
Update fields: comment, note, data_gb, days, ip_limit, enabled, show_multiplier, expire_after_first_use_seconds, remove_days, remove_expiry, remove_data_limit |
DELETE |
/api/subscriptions/<id> |
Delete subscription and remove clients from all nodes |
GET |
/api/subscriptions/<id>/stats |
Get traffic stats |
GET |
/api/subscriptions/<id>/qr |
QR code PNG for the subscription link |
POST |
/api/subscriptions/<id>/nodes |
Add node(s) to an existing subscription |
DELETE |
/api/subscriptions/<id>/nodes/<node_id> |
Remove a node from a subscription |
POST |
/api/subscriptions/<id>/regen-id |
Regenerate the subscription nanoid (updates XUI clients). Returns {new_id, url} |
POST |
/api/subscriptions/<id>/regen-uuid |
Regenerate the VLESS/VMess client UUID across all nodes. Returns {ok, uuid, errors[]} |
POST |
/api/subscriptions/<id>/reset-traffic |
Reset traffic counters to zero in all 3x-ui nodes and the database. Re-enables clients if subscription is active. Returns {ok} |
Create subscription — request body:
{
"comment": "John Doe",
"note": "Note shown in subscription (optional)",
"data_gb": 10,
"days": 30,
"ip_limit": 2,
"node_ids": [1, 2]
}Create subscription — response:
{
"id": "abc123...",
"uuid": "xxxxxxxx-...",
"url": "https://your-domain.com/sub/abc123...",
"errors": []
}The errors array lists any nodes that failed to receive the client — the subscription is still created in the database.
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/nodes |
List all nodes (password omitted) |
POST |
/api/nodes |
Add a node |
PUT |
/api/nodes/<id> |
Update node fields. Setting enabled=0 deletes all clients from 3x-ui; enabled=1 recreates them |
DELETE |
/api/nodes/<id> |
Delete a node |
PUT |
/api/nodes/reorder |
Reorder nodes. Body: {"node_ids": [1, 2, 3]} |
GET |
/api/nodes/<id>/test |
Test connection to node |
GET |
/api/nodes/<id>/inbounds |
List sub-nodes for a node |
POST |
/api/nodes/<id>/inbounds |
Add a sub-node. Body: {"inbound_id": 1, "name": "...", "traffic_multiplier": 1.0} |
PUT |
/api/nodes/<id>/inbounds/<ni_id> |
Update sub-node. Setting enabled=0 deletes all clients; enabled=1 recreates them. Changing traffic_multiplier preserves prior traffic at old rate. Changing inbound_id migrates all clients from the old inbound to the new one |
DELETE |
/api/nodes/<id>/inbounds/<ni_id> |
Delete a sub-node |
PUT |
/api/nodes/<id>/inbounds/reorder |
Reorder sub-nodes. Body: {"ni_ids": [1, 2, 3]} |
GET |
/api/nodes/<id>/inbounds/<ni_id>/test |
Test sub-node connection and inbound reachability |
GET |
/api/subscriptions/<id>/configs |
Get all per-node config URLs for a subscription |
PUT |
/api/subscriptions/<id>/nodes/reorder |
Reorder a subscription's nodes. Body: {"node_ids": [1, 2, 3]} |
Add node — request body:
{
"name": "Germany 1",
"address": "http://1.2.3.4:54321",
"username": "admin",
"password": "secret",
"inbound_id": 1,
"proxy_url": null
}Add node(s) to subscription — request body:
{ "node_ids": [1, 2] }Nodes already assigned to the subscription are silently skipped.
Removing a node from a subscription (DELETE /api/subscriptions/<id>/nodes/<node_id>) preserves the traffic that node had already consumed — it is added to the subscription's internal traffic_preserved offset so the total usage does not drop after removal.
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/bulk/nodes |
Add or remove a node across multiple subscriptions |
POST |
/api/bulk/delete |
Delete multiple subscriptions and remove their clients from all nodes |
POST |
/api/bulk/toggle |
Enable or disable multiple subscriptions |
POST |
/api/bulk/extend |
Add data (GB) and/or days to multiple subscriptions |
POST |
/api/bulk/data |
Multiply or divide the data limit of multiple subscriptions by a factor |
POST |
/api/bulk/note |
Set or clear the note on multiple subscriptions |
POST |
/api/bulk/tags |
Add or remove a tag across multiple subscriptions |
/api/bulk/nodes request body:
{
"sub_ids": ["abc123", "def456"],
"node_ids": [1],
"action": "add"
}action is either "add" or "remove". Returns {"ok": true, "errors": [...]}.
/api/bulk/delete request body:
{ "sub_ids": ["abc123", "def456"] }Returns {"ok": true, "deleted": 2}.
/api/bulk/toggle request body:
{ "sub_ids": ["abc123", "def456"], "enabled": false }Returns {"ok": true}.
/api/bulk/extend request body:
{ "sub_ids": ["abc123", "def456"], "data_gb": 10, "days": 30 }Both data_gb and days are optional and additive — data is added to the current limit, days are extended from the current expiry (or from now if no expiry is set). Negative values subtract — e.g. "data_gb": -5 removes 5 GB (clamped to 0), "days": -7 removes 7 days from the current expiry (skipped if no expiry set). Pass "remove_expiry": true to clear the expiry date (takes priority over days). Pass "remove_data_limit": true to set unlimited data (takes priority over data_gb). All expiry changes are pushed to 3x-ui nodes immediately. Returns {"ok": true}.
Note: Changing
data_gbon a subscription viaPUT /api/subscriptions/<id>immediately recalculates and pushes per-node client limits to all 3x-ui panels. If the new limit is higher than the current usage and the subscription was over-limit, clients are re-enabled immediately without waiting for the next sync cycle.
/api/bulk/data request body:
{ "sub_ids": ["abc123", "def456"], "factor": 2, "action": "multiply" }action is either "multiply" or "divide". Multiplies or divides the current data_gb of each subscription by factor. Subscriptions with unlimited data (0 GB) are skipped. Returns {"ok": true}.
/api/bulk/note request body:
{ "sub_ids": ["abc123", "def456"], "note": "Promo batch" }Omit note or pass null to clear it. The note is emitted as a vless:// info entry in each subscription. Returns {"ok": true}.
/api/bulk/tags request body:
{ "sub_ids": ["abc123", "def456"], "tag": "premium", "action": "add" }action is either "add" or "remove". Returns {"ok": true}.
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/status |
System metrics (CPU, RAM, disk, network, load) |
GET |
/api/update |
Check for a binary update. Returns {current, latest, update_available} |
POST |
/api/update |
Download and apply the latest binary update, then restart |
GET |
/api/settings |
Get all .env config values |
POST |
/api/settings |
Save config values (restart required for most changes) |
POST |
/api/restart |
Restart the GhostGate service |
GET |
/api/logs |
Last 200 log lines (plain text) |
GET |
/api/logs/stream |
Live log stream (SSE, sends : heartbeat every 10 s when idle) |
The end-user subscription URL is public and requires no authentication:
https://your-domain.com/sub/<id>
This returns a plain-text config list (VLESS and VMess) compatible with standard VPN clients.
server {
listen 443 ssl http2;
server_name your-domain.com;
ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
location / {
proxy_pass http://127.0.0.1:5000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 3600;
proxy_send_timeout 3600;
proxy_buffering off;
}
}sudo systemctl status ghostgate
sudo systemctl restart ghostgate
sudo systemctl stop ghostgate
sudo journalctl -u ghostgate -fThe CLI uses rich for colored terminal output matching the panel theme.
| Command | Description |
|---|---|
ghostgate |
Start the service (normal mode) |
ghostgate --version |
Print version and exit |
ghostgate --generate-path |
Generate a new random panel path and exit |
ghostgate help |
Show CLI help and available commands |
ghostgate status |
Show system status (CPU, RAM, disk, uptime) |
ghostgate list [--search X] |
List all subscriptions, with optional search filter |
ghostgate stats <id|comment> |
Show traffic stats for a subscription |
ghostgate create --comment X [--note X] [--data GB] [--days N] [--ip N] [--nodes 1,2|all|none] |
Create a new subscription |
ghostgate edit <id|comment> [--data GB] [--days N] [--remove-data GB] [--remove-days N] [--no-expire] [--comment X] [--note X] [--ip N] [--enable] [--disable] |
Edit an existing subscription |
ghostgate regen <id|comment> |
Regenerate the subscription nanoid (old URL stops working) |
ghostgate regen-uuid <id|comment> |
Regenerate the VLESS/VMess UUID — disconnects existing clients |
ghostgate reset-traffic <id|comment> |
Reset traffic counters to zero in all 3x-ui nodes |
ghostgate delete <id|comment> |
Delete a subscription and remove its clients from all nodes |
ghostgate nodes |
List all configured nodes |
ghostgate addnode --name X --addr http://host:port --user X --pass X --inbound N [--proxy http://...] [--multiplier N] |
Add a node and create its first sub-node |
ghostgate editnode <id> [--name X] [--addr X] [--user X] [--pass X] [--proxy X] [--enable] [--disable] |
Edit or enable/disable a node (enable/disable removes/recreates all its clients) |
ghostgate delnode <id> |
Delete a node |
ghostgate subnodes [node_id] |
List sub-nodes |
ghostgate addsubnode --node N --inbound N [--name X] [--multiplier N] |
Add a sub-node |
ghostgate editsubnode <id> [--name X] [--inbound N] [--multiplier N] [--enable] [--disable] [--move-up] [--move-down] |
Edit sub-node; enable/disable removes/recreates clients; multiplier changes preserve prior usage at old rate |
ghostgate delsubnode <id> |
Delete a sub-node |
ghostgate configs <id|comment> |
Show per-node config URLs for a subscription |
ghostgate bot [--enable|--disable] |
Show or toggle the Telegram bot (restart required) |
ghostgate update |
Check for an update and apply it if available |
Examples:
ghostgate list
ghostgate list --search alice
ghostgate stats abc123
ghostgate create --comment "Alice" --data 50 --days 30 --ip 2 --nodes 1,2
ghostgate edit abc123 --data 100 --days 60
ghostgate edit abc123 --remove-data 5 --remove-days 7
ghostgate edit abc123 --disable
ghostgate regen abc123
ghostgate configs abc123
ghostgate delete abc123
ghostgate nodes
ghostgate addnode --name "US-1" --addr http://1.2.3.4:54321 --user admin --pass secret --inbound 1
ghostgate editnode 1 --disable
ghostgate editnode 1 --enable
ghostgate delnode 1
ghostgate subnodes
ghostgate addsubnode --node 1 --inbound 2 --name "CDN"
ghostgate editsubnode 3 --multiplier 2
ghostgate editsubnode 3 --move-up
ghostgate editsubnode 3 --disable
ghostgate bot --disable
ghostgate bot --enable
ghostgate status
ghostgate update
ghostgate --version
ghostgate --generate-pathpip install -r requirements.txt pyinstaller
./build/build.shOr using Docker (recommended for Ubuntu 22.04 GLIBC compatibility):
./build/build-docker.shBinary will be created in dist/.
Join the Telegram channel for updates and announcements: @GhostSoftDev
MIT License - See LICENSE file for details