inbounds · profiles · subscription · bypass · cascade · hysteria2 · amneziawg
Read this in other languages
🇺🇸 English ·
🇷🇺 Русский ·
🇨🇳 简体中文
One bash script turns a clean VPS into a personal VLESS Reality server.
Xrayebator installs Xray-core, brings up Reality inbounds on random ports, and provisions a standard
schema-v3 HAPP profile of seven routes for a new setup. Existing seven-route profiles can be reused only
when they have enough live routes, so inspect the profile JSON when debugging labels or schema. The client
receives the routes as a single HTTPS subscription link. As of the multi-protocol stage, optional
Hysteria 2 (fast UDP) and AmneziaWG 2.0/3.1 (system VPN) backends can be installed on the same VPS from
menu items 11–13, with per-profile grants wired into the shared revoke/expire lifecycle. Current server
line — 3.0; the optional Electron desktop app is versioned separately.
curl -fsSLo ./xrayebator-install.sh \
https://raw.githubusercontent.com/howdeploy/Xrayebator/main/install.sh
less ./xrayebator-install.sh # review the script before running it
sudo bash ./xrayebator-install.sh
# Step-control flags (interrupt-safe install):
# --check Show which of the 10 steps are already done
# --resume Continue from the first unfinished step
# --fresh Reset all markers and start from zero
Debian 12/13 · Ubuntu 22.04/24.04 · 512 MB RAM or more · root or sudo
Then run sudo xrayebator, pick item 6, and the subscription is ready.
Details: Quick start
Purpose · Capabilities · How it works · Quick start · Documentation · Limitations · Updating
A personal VLESS Reality setup is a dozen manual steps: assemble the inbound, generate keys, pick an
SNI, avoid breaking the config, deliver the link to a phone. One typo in config.json and Xray does
not start.
A single route is also not enough. DPI blocks transports unevenly: TCP Vision survives in one network, only gRPC in another, and nothing but XHTTP in a third. Maintaining separate configs for each case by hand is impractical.
Xrayebator solves both problems:
- a profile is not one route but a set of
routessharing a singlesub_token; - the client receives the whole set as one short subscription link instead of seven
vless://links; - runtime config mutations go through a backup, explicit
xray run -testvalidation and an automatic rollback, so a failed edit does not intentionally leave the server without VPN; - changing SNI, port or fingerprint does not require recreating the profile. SNI and port are shared inbound settings; fingerprint is client-side per profile/route.
The project is developed and tested by one person. The consequences are listed plainly in Known limitations — read that section before installing on a VPS you care about.
| Capability | What it does | Where it lives |
|---|---|---|
| Xray-core installation | Downloads the release from GitHub, always verifies SHA-256 against .dgst, installs the binary via install -m 755, runs a self-test |
install.sh |
| Reality inbounds | Brings up inbounds on free ports in 30000-60000, checking both config.json and actually listening sockets |
xrayebator |
| Multi-route profile | One profile = a set of routes with a shared sub_token; several profiles may share one port |
profiles/<name>.json |
| HAPP subscription | Serves the vless:// list, a managed Global Proxy routing profile and token-protected geo databases; nginx publishes it over HTTPS |
subhttp.sh, xrayebator-sub.service |
| Subscription formats | One token — four bodies: HAPP, base64 for v2rayNG/v2rayN, Clash/mihomo YAML and sing-box JSON; the format is chosen by ?target= or by User-Agent |
_subscription_render_*_pure, subhttp.sh |
| DNS-01 subscription | The Let's Encrypt certificate is proven by a TXT record (Cloudflare/Route53), so the HTTPS subscription by domain works even where the hoster filters port 80. It doubles as a deploy-time fallback (quickstart --dns01-*) and as a repair for an already-degraded http_tls subscription (repair-dns01, the Fix with DNS-01 button in the GUI); dns01-test verifies a domain and a token in the Let's Encrypt staging environment without spending production quota, and --staging does the same for a real issuance |
dns01-setup, dns01-test, dns01-status, repair-dns01 |
| Post-quantum encryption | VLESS Encryption mlkem768x25519plus is available on every transport (profile-pq --transport), not just on xhttp-pq; a PQ route owns a dedicated port and can be added at deploy time with quickstart --pq or the GUI wizard toggle |
profile-pq, .vless_encryption, .vless_decryption |
| v2ray compatibility | v2rayNG and v2rayN receive a classic base64 body without HAPP metadata |
subhttp.sh |
| Subscription revoke | Generates a new 32-character hex token; the old URL stops working. Full revocation (--full) also rotates the uuid, so already-downloaded configs stop connecting |
openssl rand -hex 16, profile-revoke |
| Profile expiry | A profile can carry an expiry date; xrayebator-expire.timer switches the client off the inbounds when it passes and restores the same uuid on renewal |
profile-expire, expire-check |
| Traffic accounting and limits | Per-profile usage from the Stats API: xrayebator-traffic.timer accumulates gigabytes, the limit switches the client off when exhausted and restores it on raise/reset; the subscription serves real subscription-userinfo |
profile-limit, traffic-check |
| Monitoring and Telegram alerts | Every 15 minutes it checks services, backends (Hysteria 2, AWG), expiries and limits, subscription liveness and its TLS mode, and reboots; once a day — SNI reachability from the profiles; it writes only changes to Telegram | menu 14, xrayebator-monitor.timer |
| SSH brute-force protection | xrayebator ssh-protect installs fail2ban with an sshd jail (4 failures in 10 min → 30-minute ban, repeat offenders up to a day) and sshd_config.d limits (MaxAuthTries 3, LoginGraceTime 30). Password login stays enabled so the GUI keeps managing the server; enabled automatically on install and deploy, toggled from the Security and TLS tab. Live VPS: 9 953 failed logins/24 h from 115 addresses before, ≈72/h after |
ssh-protect, fail2ban |
| Channel check | xrayebator channel-test starts a local Xray client for every profile route and pushes a real request through the tunnel: JSON with working/checked, latency and the reason. No Let's Encrypt needed (Reality and PQ are self-contained), PQ routes are covered too; live run — 8/8 routes, 152–252 ms |
channel-test |
| Reachable default SNI | When picking the SNI for new profiles the manager checks reachability from the VPS itself and takes the first working one: an unreachable destination hangs the Reality handshake (on a live server that made 5 of 7 channels dead) | _default_sni_for_transport, probe-test |
| Bypass routing | Eight domain groups can be sent straight through freedom, skipping the VPN |
menu 7 |
| Cascade | Switches the tcp,udp catch-all to a foreign VLESS Reality upstream of type tcp or xhttp |
upstreams/cascade.json |
| Self-steal stub | Puts nginx with a valid certificate on 127.0.0.1:9444 and points a Reality fallback at it |
menu 9 |
| Safe JSON writes | Writes a temporary file inside the destination directory, validates it, renames atomically | safe_jq_write |
| Safe restart | Runs xray run -test -config before restarting; rolls the config back from a backup on failure |
safe_restart_xray |
| Migrations | One-shot migrations driven by marker files: backup → edit → restart → marker | run_migration |
| geo databases | Places extended geoip.dat and geosite.dat from Loyalsoldier releases into /usr/local/share/xray |
install.sh |
Not supported and not claimed: H2, WebSocket, SplitHTTP.
Control flow. Every config change follows the same path:
sudo xrayebator
│
▼
xrayebator (bash)
create profile · change SNI/port/fingerprint · migrations · routing
│
├─ backup_config ───────► /usr/local/etc/xray/backups/<timestamp>
│
├─ safe_jq_write ───────► config.json + profiles/<name>.json
│
└─ safe_restart_xray
│
├─ xray run -test -config → ok ──► systemctl restart xray
│
└─ invalid config ─────────────────► rollback from backup,
Xray keeps running
on the previous config
Client flow. From the subscription link to the open internet:
client (HAPP)
│ https://<domain-or-ip>/sub/<32-hex-token>
▼
nginx :443 (or :8443 when 443 is taken)
│ proxy_pass
▼
xrayebator-sub.service 127.0.0.1:8080
│ reads profiles/*.json and checks routes against the live config.json
▼
vless:// list — HAPP receives all 7 routes (base64 body: 6, PQ routes dropped)
│
▼
Reality inbound on a port in 30000-60000 (User=xray, CAP_NET_BIND_SERVICE)
│
│
└─ all other tcp/udp ─────────────────► direct
OR cascade-upstream ──► foreign VPS
The HAPP flow creates or reuses a profile of seven routes:
| Route | Transport | Purpose |
|---|---|---|
xhttp-legacy |
xhttp | HAPP-compatible XHTTP fallback, decryption=none, no PQ |
xhttp-pq |
xhttp | XHTTP with post-quantum encryption mlkem768x25519plus |
tcp-mux |
tcp | TCP Reality without Vision flow, a separate compatible fallback |
grpc |
grpc | gRPC Reality; sensitive to HTTP/2 and SNI |
tcp-vision |
tcp | TCP Reality with xtls-rprx-vision |
tcp-utls-firefox |
tcp | TCP Vision with a Firefox fingerprint |
tcp-xudp |
tcp | TCP Vision + XUDP, a narrow fallback for harsh mobile networks |
The managed HAPP profile uses schema version 3 and seven routes, and the HAPP body publishes all seven of them — PQ included, because HAPP supports ML-KEM. The base64 body served to v2rayNG/v2rayN drops PQ routes instead (six of seven), since a client without ML-KEM cannot use them; Clash/mihomo and sing-box skip PQ and xhttp routes with a recorded reason. All seven routes always remain in the profile JSON.
The default client fingerprint for new and updated profiles is firefox. Explicitly chosen
fingerprints other than the deprecated chrome are preserved across updates.
Route order in the subscription is stable but it is not a best-to-worst ranking. Whether a transport works depends on the client, its bundled Xray-core version and the specific network.
| Mode | Result | When to use |
|---|---|---|
| Public TLS by VPS IP | https://<ip>/sub/<token> |
Quick start without a domain. Let's Encrypt IP certificates are short-lived, renewal is mandatory |
| Public TLS by domain | https://sub.example.com/sub/<token> |
Recommended for permanent use |
| Local-only debug | http://127.0.0.1:8080/sub/<token> |
Only for checks from the VPS itself or through an SSH tunnel. Does not work from a phone directly |
Hard prerequisites are a Bash shell, root (or sudo), an apt-based Debian/Ubuntu-like system and a running systemd environment. The tested matrix is Debian 12/13 and Ubuntu 22.04/24.04.
- 512 MB RAM minimum, 1 GB or more recommended
- 1 CPU core, 2 or more recommended
- 1 GB of free disk space
The installer pulls ca-certificates curl wget jq qrencode uuid-runtime ufw unzip openssl socat.
The installer checks the apt/systemd prerequisites but does not enforce the release-version matrix. It detects the active SSH port before enabling UFW; if the port cannot be determined or opened safely, inactive UFW stays disabled. Take a snapshot before installing on a VPS that matters.
Download the script, review it, and only then run the local file as root:
curl -fsSLo ./xrayebator-install.sh \
https://raw.githubusercontent.com/howdeploy/Xrayebator/main/install.sh
less ./xrayebator-install.sh
sudo bash ./xrayebator-install.shThe installer does not change host TCP or sysctl settings, but manages UFW on its own. Read Environment variables and Firewall and host networking BEFORE running it, especially if SSH listens on a non-standard port.
- Xrayebator OpenWrt/Cudy companion — an independent community toolkit for running an Xray client on OpenWrt/Cudy routers, with staged activation and automatic rollback, fail-closed routing, tunnel health and memory supervision, and optional Windows dual-uplink failover. It is not an official Xrayebator component.
-
Open the menu:
sudo xrayebator
-
Choose
6) Подписка HAPP— the HAPP subscription entry. -
Choose the publishing mode: by VPS IP for a quick start without a domain, by domain for permanent use.
-
Xrayebator creates the
happprofile, brings up the inbounds, issues the certificate and prints the URL and a QR code. -
Import the subscription URL or QR into HAPP — not an individual
vless://link.
HAPP 3.3.6 or newer is required. If Xrayebator routes have a green ping but connections do not work, fully quit every old HAPP process, start exactly one current instance and refresh the subscription. A green route ping uses a separate temporary Xray-core and does not prove that the main TUN is healthy. On Linux,
ss -lntp | grep ':10808'must show the main HAPP core listening.
Current builds publish and activate the managed xrayebator-default profile with Global Proxy and
Cloudflare DoH. Its geo databases are downloaded through the same authenticated subscription URL,
not directly from GitHub. After a server update, force-refresh the subscription and reconnect once:
HAPP overwrites the old profile with the same name and clears its red geo-file warning after both
downloads succeed. Third-party routing profiles can still override the subscription, so disable
them while testing.
Use 1) Создать новый профиль for manual control over SNI, transport or a single route.
The terminal interface is in Russian. This README and the documentation describe every menu item, so the interface language is not a blocker.
Alongside the terminal menu there is an optional desktop application (Electron + React, src/) that
manages a VPS over SSH. It does not replace the bash engine — every operation is still performed
server-side by xrayebator, and the GUI only drives the same commands over SSH.
desktop app (Electron · React)
│ SSH + the documented CLI
▼
xrayebator (bash) ──► /usr/local/etc/xray/
Interface language (Русский / English / 简体中文) is switched in the header of the main screen and is
remembered in localStorage. The terminal menu remains Russian.
What the GUI can do:
| Page | Operations |
|---|---|
| Dashboard | Server cards with reachability and installation status; an empty screen offers “Deploy a new server” or “Connect an existing server”; language switch |
| Add server | Deploy a new VPS with an explicit email choice: quickstart --email or quickstart --without-email; two optional wizard blocks — the PQ route toggle (--pq) and the DNS-01 fallback (domain + provider + credentials, --dns01-*), both OFF by default; save the server and public subscription |
| Connect existing | Import a recognized Xrayebator installation over SSH (password or key) using read-only xrayebator inspect --json; partial installs are saved with diagnostics, without automatic repair |
| Server keys | Refresh the public subscription, copy the URL, show vless:// links and QR codes; with backends installed it also shows one Hysteria 2 card per granted profile (link + QR) and one AmneziaWG card per granted profile (client .conf, key fields and two QRs — the plain one and the app-native vpn:// for AmneziaVPN), plus a Client links card with the Clash/mihomo YAML and sing-box JSON links |
| Server profiles | The existing-profile list as its own screen, opened from the settings tab strip: search by name, SNI or subscription URL plus an all/active/disabled filter, and one card per profile with a transport icon, an Active/Disabled status, HAPP/PQ ×N badges, chips for transport, routes, port, SNI, expiry and traffic, the subscription URL, and copy/settings actions; the Hysteria 2 and AmneziaWG cards for granted profiles stay in the same list |
| Server settings | SSH access by password or private key, direct root or sudo; create/delete profiles (the list itself is on Server profiles), change fingerprint, SNI and port, revoke the subscription, set a profile expiry date, set a traffic limit, add a PQ route, manage the subscription and TLS (DNS-01, including Fix with DNS-01 for a degraded http_tls subscription), plus update or uninstall Xrayebator on the server. Expiry dates are displayed in the server's timezone even when the desktop uses another one. Telegram alerts are terminal-only (alerts-setup, alerts-enable/alerts-disable, alerts-test): the bot token never enters the renderer |
Root + password is the one-click default; key authentication and sudo are optional. A selected private key and a successfully used SSH login password are stored in the operating-system keychain via keytar and reused across later SSH operations and app restarts; the server card keeps only their non-secret credential ids and display name. A distinct sudo password and an encrypted-key passphrase are never persisted and are requested again when needed. If the OS keychain is unavailable, there is no plaintext fallback: the secret remains in main-process memory for the current app session and the UI warns that it must be entered again after restart. The app also persists, in the local store, the subscription_url, the fetched vless:// links, the Hysteria 2 links, the AWG client configs, the Clash/sing-box links, per-profile expiry dates and the pinned SSH host-key fingerprint — that is what lets the Keys page and the backend cards paint instantly; none of it is written back to the server. The subscription URL and VLESS links are bearer/client credentials: protect local app data and revoke the subscription from the profile settings on the Server profiles page (full revocation rotates the key as well) after a leak.
The GUI exposes only a subset of the terminal menu. Bypass, probe-test, happ-setup, cascade,
self-steal, Telegram alerts and service logs/status remain terminal-only; profile expiry dates and subscription
revocation are available in the profile settings on the Server profiles page. See
Electron Desktop GUI for the complete boundary, security model and packaging details.
Build and run in the development mode:
npm install
npm run dev # Electron + Vite dev server
npm run build # compile the renderer and the main processElectron checks: npm test runs the 19 unit files in tests/; npm run typecheck checks the
TypeScript surface, including the strict onboarding contracts in tests/type-contracts/.
npm run build produces the app bundle. On native Windows, the POSIX-only
tests/unit/shell-command.test.ts may fail because /bin/sh is absent; Linux CI is the source of truth.
See Testing and Electron Desktop GUI.
| Document | Contents |
|---|---|
| Configuration | Environment variables, firewall and host networking, main menu, commands, bypass, cascade, self-steal, domain and DNS |
| Recommendations | Choosing a VPS by criteria, domain and DNS setup, Cloudflare and Route 53 step by step, subscription formats for clients — no brands, no affiliate links |
| Architecture | Repository and on-server state trees, inbound versus profile, subscription internals |
| Security | Service account and permissions, subscription protection, SSH access to the VPS |
| Troubleshooting | Subscription not refreshing, XHTTP not working, client not connecting and other cases |
| Testing | Local checks, what validation/ covers, manual checks on a live server |
Russian and Chinese versions live in docs/ru/ and docs/zh-CN/.
- The installer requires Bash, root/sudo, an apt-based Debian/Ubuntu-like system and systemd. The tested matrix is Debian 12/13 and Ubuntu 22.04/24.04; release versions are not hard-gated.
- The installer detects the active SSH port before enabling UFW. If the port cannot be determined or
opened safely, inactive UFW stays disabled. Once the safety check succeeds, it adds the fixed
project TCP service list and records only its own rules in
.ufw_owned. xrayebator-updateautomatically removes a detected/opt/AdGuardHomeas deprecated: it first moves Xray DNS back to DoH, then stops the service and deletes the files. Do not update without a snapshot if AdGuard Home is in use on that VPS.- Most Xray state, profiles, markers, keys and manager scripts are root-owned; the
xrayservice account reads what it needs and writes runtime logs under/var/log/xray. Generated metadata such as.server_countryand rollback paths can have narrower owner/mode differences, so inspect the exact file when auditing permissions. - The Xray core is verified by SHA-256 unconditionally, while Loyalsoldier geo databases are downloaded without a checksum check.
xrayebator-uninstallstops and disablesxray, removes/usr/local/bin/xrayand the geo databases in/usr/local/share/xray, deletes/usr/local/etc/xray,/var/log/xray, thexrayebator,xrayebator-update,xrayebator-uninstallandsubhttp.shbinaries, thexray.service,xray@.service,xray.service.dandxrayebator-sub.serviceunits, the nginx vhosts matching the product's names, its own certbot certificates and UFW rules, and thexraysystem user. Before deleting the state it also tears the optional backends down: it disableshysteria-server.serviceandawg-quick@awg0, removes the Hysteria binary, its sysctl file, its certbot deploy-hook, theawg0interface, the AWG sysctl andmodules-load.dentries, and then the whole neutral/usr/local/etc/xrayebatorroot (backend registry and secrets,alerts.jsonwith the bot token). The AmneziaWG package and kernel module themselves stay in the system. Cleanup is path/name based for nginx vhosts, not a general ownership manifest. It leaves global Certbot state, the nginx package, foreign certbot certificates and UFW rules untouched. Domain-mode ACME webroot/var/www/xrayebator-domain-acmemay remain for manual cleanup.- The
tcp-muxroute is kept for compatibility; it is not a mux preset. - H2, WebSocket and SplitHTTP are not supported.
- The Clash/mihomo and sing-box formats carry no routes with PQ encryption (VLESS Encryption is not
supported by their clients) and no xhttp: sing-box has no such transport at all, while in
Clash/mihomo it is enabled by the
?xhttp=1parameter and requires mihomo ≥ 1.19. - The interface imposes no hard limit on users, but real capacity is bound by CPU, RAM, VPS bandwidth, route count and provider limits.
- Installation and project-update lifecycle paths use their own validation/restart/rollback sequences;
they are not identical to the runtime
safe_restart_xraytransaction. Verify Xray, DNS and the subscription after a lifecycle update.
sudo xrayebator update # Xray-core only
sudo xrayebator update dev # manager self-update from a branch, then core update
sudo xrayebator-update [branch] # full lifecycle updater; no arg opens branch selection
sudo xrayebator-uninstall # remove the service and configurationThe names look alike, the meaning does not:
sudo xrayebator update <branch> |
sudo xrayebator-update [branch] |
|
|---|---|---|
| What it starts | Installed manager self-update | Full project lifecycle updater |
| Source | Canonical raw manager file for the requested branch | Selected branch's update.sh workflow |
| Main result | Manager self-update followed by Xray-core update | Manager scripts, data, subscription integration and service refresh |
| Branch selection | Explicit branch is required | No argument shows .current_branch, then prompts; explicit argument selects it |
The current branch is displayed in the updater and stored in /usr/local/etc/xray/.current_branch,
but no-argument xrayebator-update still prompts for a selection. The Electron GUI invokes
xrayebator update <branch>, not the full project updater. After any lifecycle update, wait for
migrations and verify Xray, DNS and the subscription before refreshing the client.
What stays on the system after xrayebator-uninstall is listed in
Known limitations.
Import the subscription URL into the client, not an individual vless:// link. Raw routes from
3) Подключиться по профилю are for diagnostics.
| Client | Status | Comment |
|---|---|---|
| HAPP | Recommended | The target client. Supports subscriptions by URL and QR plus VLESS links |
| v2rayNG | Partial | Receives the base64 subscription, ignores HAPP metadata |
| v2rayN | Partial | VLESS subscriptions work, HAPP specifics are unused |
| Shadowrocket | Manual | Fine for raw VLESS, not the primary subscription client |
| sing-box · Hiddify · NekoBox · mihomo | Subscription | Receive YAML (Clash/mihomo) or JSON (sing-box) over the same URL; no PQ-XHTTP and no HAPP routing |
- Android: HAPP · v2rayNG · NekoBox
- iOS: HAPP · Shadowrocket · V2Box
- Windows: Throne · v2rayN · NekoRay
- macOS: Throne · V2RayXS · Qv2ray
- Linux: Throne · v2rayA · Qv2ray
Client documentation: HAPP subscription · v2rayN subscription format
MIT. See the LICENSE file.
- XTLS/Xray-core — for the protocol.
- HAPP — for the target client and the subscription format.
- 2dust/v2rayNG and 2dust/v2rayN — for the clients and subscription formats.
- Loyalsoldier/v2ray-rules-dat — for the extended geo databases.
- Umalanif/xray-server-setup — for the uTLS reference and automation.
- ServerTechnologies/simple-xray-core — for fast deployment.
- The community — for support and testing.
A star on GitHub is the simplest way to support the project.
Donations:
EVM 0x7acE4442b92f2769c24484c78A13024B139E1A5b
Solana FS9RBrG5yXJty3WNWgkBkfai6BfNoYxGMFeH1LQEpRZr
TON UQA56zsOv3zvU5x-p7iNNDL8jHh9dt7Q7WlY_gfbaj4ZhcyT
BTC 34EznmkBGpBu4dUnzoHL5GBnpg2Rq86v4H