Fleet

Nodes

Vortexa is a multi-node VPN control plane. One panel pairs with any number of edge nodes, each running its own VPN service stack plus a small Python "node agent" that the panel talks to over a signed HTTPS RPC channel. This page covers the fleet topology, the node fields you will see in the UI, and the complete step-by-step for adding, repairing and uninstalling a node.

The panel ("master") is the single web app you log into — it also doubles as a single-box install with its own built-in "local node". Every additional VPS you pair is a worker edge node. A node runs:

  • sshd — the SSH tunnel plane.
  • xray (systemd) — VMess/VLESS/Trojan over WS/xHTTP/HTTPUpgrade.
  • zivpn (systemd) — UDP VPN.
  • nginx — TLS termination and reverse-proxy fan-out across every Cloudflare-compatible port.
  • stunnel4 (autoscript-node-stunnel) — SSH-over-TLS on 445/447/777.
  • autoscript-node — the node agent (FastAPI + uvicorn), the only process the panel talks to. It owns enrolment, liveness, remote control, local enforcement, WARP egress, benchmarking and log hygiene.
  • autoscript-node-ssh-ws — a companion process bridging SSH over WebSocket, run in its own systemd unit at higher CPU priority than the agent so telemetry never throttles real tunnel throughput.

The panel never SSHes into a node for day-to-day operation. Everything goes through a signed HTTPS RPC channel (/rpc/*) proxied by the panel's own server route (src/routes/api/public/node-rpc.ts), because a browser cannot pin a self-signed certificate by fingerprint on its own. See /docs/node-security for the pinning model.

request flow
Browser (panel UI)
   -> Panel server route  /api/public/node-rpc      (Node.js https client, cert-pin + HMAC)
      -> Node agent  /rpc/*  (FastAPI/uvicorn, self-signed TLS on AGENT_PORT)
         -> xray / zivpn / sshd / nginx / stunnel4

Node agent -> POST /api/public/node-handshake   (enrol / re-pin)
Node agent -> POST /api/public/node-heartbeat    (every ~3-5s / adaptive)
Node agent -> POST /api/public/node-ssh-usage    (SSH byte ledger, own channel)

The main fleet management screen shows a table of every paired node: status (online / offline / installing / degraded), CPU/RAM/disk, live throughput, connected sessions, agent version and region.

Actions

  • Add Node — generates the install one-liner (see Adding a node below).
  • Delete Node — a DeleteNodeDialog produces the matching uninstall one-liner, and removal also runs a background fleet-wide purge (purgeNodeEverywhere(row.id)) so a deleted-then-recreated node with the same id never resurrects stale usage.
  • Per-row RPC actions (badge shows amber "Restarting…"/"Stopping…"/"Updating…" while in flight): restart node, stop agent, update agent, rebuild/re-tune, run benchmark, force-repin.
  • Bulk actions: bulk delete, bulk stop, bulk update.

Self-healing behaviours

  • Auto-update: if the panel's own single-box ("local") node reports an older agent version than the panel, it self-updates in the background (autoUpdateLocalNode()); remote nodes always require a manual "Update" click.
  • Status repair: a node marked disabled in the database but reporting installed:true and not soft-deleted gets its status corrected automatically from heartbeat data.

A deep-dive on one node: live metrics, NIC cards, service status cards, session list, protocol breakdown, Xray version/upgrade UI, and a logs viewer. Buttons drive individual RPCs:

ButtonRPC
Restart nodenode.restart
Restart Xray / Restart ZiVPNservice.restart {service:"xray"|"zivpn"}
Force re-pinPOST /api/nodes/:id/force-repin

note

Speedtest/benchmark is deliberately not rendered inline on this page — it is surfaced instead via the fleet-wide benchmark component, so a slow speedtest on one node doesn't block the detail view.
  • Per-region rollup and Region groups — aggregate node counts/health by region.
  • Infrastructure cost — cost tracking widget.
  • Agent versions — which nodes are on which agent build, driving the "needs update" indicator.
  • Cert expiry — surfaces nodes whose TLS certificate is approaching expiry (reads cert.log / cert metadata pushed via heartbeat/logs).
PlaneServiceNotes
SSH tunnelssshdNative OpenSSH, tuned via sshd_config.d/99-autoscript-node.conf.
SSH over WebSocketautoscript-node-ssh-wsOwn systemd unit, Nice=-5 / CPUWeight=800 — outranks the agent since it is customer data plane.
SSH over TLSstunnel4 (autoscript-node-stunnel)Ports 445/447/777.
VMess/VLESS/TrojanxrayWS/xHTTP/HTTPUpgrade over nginx-fronted Cloudflare-compatible ports.
UDP VPNzivpnStatic binary, port 5667/UDP.
TLS termination / fan-outnginxReverse-proxies every Cloudflare-compatible port to loopback Xray/SSH-WS listeners.
Port(s)ProtocolPurpose
AGENT_PORTTCP, HTTPS (self-signed, pinned)Panel <-> node RPC + heartbeat channel. Default 4001, auto-reassigned if busy. Firewalled to panel IPs only.
22TCPNative SSH (also reachable via the nginx WS bridge and stunnel).
443, 2053, 2083, 2087, 2096, 8443TCP, TLSCloudflare-compatible HTTPS fronting for Xray WS/xHTTP/HTTPUpgrade.
80, 8080, 8880, 2052, 2082, 2086, 2095TCP, plain HTTPSame Xray transports over plaintext, plus ACME http-01 challenge.
445, 447, 777TCP, TLS (stunnel)SSH-over-TLS for clients that don't speak WebSocket.
5667UDPZIVPN.
10001-10003 / 10011-10013 / 10021-10023 / 10080 (loopback)TCPInternal Xray/SSH-WS inbound listeners nginx reverse-proxies to.
  1. 1
    Prereqs: a fresh Ubuntu/Debian VPS with root SSH access and outbound internet, and DNS A/AAAA record(s) for the VPN hostname pointed at the VPS (needed for Let's Encrypt http-01, unless using dns-01 for a wildcard).
  2. 2
    In the panel, go to Nodes → Add Node. This generates a unique NODE_ID, a BEARER token and a SHARED_SECRET (fleet-wide NODE_SHARED_SECRET or a per-node override), and the install one-liner.
  3. 3
    Paste the one-liner into the VPS's own terminal — not your laptop. If EXPECTED_IP is set, the installer verifies (via ipify/ifconfig.me/icanhazip) that it's actually running on that box and refuses + cleans up if not.
  4. 4
    If PANEL_DOMAIN/VPN_DOMAIN are omitted and a TTY is attached, the installer runs an interactive domain wizard asking for the node's API/RPC hostname and its customer-facing VPN hostname (wildcard requires dns-01 + DNS_API).
  5. 5
    The installer runs 17 phases end to end, installing packages, fetching agent code, generating the self-signed TLS cert, installing Xray/ZIVPN/sshd, tuning the kernel, issuing the public TLS cert via acme.sh, publishing Xray on every Cloudflare port, firewalling the agent port, and starting all the systemd units.
  6. 6
    The panel flips the node to Online on the next heartbeat once installed:true is seen and the handshake fingerprint is pinned.
  7. 7
    Push a first user set (or wait for the panel's normal sync cycle) — the node terminates real traffic as soon as users.apply lands.
install one-liner
curl -fsSL https://<panel-host>/api/public/node-install.sh \
  | NODE_ID=<id> MASTER=https://<panel-host> BEARER=<token> \
    SHARED_SECRET=<secret> EXPECTED_IP=<vps-ip> \
    VPN_DOMAIN=<vpn.example.com> PANEL_DOMAIN=<node-api.example.com> \
    sudo -E bash

Key install phases

  • 1. Verify server identity (EXPECTED_IP check).
  • 2. Select/verify AGENT_PORT (default 4001; falls back to a random 20000-60000 port if busy, reported back to the panel).
  • 3. Reset any previous agent install (idempotent re-run support).
  • 4. apt-get install curl jq socat cron nginx stunnel4 python3 python3-venv python3-pip iptables iproute2 openssl wireguard-tools iputils-ping ca-certificates.
  • 5. Fetch agent code from $MASTER/api/public/node-agent.tar.gz into /opt/autoscript-node; generate the self-signed TLS cert for the agent RPC listener.
  • 6. Create the Python venv, pip install fastapi/uvicorn[standard]/httpx/psutil/pydantic/websockets; install sshd, Xray-core (official installer), ZIVPN (arch-detected static binary).
  • 6b-2. Kernel/network tuning: BBR + fq, huge socket buffers, somaxconn, nf_conntrack_max, fs.file-max=1048576, initcwnd 40; xray.service.d/99-autoscript-tuning.conf grants CAP_NET_ADMIN/CAP_NET_BIND_SERVICE/CAP_NET_RAW and runs Xray as root for SO_MARK shaping.
  • 6c. Issue the public TLS certificate via acme.sh (http-01 via nginx webroot, or dns-01 for wildcards); install stunnel for SSH-over-TLS on 445/447/777.
  • 6c-2. Publish Xray on every Cloudflare-supported port (TLS: 443 2053 2083 2087 2096 8443; plain: 80 8080 8880 2052 2082 2086 2095), each on a real-looking path (/assets, /stream, /media...) rewritten internally to VMess/VLESS/Trojan WS/xHTTP/HTTPUpgrade backends. SSH-over-WebSocket answers only on /.
  • 8. Write /etc/default/autoscript-node (mode 0600) with NODE_ID/MASTER/AGENT_PORT/BEARER/SHARED_SECRET; install the self-updater and the logclean timer/service pair.
  • 8b. Firewall the agent port to panel IPs only.
  • 8c. POST the handshake fingerprint to the panel immediately, without waiting for the agent's own first boot.
  • 9. Migrate away from the legacy single-box per-UID shaper (autoscript-ip-limit.timer/.service) if present: stop, disable, mask, clear its tc/ipset state.
  • 10. Enable + start autoscript-node, autoscript-node-ssh-ws, autoscript-node-stunnel; write /var/lib/autoscript-node/install-complete.json and bounce the agent so the next heartbeat reports installed:true.

The installer is fully idempotent. Re-running the exact same one-liner on an already-installed node repairs configuration, re-issues certificates if needed, and does not duplicate systemd units or ports. This is the standard fix for a node stuck mid-install or with drifted config.

From the panel's Delete-node dialog, the generated one-liner is:

bash
curl -fsSL <panel>/api/public/node-uninstall | sudo [PURGE_VPN=1 ]bash

PURGE_VPN=1 additionally removes Xray/ZIVPN/SSH account data instead of just the agent. Seenode/uninstall.sh / backend/uninstall.sh.