A modern web hosting control panel for WordPress and general PHP hosting. Jabali focuses on clean multi-tenant isolation, safe automation, and a consistent admin/user experience. It ships with a privileged agent for root-level tasks, built-in mail and DNS management, migrations from common panels, and an integrated security daemon (jabali-security) for real-time threat detection and automated response. The UI is designed to be fast, predictable, and easy to operate on a single server.
Version: see VERSION (release candidate)
This is a release candidate. Expect rapid iteration and breaking changes until 1.0.
- Website: https://jabali-panel.com/
- Demo: https://jabali-panel.com/demo/
GitHub install:
curl -fsSL https://raw.githubusercontent.com/shukiv/jabali-panel/main/install.sh | sudo bash
Optional flags:
JABALI_MINIMAL=1for core-only installJABALI_FULL=1to force all optional components--debugto show full command output instead of spinner
If Jabali is already installed, the script will detect it and offer to re-install (uninstall + fresh install). Your .env and credentials are backed up to /root/.jabali_reinstall_backup_<timestamp>/ before wiping.
Uninstall:
curl -fsSL https://raw.githubusercontent.com/shukiv/jabali-panel/main/install.sh | sudo bash -s -- uninstall
Force uninstall (no confirmation prompts, keeps home directories):
curl -fsSL https://raw.githubusercontent.com/shukiv/jabali-panel/main/install.sh | sudo bash -s -- uninstall --force
After install:
- Admin panel:
https://your-host:8443/jabali-admin - User panel:
https://your-host:8443/jabali-panel - Webmail:
https://your-host/webmail
The panel runs on port 8443 via FrankenPHP, independent of nginx. If nginx goes down, the panel stays accessible so users can log in and diagnose problems.
Jabali can run as a single container with all services managed by supervisord (MariaDB, Redis, Nginx, PHP-FPM, jabali-agent, queue-worker, cron, PowerDNS, Stalwart Mail Server). The Containerfile uses a multi-stage build based on debian:bookworm-slim.
docker pull shukivaknin/jabali-panel:latest
docker run -d --name jabali \
--hostname panel.example.com \
-p 80:80 -p 443:443 -p 8443:8443 \
-p 25:25 -p 587:587 -p 993:993 -p 110:110 \
-p 53:53/tcp -p 53:53/udp \
-v jabali-mysql:/var/lib/mysql \
-v jabali-storage:/var/www/jabali/storage \
-v jabali-mail:/var/mail \
-v jabali-home:/home \
-v jabali-letsencrypt:/etc/letsencrypt \
-e APP_URL=https://panel.example.com \
-e SERVER_HOSTNAME=panel.example.com \
--cap-add NET_BIND_SERVICE \
--cap-add NET_RAW \
shukivaknin/jabali-panel:latestThe entrypoint handles first-run initialization (database setup, key generation, migrations, self-signed SSL). Persistent data is stored in the named volumes listed above.
After the container starts, create an admin user:
docker exec -it jabali php /var/www/jabali/artisan tinker --execute="
\$u = new App\Models\User();
\$u->name = 'Admin';
\$u->username = 'admin';
\$u->email = 'admin@example.com';
\$u->password = bcrypt('changeme');
\$u->is_admin = true;
\$u->save();
"Then open https://panel.example.com:8443/jabali-admin to log in.
Requires auth.json for Filament packages:
podman build --secret id=composer_auth,src=auth.json -t jabali-panel:latest .- Per-user Linux accounts and PHP-FPM isolation
- SSH shell access via nspawn containers with auto-start and 5-minute idle timeout
- Root agent for SSL, mail, backups, and migrations
- Health monitor with auto-restarts and alerts
- cPanel and WHM migrations with step-by-step logs
- IMAP sync for migrating mail from external servers
- Stalwart Mail Server with webmail SSO (Bulwark JMAP client)
- Shared mailbox folders via Stalwart Mail Server
mail.domain.extauto-redirects to webmail- One-time login tokens (CLI + dashboard UI) with IP binding
- PowerDNS with REST API and native DNSSEC
- Restic backups with deduplication, encryption, and SFTP/S3 support
- First-time backup setup wizard with encryption password and remote destinations
- WordPress management (install, updates, and SSO)
- Integrated security suite (jabali-security) with real-time threat detection
- Encrypted diagnostic log sharing with ticket tracking
- Per-user page cache directories (moved from global nginx cache)
- Passphrase password generator (optional, 3 random words)
- GoAccess real-time statistics with WebSocket updates
- Domain bandwidth tracking synced daily from nginx logs
- 80+ CLI commands with full panel parity (noun:verb pattern)
- Audit logs and admin notifications
- Dashboard with stats, health, and recent activity
- User management with suspension and quotas
- Service manager for systemd services
- PHP version and pool management
- DNS zones, templates, and DNSSEC
- SSL issuance and renewals
- IP address assignments
- Backups and restores (local + remote) with first-time setup wizard
- Migrations (cPanel restore, WHM downloads, IMAP sync)
- Security (jabali-security daemon with real-time monitoring)
- One-time login link generator for support access (IP-bound tokens)
- Diagnostic report (encrypted sharing to support via paste service)
- Database tuning and query analysis
- Email queue management with delivery logs
- Audit logs and notifications
- Domains, redirects, and Nginx config
- DNS records editor
- Mail domains, mailboxes, forwarders, shared folders, and per-domain disclaimers
- IMAP sync (single and bulk mail migration)
- Webmail SSO (Bulwark, Next.js JMAP client)
- WordPress manager (install, SSO)
- File manager plus SFTP/SSH keys
- SSH shell access via nspawn containers with 5-minute idle timeout
- Databases (MySQL and PostgreSQL in tabbed view)
- PHP settings per account
- SSL management
- Cron jobs
- Backups and restore
- Logs, statistics, and bandwidth usage
- Support access link generator (one-time IP-bound tokens)
- Protected directories
- Root-level agent for privileged operations
- Queue-backed jobs for long-running tasks
- Health monitor with auto-restarts and alerts
- Redis ACL isolation for WordPress caching
- Multi-language UI
- Control plane: Laravel 12 app with Filament v5 and Livewire v4
- Panel web server: FrankenPHP on port 8443 (independent of nginx)
- Data plane: root agent handling privileged operations via Unix socket
- Job queue: async tasks and migration steps
- Webmail: Bulwark (Next.js JMAP client) at
/opt/bulwark, served at/webmail/via nginx proxy to port 3000 - SSH shell: jabali-isolator (Python, separate repo) managing nspawn containers for SSH access isolation
- Security: jabali-security daemon (separate repo) with real-time threat detection and automated response
- Logging: panel and agent logs for troubleshooting
- Server metrics: live /proc filesystem reads
Service stack (single-node default):
- FrankenPHP (panel on port 8443, self-signed or Let's Encrypt SSL)
- Nginx (user domain sites, phpMyAdmin, webmail proxy, Bulwark proxy)
- PHP-FPM (user site pools)
- MariaDB (user databases)
- Stalwart Mail Server (SMTP, IMAP, JMAP, ManageSieve)
- PowerDNS (DNS with REST API, MySQL backend)
- Restic (encrypted, deduplicated backups)
- Redis
- GoAccess (real-time web analytics in daemon mode with WebSocket)
- jabali-isolator (nspawn container management for SSH shell isolation)
- jabali-security (real-time threat detection, brute-force protection, WAF)
- Bulwark (Next.js JMAP webmail client on port 3000)
- Fresh Debian 13 install (no pre-existing web or mail stack)
- A domain for panel and mail (with glue records if hosting DNS)
- PTR (reverse DNS) for mail hostname
- Open ports: 22, 80, 443, 8443, 25, 465, 587, 993, 995, 53
See SECURITY.md for the full security policy, architecture, and audit history.
| Variable | Purpose | Default |
|---|---|---|
TRUSTED_PROXIES |
Comma-separated proxy IPs/CIDRs (or * to trust all upstream proxies) |
(unset) |
JABALI_INTERNAL_API_TOKEN |
Shared token for internal API calls from non-localhost | (unset) |
JABALI_IMPORT_INSECURE_TLS |
Disable TLS certificate verification for WHM/cPanel migration API calls | false |
SESSION_ENCRYPT |
Encrypt session data at rest | false |
SESSION_SECURE_COOKIE |
Send session cookies only over HTTPS | false |
PANEL_PORT |
HTTPS port for the FrankenPHP panel server | 8443 |
PANEL_HOSTNAME |
Hostname for the panel (used in APP_URL) | (auto-detected) |
PANEL_TLS_CERT |
Path to the panel TLS certificate | /etc/ssl/jabali/panel.crt |
PANEL_TLS_KEY |
Path to the panel TLS private key | /etc/ssl/jabali/panel.key |
- Shell arguments escaped with
escapeshellarg()to prevent OS command injection - Admin impersonation uses one-time IP-bound tokens; stop action requires POST + CSRF
- DKIM private keys encrypted at rest via Laravel's
encryptedcast - Migration API calls verify TLS certificates by default (opt-out with
JABALI_IMPORT_INSECURE_TLS) - Webmail SSO tokens stored in restricted directory with
0600permissions and 5-minute expiry - Admin backup downloads restricted to allowed directory prefixes
- WordPress page-cache API uses SHA-256 verification of
AUTH_KEY - CSP, HSTS, and other security headers on all panel responses
- Git deployment webhooks support signed payloads via
X-Jabali-Signature/X-Hub-Signature-256(HMAC-SHA256)
Update the panel (code, dependencies, database migrations, and infrastructure):
jabali update
This pulls the latest code from GitHub, runs composer/npm, applies database migrations, rebuilds caches, upgrades infrastructure (PHP config, nginx config, systemd services), and updates jabali-security if installed. Safe to run on a live server — the panel enters maintenance mode during the update.
Force a full update even if already on the latest version:
jabali update --force
The jabali command uses a noun:verb pattern. All commands support --json for machine-readable output and --yes to skip confirmations. Aliases: wordpress -> wp, database -> db, email -> mail.
jabali user list|create|delete|show|password|suspend|unsuspend|admin
jabali domain list|create|delete|show|enable|disable
jabali db list|create|delete|users|user-create|user-delete|tune
jabali mail list|create|delete|password|quota|queue|queue-retry|queue-delete|log
jabali ssl list|status|check|issue|renew|panel|panel-issue
jabali dns list|records|add|delete-record|sync
jabali backup list|create|delete|info|restore|password
jabali cron list|create|delete|toggle|run
jabali php list|install|uninstall|default
jabali service list|status|start|stop|restart|enable|disable
jabali system info|status|disk|memory|hostname|kill
jabali wp list|install|delete|update|scan|import
jabali agent ping|status|restart|log
jabali cpanel analyze|restore|fix-permissions
jabali login token [--user=] [--ttl=15] [--panel=]
jabali logs share [--raw] [--ttl=86400]
jabali update [--force]
See docs/cli-reference.md for full signatures and examples.
composer dev
php artisan test --compact
./vendor/bin/pint
The version string in the VERSION file must be kept in sync between the panel codebase and the installer (install.sh). When the installer clones the repository during a fresh install, it reads VERSION to display the installed version. If you bump the version in one place but not the other, the panel footer and installer output will show different versions.
Always update VERSION in a single commit that includes both the panel changes and any corresponding install.sh changes.
GPL-3.0 — see LICENSE for details.
Visiting mail.domain.ext in a browser automatically redirects to webmail (Bulwark). Autoconfig and autodiscover paths are excluded so mail client auto-discovery continues to work.
See the docs/ directory for detailed guides:
- Architecture — panel, agent, service stack, isolation
- CLI Reference — all 80+ commands with signatures
- Mail — Stalwart, mailboxes, webmail SSO, DKIM
- SSL — auto SSL, panel cert, certbot, rate limits
- DNS — PowerDNS, DNSSEC, CLI commands
- Backups — Restic, remote destinations, selective restore
- Security — jabali-security, WAF, nspawn isolation
- One-Time Login — shareable login tokens
- Diagnostic Logs — encrypted log sharing
- ADRs — architecture decision records