Releases: BrowserlessAPI/VortexPanel
Release list
VortexPanel v3.4.0 — Go/Node.js Project Manager, FFmpeg & Memcached, Website Import, Nine-Distro Reliability
VortexPanel v3.4.0 — Go/Node.js Project Manager, FFmpeg & Memcached, Website Import, Nine-Distro Reliability
Release date: July 2026
The biggest release since v3.2.0's security overhaul. Two entirely new deployment managers (Go and Node.js binary/app hosting — something none of cPanel, Plesk, aaPanel, or HestiaCP offer out of the box), a full Website Import wizard for migrating off other panels, two new App Store modules, and a broad reliability pass across all nine supported distros and both x86_64/arm64 architectures.
🚀 Go Projects & Node.js Projects — New Deployment Managers
VortexPanel can now host compiled Go binaries and Node.js applications directly, with the same one-click simplicity as a WordPress install.
Go Projects
- Binary-only deployment — point at a compiled Go binary, VortexPanel handles the rest
- Go SDK manager — install/switch between multiple Go versions side by side, GOPROXY configuration (official/goproxy.io/goproxy.cn/direct)
- systemd service — auto-restart on crash, enable-on-boot, journal logging
- Reverse proxy on all 4 webservers — nginx, Apache, OpenLiteSpeed, and Caddy, auto-detected
- WebSocket support — proper
Upgrade/Connectionheader handling on nginx and Apache (needed forgorilla/websocket, gRPC-Web, etc.) — the shared nginx$connection_upgrademap is written once and reused across every proxied project - Let's Encrypt SSL per domain — nginx/Apache use certbot's native plugin, OpenLiteSpeed uses
certonly+ manual vhost wiring, Caddy is detected as already-automatic (no action needed) - Resource limits —
MemoryMax/CPUQuotain the systemd unit, so a runaway process gets capped instead of taking down the whole server - On-demand health checks — real TCP connect to the app's port, catching the case
Restart=alwayscan't: a hung process still holding the port open - Binary version history + rollback — every start/restart snapshots the binary (skipped if unchanged, via SHA-256 hash), keeps the last 5 versions, one-click revert
- Firewall integration (UFW + firewalld), all 9 supported distros
Node.js Projects
- PM2 + systemd — choose either process manager per project
- nvm-based version switching — install and switch Node versions without affecting other projects
- Same reverse-proxy-on-all-4-webservers support as Go Projects
- Current LTS reality reflected: v24 Active LTS default, v22 Maintenance, v18/v20 correctly blocked as EOL
📥 Website Import Wizard — Migrate from cPanel, aaPanel, HestiaCP
A new "Import Website" flow on the Websites page, upload-based (no SSH pull required):
- Upload the backup archive exported from your old panel
- Detect — VortexPanel extracts it and makes a best-effort guess at the domain, PHP version, document root, and database dump
- Confirm — every detected field is editable before anything happens; nothing is auto-executed blind
- Import — creates the site + nginx vhost (the exact same code path as "New Website", so imported sites are indistinguishable from natively-created ones), copies files, creates the database and imports the dump, generates a fresh random DB password (originals are never in a backup dump, so there's nothing to recover)
Supports cPanel (cpmove/full-backup format, userdata YAML domain detection), aaPanel (domain-folder detection + .sql/.sql.gz filename matching), and HestiaCP (v-backup-user's nested web.tar/mysql.tar format). Files + database only in this release — email, cron, and SSL migration are not yet included.
📦 App Store — 2 New Modules
- FFmpeg Manager — install multiple FFmpeg versions side by side (7.1, 8.1, and nightly master), each with its own command alias (
ffmpeg7,ffmpeg8). Sourced from BtbN/FFmpeg-Builds — the provider ffmpeg.org's own download page links to for Linux static builds — with full x86_64 and arm64 support - Memcached — full management UI: Service (start/stop/restart/reload), Config File editor, Switch Version, Load Status (live stats via Memcached's own
statsprotocol), and Optimization (bind IP/port/cache size/max connections)
App Store module count: 27 (up from 25).
🖥 Dashboard & Monitoring
- Realtime charts — CPU/RAM history and Network I/O, client-side rolling window, no backend polling changes needed
- Global SSL expiry banner — aggregates certificate expiry across every site, warns at 14 days with severity-graded colour coding (yellow → orange → red-for-expired)
- Settings → Audit Log viewer — last 200 login attempts (time/status/IP/user/note) — the backend already existed, it just had no UI until now
- Per-site disk usage — lazy-loaded (only fires when you open a site's Directory tab, so it doesn't slow down the Websites list on servers with many/large sites)
🛠 Reliability — RHEL-Family & arm64 Fixes
A full audit turned up (and fixed) install-time failures that were silently breaking on real-world server configurations:
- RHEL/CentOS/Fedora/AlmaLinux/Rocky — nginx, MySQL, Caddy, MongoDB, and PostgreSQL were previously completely uninstallable on any RHEL-family distro. The install scripts only had a Debian-specific GPG-keyring +
sources.listcode path; on RHEL this produced garbage commands (dpkg -ion a distro with nodpkg, Debian package names fed todnf, etc.). All 5 now have proper distro-aware install paths — MySQL uses RHEL's built-in AppStream module stream (no external repo needed at all), PostgreSQL correctly disables the conflicting built-in module before installing PGDG's versioned packages. - arm64 — MongoDB and PostgreSQL RHEL install paths had hardcoded
x86_64in the repo URLs, silently breaking on ARM servers. MongoDB now usesdnf's auto-resolving$basearch; PostgreSQL detects the real architecture viauname -m. - A pre-existing Caddy bug (not new — this predates the audit) was piping a fetched GPG key into
rm -finstead ofgpg --dearmor, producing an empty/invalid keyring on Debian too, not just RHEL. Fixed. - All module install/uninstall cycles audited for safety on repeat runs — GPG keys and repo files are now properly cleaned up on uninstall, preventing reinstall failures.
🎨 UI System — Modal & Layout Fixes
A cluster of related frontend bugs, all traced to the same root causes and fixed together:
- Modal centering — the underlying issue was
position:fixedbreaking when nested inside a scrolling ancestor (overflow-y:autocontainers). All modals are now rendered as direct children of<body>via a shared global portal pattern, using explicitwidth:100vw;height:100vhso they're immune to any ancestor's overflow settings. - Horizontal scrollbar shifting the entire panel — a classic flexbox
min-width:autotrap:.main-areaand.page-contentwereflex:1with nomin-width:0, so a single unconstrained<select>dropdown with long option text (App Store version pickers) could force the whole layout wider instead of scrolling internally. Fixed at the source, plus a body-level safety net. - Cross-component scoping bugs — several modals had their interactive functions defined inside the wrong Alpine.js component (a page's own scope instead of the global scope the portal actually renders in), causing buttons to silently do nothing. Standardized on the same pattern used by
toast()/get()/post(): plain global functions, reachable from any component. - Eight pages (
dashboard,docker,settings,files,mail,bandwidth,security,caddy) had dead method calls in their tab-refresh event listeners from an earlier bulk edit — switching to those tabs was silently failing. All fixed.
🔩 Smaller Fixes
- Self-hosted SVG brand logos for all App Store modules — zero CDN dependency for icons
- nginx version bump — Stable 1.30.3, Mainline 1.31.2 (security patch)
- Update-check false positive (was comparing git hash against semver)
- Databases page now auto-detects MongoDB/PostgreSQL correctly with engine-aware columns
- Install/uninstall hang fixes — proper timeouts (10min install, 5min uninstall, 8min switch), service-stop-before-remove ordering
install.shnow clones to a permanent/root/Vortexpanelinstead of/tmp(which gets wiped on reboot, silently orphaning git tracking) — and auto-generates a correctly-patheddeploy.shduring install
VortexPanel v3.3.0 — Visual Design, Multi-Webserver HTTP/3, Live Terminal, Mail & Bandwidth
VortexPanel v3.3.0 — Visual Design, Multi-Webserver HTTP/3, Live Terminal, Mail & Bandwidth
Release date: June 2026
A broad quality-of-life release covering visual polish, long-standing bugs, and several features that were half-built in earlier versions and are now complete and properly tested.
🎨 Visual Design Overhaul
The dashboard and sidebar were reworked to feel intentional rather than generic.
- Stat cards — each of the four dashboard metric cards now has its own colour identity: CPU (blue), RAM (purple), Disk (amber), Uptime (green). Each card has a left accent bar, a tinted progress bar, a coloured metric value, and a ghost emoji icon. The old design used a single cyan colour across all four.
- Sidebar icon pills — every nav item now has a small coloured pill behind its icon (unique colour per section). Active item switches to blue with a filled pill, matching the style used by aaPanel and modern control panels.
- Active nav colour — changed from cyan (
#0e7490) to blue (#2563eb) for stronger contrast. - Semantic stat colour variables —
--stat-cpu,--stat-ram,--stat-disk,--stat-uptimeadded totheme.cssfor consistent use across the panel. - App Store settings sidebar — the floating settings window tabs now use the same coloured icon pill pattern as the main sidebar, replacing the previous plain text-only tabs.
⚡ HTTP/3 — Full Multi-Webserver Support
The previous HTTP/3 toggle only worked on nginx and showed a static "go install it yourself" warning for everything else. It now handles all four supported webservers correctly:
- nginx (nginx.org official package, 1.25+): Enable/Disable toggle. Writes
listen 443 quic reuseportandAlt-Svcheader. Automatically opens UDP 443 in UFW or firewalld. - nginx (distro package, 1.25+): Detects that the package lacks
--with-http_v3_module. Shows a one-click "Upgrade to nginx.org Mainline" button that installs the official package, preserving all existing vhost configs. - nginx (< 1.25): Clear error message — too old, no upgrade path within the distro.
- Caddy: Shows "Always On" — Caddy enables HTTP/3 automatically when SSL is active. Panel checks and opens UDP 443 if needed.
- OpenLiteSpeed: Same as Caddy — HTTP/3 is default-on. Panel ensures UDP 443 is open.
- Apache: Clear "Not Supported" message explaining why and suggesting switching to nginx or Caddy.
- UDP 443 firewall — opening UDP 443 is now done automatically on enable for all supported webservers across UFW (Debian/Ubuntu) and firewalld (RHEL/Fedora/Alma/Rocky).
🔧 Bug Fixes — SyntaxWarnings in modules.py
Seven invalid escape sequences in modules.py that would break on strict Python builds (Python 3.12+ raises SyntaxWarning, future versions will error):
- Lines 368, 405, 421:
\.,\s,\Kin regular strings → raw strings (r'''...''') - Lines 1065, 1217, 1405:
\s,\K,\Sin f-strings → raw f-strings (rf"...")
🔄 Session Persistence — Survives Restarts
Root cause: Flask's default client-side signed cookie sessions are invalidated when gunicorn restarts, because each worker independently generates a secret key if the file doesn't exist yet — causing a race condition where workers end up with different keys in memory.
Fix: Enabled flask-session (already in requirements.txt but never activated) with SESSION_TYPE = 'filesystem'. Session data is now stored as files in /opt/vortexpanel/sessions/. The session cookie holds only a signed session ID. Sessions survive gunicorn restarts, nginx reloads, and worker recycling. Sessions can be individually invalidated server-side on logout.
📊 Live Installation Terminal — All App Store Actions
The App Store previously showed no progress when switching versions — the button would appear to do nothing for several minutes, then either succeed or fail with no output.
Root cause chain (three separate bugs):
switch_versionused blockingsh()calls with no output streaming- Jobs were stored in an in-memory dict (
_jobs = {}), but gunicorn runs with--workers 4— each worker has its own memory space, so the SSE stream (potentially on worker 2) couldn't see jobs created on worker 1 - The SSE stream opened before the job file was created, immediately got "Job not found", and closed silently — leaving the terminal blank forever
Fix: Full rewrite of the job system:
- All three actions (install, uninstall, switch version) now use the same SSE streaming pipeline
- Jobs stored as JSONL files in
/tmp/vortex_jobs/— append-only, shared across all workers via the filesystem - SSE stream waits up to 5 seconds for the job file to appear before giving up
- subprocess output streams line-by-line in real time via
Popenwithbufsize=1 - 8-minute process timeout kills hung operations (e.g.
apt-get updatewaiting on a slow mirror) apt-getnetwork timeout set to 30 seconds per mirror on all switch scripts
Terminal colours: All output lines now have explicit hex colours so they're readable on the black terminal background: regular apt output (#d1fae5), info lines (#67e8f9 cyan), success (#4ade80 green bold), warnings (#fbbf24 amber bold).
🗄 MariaDB — Settings & Versions Fixed
- Optimization tab was blank —
save_module_settings()never definedmod, causing aNameErrorin therun_switchclosure. Addedmod = _get_mod(mod_id)at the top of the handler. - MariaDB settings response missing fields — the GET handler returned
status,version,conf_path,conf_content,logs,portbut was missingoptimization,current_status,slow_log,log_path,datadir. All fields now match the MySQL handler. save_optimizationhad no MariaDB handler — nginx, Apache, and OLS had handlers; MySQL/MariaDB fell through silently. Now reads the correct.cnfpath per distro, updates existing keys with regex, appends new keys under[mysqld]if missing, then restarts the service.- Versions updated: Added MariaDB 12.3.2 (Latest), 11.8.8, keeping 11.4 LTS, 10.11 LTS, 10.6 LTS. Previous list was missing the entire 12.x series.
🔒 Database Version Switching — Removed (Uninstall-First Approach)
Following aaPanel's approach: switching a running database engine version in-place risks data corruption, especially on downgrades. The "Switch Version" tab has been removed from MariaDB, MySQL, PostgreSQL, and MongoDB settings modals.
The safe workflow is: Uninstall → Install new version from App Store. Version switching remains available for non-database modules (Redis, nginx, Apache, OLS, Caddy, Node.js, BIND9, pure-ftpd).
🌐 Two-Webserver Conflict Detection
If two webservers (e.g. nginx + apache2) are both active simultaneously — causing port 80/443 conflicts and unpredictable routing — the dashboard now shows a red warning banner with a "Fix Now →" button linking directly to Services. Detection runs in parallel with the existing CPU/disk/services poll on every dashboard stats request.
📧 Mail — Forwarding & Logs Completed
- Forwarding tab previously broke silently because
selDomainwas only set when the user clicked a domain in the Mailboxes tab. The Forwarding tab now has its own domain selector dropdown, independent of the Mailboxes tab. - Mail logs now support: line count selector (100/300/500), log type filter (All/Postfix/Dovecot), real-time client-side search/filter, RHEL log path (
/var/log/maillog) withjournalctlfallback, configurable via?lines=query param.
🟢 Node.js — Versions Corrected, Switch Bug Fixed
- EOL versions removed: v18 (EOL Oct 2023) and v20 (EOL Apr 2026) removed from all version lists.
- Default install updated: Was
setup_20.x(EOL). Nowsetup_24.x(Active LTS). - Active LTS labelled correctly: v24 "Krypton" is Active LTS; v22 "Jod" is Maintenance LTS; v26 is Current (non-LTS until Oct 2026).
- Switch version bug fixed: Switching from v26 to v24 previously installed v26 again because the old nodesource repo was still active and apt resolved to v26. Fix: old nodesource repo, list, and GPG keys are removed before the new version's setup script runs.
nodejs_install_script()default inos_utils.pyupdated from'22'→'24'.
🚀 nginx Install — UDP 443 + Stream Block
nginx_install_script() in os_utils.py now runs two post-install steps automatically:
stream {}block added to/etc/nginx/nginx.confif missing (required for TCP load balancing to work at all — was causing silent failure on fresh installs).- UDP 443 opened in UFW (Debian/Ubuntu) and firewalld (RHEL/Fedora/Alma/Rocky) — required for HTTP/3 QUIC. Idempotent; safe to run if firewall is not active.
🔌 nginx Stream Module — Auto-Install All 9 Distros
The TCP Load Balancer previously showed a static warning telling users to run apt install libnginx-mod-stream manually. It now installs automatically:
- Debian/Ubuntu:
apt install libnginx-mod-stream, with retry afterapt updateif first attempt fails. - RHEL/Fedora/AlmaLinux/Rocky/CentOS: Checks if
.soalready exists (nginx.org packages bundle it). Falls back todnf install nginx-mod-streamif not found. - After install: ensures
load_moduledirective exists innginx.conf, runsnginx -t, reloads nginx. - Detects Debian's auto-created
modules-enabled/50-mod-stream.confsymlink and skips manual injection when present. - Compatible with nginx 1.24 through 1.31.x.
Upgrading
cd /root/Vortexpanel
git pull
cp -r panel/ web/ app.py /opt/vortexpanel/
mkdir -p /opt/vortexpanel/sessions
systemctl restart vortexpanelOr fresh install:
wget -O install.sh https://raw.githubusercontent.com/BrowserlessAPI/VortexPanel/main/install.sh ...VortexPanel v3.1.0 — WP Toolkit, firewalld, Performance
VortexPanel v3.1.0 — WP Toolkit, firewalld, Performance
Release date: June 2026
This is a large release. The headline addition is the WP Toolkit — a full WordPress management module with a better UI than aaPanel's, built entirely free with no Pro tier required. There are also critical bug fixes that affected every fresh install via install.sh.
🔷 New: WP Toolkit
Full WordPress lifecycle management — install, manage, secure, stage, back up — without needing a separate plugin or paid add-on.
Install
- One-command WordPress install: downloads core, creates DB, writes
wp-config.php, runs the installer, creates the vhost, sets file ownership, configures SSL, and enables system cron — all in one click - PHP 7.4 → 8.5 support with automatic FPM socket detection across all distros
- Nginx, Apache, OpenLiteSpeed, Caddy — correct vhost template per webserver including permalink rewrite rules
- MariaDB and MySQL — auto-detected
- Auto-generates a non-default admin username and randomised table prefix (security defaults)
- Detects which webservers are actually installed — warns and blocks if you select one that isn't
Manage
- Site cards grid: WP version, PHP badge, SSL status, plugin/theme counts, update count
- Slide-in drawer with 7 tabs: Overview, Plugins, Themes, Security, Staging, Backups, Settings
- One-click admin login (wp-cli
user session create→ auto-login URL, no password needed) - Plugin management: list, activate, deactivate, update, delete, bulk update all
- Theme management: list, activate, update, delete
- Core update with database migration
Security scanner
- 9-point scan: admin username strength, file permissions, WP version, SSL, debug mode, XML-RPC exposure, wp-config.php HTTP access, plugin versions, login URL exposure
- A/B/C grade with score (0–100)
- One-click fix buttons for every failed check
Staging
- Full site clone (rsync files + DB export/import + URL search-replace)
- Creates a vhost for the staging domain automatically
- Push staging → live: auto-creates a backup of live first, then syncs files + DB + URL rewrite
- Pull live → staging
More
- Backup/restore:
tar.gzof files + DB dump - Settings: site title, email, password reset, language, PHP version switch, debug mode, maintenance mode, search engine visibility, system cron
- wp-cli auto-installed if not present
🔥 New: firewalld support (Fedora / RHEL / AlmaLinux / Rocky / Oracle / CentOS / CloudLinux)
The Firewall module previously only supported UFW (Debian/Ubuntu). Every RHEL-family distro uses firewalld, so the Firewall page showed inactive/empty and all actions silently no-op'd on those systems.
Now fully supported on both:
- UFW path (Debian/Ubuntu): unchanged
- firewalld path (Fedora/RHEL-family):
--list-ports,--list-services,--list-rich-rulesmerged into a unified numbered list;--add-portfor simple allow rules,--add-rich-rulefor deny/reject/source-restricted rules; presets (webserver, mailserver, database) via rich rules; toggle enable/disable viasystemctl
Also fixed on all distros: the frontend sent protocol but the backend read proto — UDP rules silently became TCP. Now fixed in both UFW and firewalld paths.
🐛 Critical bug fixes
app.py — gunicorn entrypoint crash-loop on every fresh install
app = create_app() was only defined inside if __name__ == '__main__'. The systemd service created by install.sh runs gunicorn ... app:app, which imports the module — that block never executed. Every fresh install via install.sh produced a crash-looping service that would never start. Fixed by defining app = create_app() at module level.
Pages not loading after login (browser refresh required)
All page components (x-data="websitesPage()" etc.) were initialised by Alpine.js at page load time — before login — so every init() hit 401 and stored empty data, which was never re-fetched after login. Added document.addEventListener('vortex-logged-in', ...) to all 22 page component init() functions. _onLoggedIn() already dispatched this event; now every page listens for it.
Dashboard Disk card showing blank / wrong bar width
ramPct() and diskPct() were called in the Dashboard template as methods but never defined in dashboardPage(). Alpine threw a silent ReferenceError, leaving the Disk card value empty and the bar fill defaulting to nearly-full regardless of actual usage.
dashboard/stats — 10× performance improvement
Replaced top -bn1 (800–1500ms per request, waits for a CPU measurement cycle) with /proc/stat 100ms sample. RAM, load, uptime, and network now read from /proc (instant). Services detection changed from 8 sequential systemctl is-active calls to one batch call. Added 1.5s TTL cache. Total dashboard stats response: ~103ms (was ~1000ms).
security.py — two 500 Internal Server Errors
GET /api/security/modsecurity:sh()returns a 3-tuple in this file butrules_countwas assigned the whole tuple and passed toint()— TypeErrorPUT /api/security/loadbalancer: same issue — tuple assigned to variable then.lower()called on it — AttributeError
Load Balancer config corruption bug
The server-parsing regex also matched the virtual host's server { block declaration, adding a phantom {address:'{'} upstream entry. On re-save this wrote server { weight=1; into the nginx config before testing it — if nginx -t failed, the broken config stayed on disk. A future server restart would break nginx (all sites down). Fixed regex + made saves atomic (backup existing config, write new, test, rollback on failure).
phpMyAdmin php_version save always failing
phpMyAdmin's PHP-version dropdown checked /usr/bin/php{v} (CLI binary), but only php{v}-fpm may be installed — leaving the dropdown empty, currentPhp='', and Save always failing with "Config not found or PHP version missing". Fixed to check /run/php/php{v}-fpm.sock instead.
PHP 8.5 FPM socket permission denied (502 Bad Gateway)
php_install_script() in os_utils.py never aligned new PHP-FPM pool listen.owner/listen.group with nginx's actual worker user. Package defaults (www-data on Debian) didn't match a server running nginx as user nginx, causing 502 for every site on that PHP version. Fixed for both Debian and RHEL-family.
phpMyAdmin link opening on localhost
Databases page button hardcoded href="http://localhost:8082" — clicking from a browser opened port 8082 on the user's own machine, not the VPS. Fixed to use location.hostname.
bandwidth.py — vnstat install broken on RHEL-family
/api/bandwidth/install-vnstat hardcoded apt-get install -y vnstat, non-functional on all RHEL-family distros. Fixed to use pkg_install() + EPEL enablement.
Roundcube PHP version switcher never worked
Frontend posted {action:'set_php'} but save_module_settings() had no set_php handler — always returned Unknown action. Added the missing handler.
DDNS Manager Uninstall button
DDNS Manager's check command is echo found (it's a built-in feature). The Uninstall button ran apt-get remove ddclient (unrelated package) and the check still reported "found" — appearing as if uninstall silently failed. Added builtin:True flag, threaded through the API, hidden the Uninstall button for built-in modules.
Websites refactor (websites.py 1266-line → 8 modules)
Split monolithic websites.py into 8 focused modules. Added ensure_web_ownership() in websites_core.py to fix wp-config.php write-permission failures on new site creation.
⚡ Performance & UX
- Gzip compression via Flask-Compress:
app.js150KB → ~40KB,index.html350KB → ~70KB over the wire /api/modulesresponse cached (30s TTL) — App Store page loads instantly on revisit; cache invalidated on install/uninstall- Modal positioning fixed — WP Toolkit and other modals now use
.modal-overlayCSS class instead of inlineposition:fixed, which was getting trapped by Alpine's stacking context - Dashboard services list expanded — now includes all PHP-FPM versions, fail2ban, supervisor
Upgrading
cd /your/vortexpanel/source
git pull
cp -r panel/ web/ app.py /opt/vortexpanel/
pip install -r requirements.txt # adds flask-compress
systemctl restart vortexpanelOr fresh install:
wget -O install.sh https://raw.githubusercontent.com/BrowserlessAPI/VortexPanel/main/install.sh && bash install.shWhat's next (v3.2 roadmap)
- Per-site website analytics (nginx access log → traffic/top-URIs/status-codes dashboard)
- Alerting — CPU/RAM/SSL-expiry push notifications (Telegram/Discord/email/webhook)
- Disk usage analyzer
- Multi-user / RBAC
- LB node health checks