Skip to content

Releases: BrowserlessAPI/VortexPanel

VortexPanel v3.3.0 — Visual Design, Multi-Webserver HTTP/3, Live Terminal, Mail & Bandwidth

22 Jun 09:30

Choose a tag to compare

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-uptime added to theme.css for 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 reuseport and Alt-Svc header. 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, \K in regular strings → raw strings (r'''...''')
  • Lines 1065, 1217, 1405: \s, \K, \S in 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):

  1. switch_version used blocking sh() calls with no output streaming
  2. 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
  3. 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 Popen with bufsize=1
  • 8-minute process timeout kills hung operations (e.g. apt-get update waiting on a slow mirror)
  • apt-get network 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 blanksave_module_settings() never defined mod, causing a NameError in the run_switch closure. Added mod = _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, port but was missing optimization, current_status, slow_log, log_path, datadir. All fields now match the MySQL handler.
  • save_optimization had no MariaDB handler — nginx, Apache, and OLS had handlers; MySQL/MariaDB fell through silently. Now reads the correct .cnf path 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 selDomain was 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) with journalctl fallback, 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). Now setup_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 in os_utils.py updated 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.conf if 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 after apt update if first attempt fails.
  • RHEL/Fedora/AlmaLinux/Rocky/CentOS: Checks if .so already exists (nginx.org packages bundle it). Falls back to dnf install nginx-mod-stream if not found.
  • After install: ensures load_module directive exists in nginx.conf, runs nginx -t, reloads nginx.
  • Detects Debian's auto-created modules-enabled/50-mod-stream.conf symlink 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 vortexpanel

Or fresh install:

wget -O install.sh https://raw.githubusercontent.com/BrowserlessAPI/VortexPanel/main/install.sh ...
Read more

VortexPanel v3.1.0 — WP Toolkit, firewalld, Performance

17 Jun 01:26

Choose a tag to compare

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.gz of 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-rules merged into a unified numbered list; --add-port for simple allow rules, --add-rich-rule for deny/reject/source-restricted rules; presets (webserver, mailserver, database) via rich rules; toggle enable/disable via systemctl

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 but rules_count was assigned the whole tuple and passed to int() — TypeError
  • PUT /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.js 150KB → ~40KB, index.html 350KB → ~70KB over the wire
  • /api/modules response 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-overlay CSS class instead of inline position: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 vortexpanel

Or fresh install:

wget -O install.sh https://raw.githubusercontent.com/BrowserlessAPI/VortexPanel/main/install.sh && bash install.sh

What'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