Integrations

SSH banner

Settings → SSH Banner lets you fully customize the text every SSH account sees before the password prompt — a classic pre-auth MOTD, generated from a template and shipped to every node.

Vortexa shows a custom banner before authentication on every SSH VPN account. This is the text a client's SSH tool prints immediately on connect, before any credentials are exchanged — useful for branding, support contact info, and letting a user self-check their expiry/usage without logging in anywhere.

Format

The banner (sshBanner setting) is a raw, real-newline-separated multi-line text template — not HTML/Markdown, because pre-auth SSH banners collapse HTML and only render plain text with literal line breaks.

Default template

SERVER_BANNER_BLOCK (default)
Server:  {SERVER_LINE}
Node:    {SERVER_NODE}
Account: {USERNAME}
Expires: {EXPIRES} ({DAYS_LEFT}d left)
Usage:   {USED_GB} / {QUOTA_GB} GB
Status:  {STATUS}

Editing controls

  • Edit the template directly in the Banner template textarea.
  • Reset to default restores SERVER_BANNER_BLOCK exactly as shown above (still gets the attribution line appended).
  • Save banner both persists the setting and mirrors the raw template to every node — see Delivery below.

Per-user variables (resolved by the panel)

These are filled in by the panel before the account bundle ships to its node — they differ per SSH account.

VariableMeaning
{USERNAME}The SSH account's username.
{EXPIRES}The account's expiry date/time.
{DAYS_LEFT}Days remaining until expiry (integer).
{USED_GB}Data used so far, in GB.
{QUOTA_GB}The account's data quota, in GB (0 typically means unlimited upstream).
{STATUS}Account status label, e.g. active/expired/suspended.

Per-node variables (resolved on the node itself)

These are filled in locally on each node by that node's own banner.py process, not by the panel — so the same template renders differently per node.

VariableMeaning
{SERVER_LINE}Composite line, e.g. 🇮🇹 Italy - Milan - 01 (flag + country + city + node sequence number).
{SERVER_COUNTRY}The node's configured country.
{SERVER_FLAG}Emoji flag for that country.
{SERVER_NODE}The node's display name.
{SERVER_CITY}The node's configured city.
{SERVER_SEQ}The node's sequence number (e.g. 01, 02 — used to disambiguate multiple nodes in the same city).

Cannot be removed from the UI

A fixed brand attribution string (BANNER_ATTRIBUTION, from src/lib/brand) is always appended to whatever you type, both in the live preview and on save (withAttribution(...)). It is reapplied server-side on every node refresh too, per the source code comment — so it will reappear even if a node's local file is edited by hand.

Save flow

  1. 1
    You edit the template and click Save banner.
  2. 2
    The panel calls api.settings.save(payload) with the attributed banner (withAttribution(f.sshBanner)) so it is stored as the canonical setting.
  3. 3
    The panel additionally POSTs the raw template to /api/panel/banner, mirroring it into the panel-side key-value store (api/panel/kv.$key.ts / api/panel/state.ts proxies) so the node-heartbeat pipeline can push it to every node without a second admin action.
  4. 4
    This mirror call is best-effort: if it fails, it is silently retried on the next save rather than blocking the settings save itself.
  5. 5
    Nodes pick up the latest banner via their periodic heartbeat / RPC channel (see /docs/node-agent), where each node's own banner.py resolves the per-node variables and writes the rendered banner to the appropriate SSH pre-auth banner file (e.g. /etc/issue.net plus the corresponding sshd_config Banner directive) and reloads/restarts the SSH service so it takes effect immediately.

Nodes resolve, not the panel

The panel only resolves per-user variables and ships the raw template plus per-user data; the per-node variables (flag, city, sequence) are rendered locally by each node so a single template can serve an entire fleet correctly labeled per location.

Live preview

The Settings page renders a live preview pane showing withAttribution(f.sshBanner) verbatim (with placeholders still visible, unresolved) so you can see exactly what will be sent, including the locked attribution line, before saving.

Troubleshooting

Banner not updating on a node
Confirm the node is actually heartbeating (see /docs/node-agent); the mirror POST to /api/panel/banner is best-effort and retries on the next save if it failed.
Placeholders showing literally (e.g. {USERNAME})
That variable name is misspelled or unsupported — check the exact variable list above; unknown tokens are not substituted and print as-is.
Attribution line missing on a node
The node is running a stale banner file from before this feature was enabled, or its refresh cycle has not run yet — trigger a manual re-pin/heartbeat.
HTML/formatting looks broken
SSH pre-auth banners are plain text only; remove any HTML tags or Markdown from the template — use real newlines for line breaks.