Skip to content

Repository files navigation

RosPanel

RosPanel

Self-hosted VPN control panel built on Xray-core — from a single personal server to a network of nodes.

Release Go Xray-core React Platform Deploy

English · Русский

What it is · Where to get a server · Quick start · Features · CLI · Architecture · Disclaimer · Development · Support


What it is

RosPanel is a self-hosted VPN control panel built on Xray-core. A single process serves several protocols at once, and the panel gives you a web interface for users, subscriptions, plans and payments, routing, statistics, backups and branding — no hand-editing of config files. When one server is no longer enough, the same panel grows into a network of servers: add nodes and every user is served by all of them.

Self-contained: one static binary — no nginx, no certbot, no third-party scripts. The panel issues its own TLS via ACME, and a domain is optional: the certificate will be issued for a bare IP too. The panel is reachable at its own address right after install — no DNS, no reverse proxy, no SSH tunnels. The Xray config is generated from the panel's state (you never touch JSON by hand), users are added and removed on the fly without dropping anyone else's connections, and if the database gets corrupted the panel brings itself back up from the last backup.

Note

This is a control plane, not a VPN client. It configures and operates your own server. The project is intended for educational and research use (see Disclaimer).


🖥️ Where to get a server

The cheapest VPS is enough: 1 vCPU, 1 GB RAM, any Linux — that covers both the panel and Xray for dozens of users.

If you haven't picked a host yet, sign up through the links below. It's a free way to support the project: the price is the same for you, and a share of the rent goes into the panel's development.

VDSina · Aeza · NetGrid · Serv.host · u1Host · Waicore

Thanks to everyone who signs up through them 🙏


🚀 Quick start

Option 1 — install script (recommended)

One command: downloads the release, installs a systemd service, starts it and prints the login.

curl -Ls https://raw.githubusercontent.com/AppsGanin/rospanel/main/install.sh | sudo bash

A domain is optional. The script will ask for one: if you have a domain, enter it; if you don't, just press Enter and the panel comes up on the server's IP. It gets a Let's Encrypt certificate either way — certificates are issued for IP addresses as well, so no browser warnings.

You can set the domain up front and skip the question: curl -Ls … | sudo ROSPANEL_HOST=vpn.example.com bash.

Option 2 — binary + systemd by hand

# download the latest release (replace amd64 with arm64 for ARM servers)
curl -fsSL -o rospanel \
  https://github.com/AppsGanin/rospanel/releases/latest/download/rospanel-linux-amd64
chmod +x rospanel

# install as a service (copies the binary to /usr/local/bin, writes a systemd unit, starts it)
sudo ./rospanel install
#   with a domain right away:  sudo ROSPANEL_HOST=vpn.example.com ./rospanel install

# the login and the secret path are printed ONCE:
journalctl -u rospanel | grep -A6 FIRST-RUN

Option 3 — Docker

docker run -d --name rospanel \
  --network host \
  --cap-add NET_ADMIN \
  -v rospanel-data:/data \
  ghcr.io/appsganin/rospanel:latest

docker logs rospanel | grep -A6 FIRST-RUN

Note

--network host is required so Xray can listen on 443/TCP, 80/TCP and the Hysteria2 UDP ports directly; NET_ADMIN lets the panel manage firewall rules: port hopping and connection limits (nftables), brute-force bans (iptables).

🔑 Default login

Field Value
Username admin
Password admin
Panel path /rospanel/

Right after install the panel is available at https://<domain-or-IP>/rospanel/. The exact link is also in the log (journalctl -u rospanel | grep -A6 FIRST-RUN or docker logs rospanel | grep -A6 FIRST-RUN).

On first login the setup wizard forces a password change and offers to replace the panel path with a random secret — the default admin/admin and /rospanel/ only work up to that step.

Important

After the change the panel is reachable only via the secret path — the root serves a decoy page. Without knowing /<secret>/ there is no login form to find.

🌐 Adding a node

A node is a clean Ubuntu server running the same binary in node mode: the panel generates its config, there is nothing to set up separately. Everything starts in the UI: Servers → Add node, where you enter a name and the node's domain or IP. From there you have two options.

Option 1 — install command. The panel shows a ready one-liner; run it on the node's server as root:

curl -Ls https://raw.githubusercontent.com/AppsGanin/rospanel/main/install.sh \
  | sudo bash -s -- --join 'https://<panel>/<node-api-path>/v1/join#<token>'

Copy the command whole, from the dialog — both the address and the token are filled in automatically. The token is shown once and lives for 24 hours; <node-api-path> is a separate unguessable segment for node sync (neither the panel path nor the REST API path: changing either one won't detach your nodes). If the panel runs on a bare IP (certificate not from a public CA), the panel adds --insecure to the command itself.

Option 2 — install over SSH. The "Install over SSH" tab in the same dialog: the panel logs into the server itself (address, port, user + password or a PEM private key), uploads its own binary and installs the agent — with a live install log. The node version is guaranteed to match the panel's. SSH credentials are never stored.

A few seconds after install the node shows up in the list as online: it reaches out to the panel over outbound HTTPS, so the panel needs no inbound access to it and there is nothing to forward.

systemctl status rospanel-node        # node service
journalctl -u rospanel-node -f        # agent log
rospanel node status                  # local status

Note

One server is either a panel or a node: they share port 443. If the panel service is already on the machine, node install will disable it.


✨ Features

🔐 Protocols, masquerading, TLS

One config, one set of credentials: VLESS-Vision (TCP/443 + uTLS), VLESS-XHTTP-REALITY (masquerading as someone else's TLS), Hysteria2 (UDP + port hopping). On top of that — custom inbounds on every server: VLESS / Trojan / Hysteria2 over any transport (TCP, WebSocket, XHTTP, gRPC, HTTPUpgrade) with their own port, REALITY keys, hop range and fine-grained transport tuning (XHTTP extra, HTTP masquerading, sockopt, extra TLS fields) — as individual fields or raw JSON; the config is validated on the target machine itself (xray -test + port bind) before saving, and combinations a client can't handle are silently kept out of Clash/sing-box subscriptions. The panel hides behind a secret path; any other path serves a decoy site (11 templates). TLS out of the box — ACME (Let's Encrypt / ZeroSSL) with auto-renewal; the certificate can also be issued for a bare IP, with no domain and no DNS.

👤 Users

Traffic and time limits with auto-disable and quota auto-reset (day/week/month/year), a device limit by unique IP. Traffic accounting via Xray Stats, online status, connection list; expired users can be auto-deleted. Search and filters stay fast with hundreds of users, and bulk operations (enable/disable/reset/extend/delete) go through a single Xray reload. The dashboard shows CPU / RAM / swap / disk and VPN traffic in real time.

Access groups decide which connections a user gets: built-in lanes and custom inbounds are ticked per server, a user with no group gets everything, a user in several groups gets the union of their connections. The restriction is server-side — the account simply isn't added to the forbidden Xray inbounds (rather than being hidden in the UI), so both the subscription and a hand-crafted link only ever hand out what's allowed. Membership is editable from both sides — in the user's card and in the group itself — and a user's groups are visible in their card and in the list.

📲 Subscriptions

/<path>/<token> — a base64 list plus a page with a QR code, deep links and import into popular clients (auto-routing headers for Happ / INCY / Mihomo), with your own node names. The link can be reset (token rotation) without changing UUIDs and passwords. An announcement inside the client (Happ, v2RayTun) puts a short text right in the app.

🧭 Routing and egress

block / direct / WARP / Opera categories with priority, geosite/geoip presets with automatic database downloads, egress through Cloudflare WARP (WireGuard) and the free Opera VPN with region selection. Proxy lanes are independent egresses, each with its own socks5/http upstreams and zone rules, balanced across whatever is alive (Observatory).

🌐 Server network (multi-node)

A single panel manages the master and any number of remote nodes. A server is added from the UI: copy one command for a clean Ubuntu box, or let the panel log in over SSH and install the agent — with a live install log. The node reaches out to the panel itself (outbound HTTPS long-poll), so the panel needs no access to the nodes, and moving the panel doesn't detach them. Users, limits and plans roll out to every node; traffic and devices count against shared limits, while statistics and the user card show how much traffic went through each server.

Every server is configured separately (protocols, egress, DNS, REALITY keys, domain and TLS, geo databases, decoy). A node is the same binary in node mode: the panel generates its config, a local xray -test with rollback guards against version mismatches, and updates run from the UI with SHA256 verification.

💳 Plans and payments (optional)

Plans: price, duration, traffic and device limits; price 0 makes a free plan. There's a trial period, a free fallback plan for expired users, renewals and user migration between plans. Payment acceptance — pick a provider: YooKassa, PayPalych, RioPay, RollyPay, SeverPay, Platega, PayPear, AuraPay (cards, SBP, ₽), CryptoBot and Heleket (crypto). The client pays in the bot or on the subscription page, and the plan activates itself. A webhook confirms it (signature verified), polling covers the case where the webhook never arrives; processing is idempotent and the amount is checked against the order. With no provider configured, an admin confirms payments manually.

Warning

Payment providers have not yet been verified against live accounts. If you've connected one of them, please open an issue and say whether it works (which provider, what worked, what broke). That's what lets verified providers be marked as such and the rest get fixed.

👥 Access, roles and audit

Roles: owner (can do everything, exactly one, cannot be deleted), administrator (everything except the admin list), operator (users, statistics, activity log). Permissions are checked server-side on every request; a new admin gets a temporary password that must be changed on first login. The user log records what was done to them and by whom (admin, API key, bot, the user themselves, the system) and survives their deletion. The panel log (visible to the owner) covers logins and failed attempts with IPs, settings changes and backups; only successful actions are written, request bodies never are. Both logs are kept for 90 days.

🤖 Integrations

Admin bot in Telegram: user management, plans, subscription QR codes, scheduled backups, event notifications (signups, expirations, Xray failures, payments, certificates).

Client bot: self-signup, a personal menu with the subscription and statistics, plan purchase — plus personal notifications to the user: subscription ending, traffic running out, payment received.

Support bot — a third bot: a person writes to it in a DM and the conversation lands in your forum group, one topic per person; you reply straight from there and the answer goes back to them in the bot.

Broadcasts — nine audience slices (everyone, with and without an account, active, expired, expiring soon, long-inactive, never connected) with an image and a button. Delivery is driven by a recipient table, so an interrupted broadcast resumes instead of restarting, and nobody gets the message twice.

REST API with named keys (Authorization: Bearer), OpenAPI generated from the code and Swagger UI; webhooks send HMAC-SHA256 signed events with retries. More in docs/api.md.

🌍 Language (RU / EN)

Surface Language comes from
Panel the admin's own pick (per browser), otherwise the browser's languages
Subscription page Accept-Language
Client and support bots each person's Telegram language
Admin bot a panel-wide setting (Settings → Telegram)
CLI English

🎨 Branding and theme

Your own name and logo instead of "RosPanel" — in the panel and on the subscription page. An accent color repaints the whole interface, and dark mode adapts text, statuses and charts on its own.

🛡️ Abuse detection

The panel checks destination IP addresses from the Xray access log against a list of malicious networks and records matches only — ordinary traffic is never stored. It exists for exactly one purpose: when an abuse complaint arrives about the server's address, you can tell whose traffic it was.

The list is FireHOL level 1: botnet command-and-control servers, attackers and spam networks. A curated level with minimal false positives, without CDNs or shared hosting. Alongside it there's your own list (IP/CIDR), which is checked first. Matches show up in the statistics and in the user's card, attributed to the server the traffic left from; when the daily threshold is exceeded a Telegram notification goes out. Categories, the custom list, the threshold and updates live in Settings → Blocklists.

Checks run against addresses, not domains, and that isn't a simplification. Modern clients resolve DNS outside the tunnel and encrypt SNI (ECH), so all that reaches the server is a bare IP.

Matches are kept for 14 days — enough to handle a complaint.

🧰 Operations and security

Diagnostics in one click: the Xray process, config application, TLS expiry, disk space, geo database freshness, egress health — every check with a hint. A separate connection self-test connects to each protocol as a real client and confirms traffic actually goes out — catching credential, TLS or ALPN drift before a user does. Backup / restore and reset are available from the panel and the CLI.

Updates in one command: the panel verifies SHA256, runs the binary dry, takes a backup and only then replaces itself, keeping the previous version next to it. The Xray core is pinned, and the supervisor restarts it if it crashes.

Secrets in the database are encrypted (AES-GCM). Session tokens and API keys are stored as hashes only — even with table access you can't reuse someone's session. Payment confirmation and admin management require re-entering the password. Outbound requests are protected against SSRF, brute force on inbounds is banned via iptables, and the number of connections per IP is limited via nftables.


🛠️ CLI

rospanel                     run the panel (usually via systemd)
rospanel install             install the systemd service and start it (root)
rospanel uninstall [-y]      remove the service (data is kept)
rospanel start|stop|restart  service control
rospanel status              service status
rospanel update [-y]         update to the latest GitHub release
rospanel backup [file]       .tar.gz snapshot (DB + encryption key + certificates + Xray config)
rospanel restore [-y] <file> restore from a snapshot (applied on start)
rospanel host [-y] [domain|IP] show/change the address (reissues TLS)
rospanel path                show the panel URL and check secrets.key / the DB
rospanel reset [-y]          factory reset (wipes the DB)
rospanel version             version
rospanel help                full help

Destructive commands (reset, restore, host, uninstall) ask for confirmation; the -y flag skips it.

Node mode

The same binary can run as a panel-managed node. The install command is generated by the "Add node" dialog in the panel — you normally never type it by hand.

rospanel node install --join '<url>'   join the panel and install the service (root)
rospanel node run                       node agent (systemd entry point)
rospanel node set-panel <url>           point the node at a new panel address
rospanel node status                    local node status
rospanel node uninstall [-y]            remove the node service (data is kept)

--join comes from the add-node dialog; the token in it is single-use and lives for 24 hours. For the full walkthrough see 🌐 Adding a node.


🧱 Architecture

The single source of truth is SQLite; the Xray config is always generated from it and applied by the supervisor. The web panel is embedded in the binary.

Stack: Go 1.26 · Xray-core · SQLite (modernc, CGO-free) · React + Vite + Tailwind.


⚠️ Disclaimer

The software is provided "as is", without warranties of any kind.

The project is intended for educational and research use: studying network protocols, TLS and proxy technologies, CTF and network security lab work, authorized penetration testing, and managing your own infrastructure. The project is not intended for circumventing lawful restrictions or for any other unlawful activity; the masquerading mechanisms exist to study the technology and to test service resilience within sanctioned testing.

Responsibility for installing, configuring and operating the software, and for complying with the laws of your jurisdiction, lies with the server operator. The authors and contributors are not responsible for how third parties use the project.


🧑‍💻 Development

# frontend (after changes in web/)
npm --prefix web install
npm --prefix web run build      # → web/dist (embedded into the binary)

# binary
go build -o rospanel ./cmd/rospanel
./rospanel

Localisation. The panel's dictionaries are web/src/i18n/ru.ts and en.ts, typed against each other: a key present in one and missing in the other is a build error, not a silent fallback. What Go renders itself — the bots and the subscription page — lives in internal/i18n/; the backend hands the panel a key plus arguments and never rendered prose. Adding a third language means one more dictionary on each side.

Useful environment variables (all optional): ROSPANEL_DATA (data directory), ROSPANEL_ADMIN_ADDR (the panel's loopback address, 127.0.0.1:8080 by default), XRAY_BIN, ROSPANEL_HOST, ROSPANEL_ACME_EMAIL.

Flood protection (nftables limits on public TCP ports, see "Operations") is configured through the environment too — handy when a whole office or a CGNAT carrier sits behind one IP and clients hit the defaults:

Variable What it does
ROSPANEL_CONNLIMIT=off Disables the limits entirely (nftables rules are removed)
ROSPANEL_CONNLIMIT_MAX Maximum concurrent TCP connections from a single IP
ROSPANEL_CONNLIMIT_RATE Maximum new connections per second from a single IP

The current state is visible in Dashboard → Management → Diagnostics: if nftables isn't installed or the panel isn't running as root, the rules silently won't apply — diagnostics will say so.

PRs and issues are welcome. Commits follow Conventional Commits: release-please uses them to cut releases and publish the binary and the Docker image to GHCR.


💝 Support the project

RosPanel is developed in spare time and distributed for free. If the panel turned out to be useful, the easiest way to support it is DonationAlerts or Boosty (RU cards, one-off or recurring). Thank you! 🙏

Crypto

USDT. Pick a network and copy the address carefully — the sender's and the receiver's network must match. Send USDT only, and only on the network listed: a transfer on the wrong network cannot be recovered. The cheapest fees are on TRON (TRC20) and TON.

Network Token Address
TRC20 (Tron) USDT TJwyrPVEZVZ1YrcmDiZTyFjLo3Q2DmEGzs
ERC20 (Ethereum) USDT 0xf9d663146ce902da91911b214c71cc73a5269d1d
Solana USDT 2qAZRTbaUMTfYuZbD1dCYHjkYgxkw4dUYE9XY3JhC2Cs
TON USDT UQDoat731MLYuIw8ayL3Vhhw7zTBbLvRaQFmDvab--CNNI7e

Bybit. If you're on Bybit too — transfer to UID 136462734: instant and free.

Can't support financially? You can still help — rent servers through the referral links in Where to get a server. It costs you nothing and helps the project.


📄 License

GNU Affero General Public License v3.0 (AGPL-3.0).

The project is free to use, modify and self-host. If you provide network access to a modified version of the panel (including as a service), you must release the source of your changes under the same terms.