cPanel migration: move accounts from cPanel to Jabali Panel
Last updated
The cPanel ingest path. Status: production-supported.
Source archive
The standard cpmove-<user>.tar.gz produced by cPanel’s Backup Wizard or pkgacct. Either format works.
For server-wide migration, produce a separate cpmove per account or feed a WHM-level dump through the WHM pipeline.
What gets migrated
| Asset | Behavior |
|---|---|
| Linux user account | Recreated under the destination panel; UID may differ. |
| Home directory contents | Copied to /home/<user>/. |
| Hosted domains | Created as panel Domain rows; vhosts rendered by the reconciler. |
| Subdomains | Created as Domain rows (or aliases, per heuristic on the cPanel addon_domains map). |
| DNS zones | Translated to PowerDNS schema rows. |
| MySQL databases | Restored via mysql against panel-managed MariaDB. |
| MySQL users + bcrypt password hashes | Preserved: the panel stores the bcrypt hash directly so migrated apps keep authenticating without password reset. See commit be788a87. |
| Email accounts | Created in Stalwart. Cleartext passwords are not preserved (cPanel uses Dovecot/CRAM-MD5 which Stalwart’s Argon2id store rejects); generated passwords are printed in the migration report for out-of-band delivery. |
| DKIM keys | Imported as-is so mail flow is uninterrupted. |
| Mailing lists | Not migrated, Mailman is not currently in the panel; the affected accounts surface in the migration report. |
| FTP accounts | Mapped to SFTP via Match Group + the user’s SSH key vault. cPanel FTP-only passwords do not transfer (FTP is plaintext; the panel does not host FTP). |
| Cron jobs | Translated into systemd-user timers if the command passes the Cron allowlist. Disallowed commands surface in the report; the operator may add them to the allowlist and re-run. |
| SSL certificates | Re-issued by the reconciler via Let’s Encrypt; the cPanel cert is not migrated (paths and renewal hooks differ). |
What is not migrated
- WHMCS / Reseller-only configuration (Jabali has no reseller construct).
- cPanel “Backup Manager” snapshots (use the new panel’s Backups going forward).
- Spamassassin per-mailbox rules (Stalwart handles spam scoring server-side).
- Apache
.htaccessfiles referencing modules nginx does not have (mod_rewrite is supported via the nginx vhost template; mod_php directives are dropped).
Commands cheat sheet
Every command below is run on the source cPanel/WHM host unless noted:
# Per-account archive (Backup Wizard equivalent, headless)
/scripts/pkgacct <username>
# -> /home/cpmove-<username>.tar.gz
# Whole-server dump (one cpmove per account)
/scripts/cpbackup
# Copy archive to Jabali Panel host with resume
rsync --partial --progress cpmove-<username>.tar.gz \
root@<jabali-host>:/var/lib/jabali/migrations/incoming/
On the Jabali Panel host:
# List queued migrations
jabali migration list
# Analyze without restoring
jabali migration analyze <archive>
# Restore
jabali migration restore <archive>
# Retry a failed per-account restore from a WHM dump
jabali migration retry <job-id>
# Prune domains left over from soft-deleted cPanel accounts
jabali domain orphan-prune --dry-run
jabali domain orphan-prune --apply
Cutover playbook (minimise DNS-propagation downtime)
The general shape of a low-downtime cutover:
- T-48h — Lower TTLs. At the DNS provider (Cloudflare, Route 53, Namecheap, whichever hosts the zone), drop the record TTLs on the domains you’re moving to
300seconds. Wait 24h so resolvers pick up the shorter TTL. - T-1h — Freeze writes on the source. Put the source cPanel account into maintenance (or ask the tenant to stop posting/uploading). Prevents split-brain between old and new hosts.
- T-1h — Generate the cpmove.
pkgacct <user>on the source host. - T-45min — Ship the archive.
rsync --partialto the Jabali Panel host. - T-30min — Restore. Analyze, then Restore. Watch the per-phase log.
- T-15min — Smoke-test on the new host. Hit the new server IP directly with a
Host:header override:
Load WordPress admin, log a mailbox in via Roundcube, runcurl -sI --resolve example.com:443:<new-ip> https://example.com/ | headmysql -u <migrated-user> -p<pw> -e "SELECT 1;". - T-0 — Repoint DNS. Update A/AAAA/MX at the registrar. Given the 300s TTL, most of the world sees the new host within 5-10 minutes.
- T+1h — Issue SSL. Once DNS resolves, toggle the per-domain SSL switch. Certbot issues via the panel’s HTTP-01 challenge on port 80.
- T+24h — Restore original TTL. Bump TTLs back to
86400at the DNS provider.
The full end-to-end guide on the blog covers the same playbook with screenshots + WHMCS-billing edge cases.
MySQL bcrypt hash preservation
MySQL user passwords in cPanel are stored as bcrypt hashes in mysql.user.authentication_string. The panel stores the hash directly — no re-hashing, no password reset — so:
- WordPress
wp-config.phpconnects without any change. - Legacy PHP apps whose DB user is hardcoded in a config file keep working.
- Only the DB host in the app config may need updating (from
localhostto the panel’s socket path or127.0.0.1).
See commit be788a87 for the rationale and the compatibility matrix (MariaDB 10.6 through 11.x).
Migrating multiple cPanel accounts at once
For >1 account, produce one cpmove per user and batch them, or use the WHM pipeline:
# Source: one archive per account
for user in $(ls /var/cpanel/users/); do
/scripts/pkgacct "$user"
done
# Batch-ship
rsync --partial --progress /home/cpmove-*.tar.gz \
root@<jabali-host>:/var/lib/jabali/migrations/incoming/
# On Jabali: restore N accounts in parallel
jabali migration restore-all --parallel 4
The --parallel flag caps concurrent restores. Sensible upper bound: min(CPU cores / 2, 8) — MySQL import is the CPU hotspot.
Operator workflow (short version)
- Produce the
cpmove-<user>.tar.gzon the cPanel host. - Upload to
/jabali-admin/migrations(web) or SCP to/var/lib/jabali/migrations/incoming/. - Click Analyze on the row. Review the report.
- Click Restore.
- After completion, communicate generated mail passwords to mailbox owners (or set a force-first-login password-reset policy under Server Settings so they self-serve).
- Update DNS at the registrar to point to the new panel.
- Issue SSL via the per-domain SSL toggle.
Troubleshooting
“Analyze” fails with unrecognized cpmove format.
The archive was produced by an unsupported cPanel version (< 11.90 has legacy format differences). Regenerate on the source with pkgacct --version ≥ 11.90.
MySQL restore fails: Error: Duplicate entry '<db-name>' for key 'PRIMARY'.
The destination already has a database with the same name. Either drop the destination DB first (DROP DATABASE <db-name>;) or rename the incoming DB in the Analyze report before Restore.
Mailboxes restore but IMAP shows “authentication failed”. Expected — cPanel Dovecot/CRAM-MD5 hashes are not portable. The Restore report prints new generated passwords for each mailbox. Distribute them out of band, or set the force-reset flag in Server Settings so mailbox owners self-serve.
Certbot fails after cutover with no valid A record.
DNS hasn’t propagated. Run dig example.com A @1.1.1.1 and wait until the answer matches the new IP. Then retry the SSL toggle.
A domain restored but the site shows a “Welcome to nginx” page.
The vhost render is queued in the reconciler (60-second interval). Wait one interval, then reload. If it persists, jabali reconciler run --once --domain example.com forces immediate reconciliation.
Audit
Each phase emits an audit row; the per-domain creation also writes one domain.create row per domain.
Related reading
- cPanel migrations to Jabali Panel: end-to-end guide — extended walkthrough with the cutover playbook, TTL prep, and MySQL bcrypt preservation rationale.
- An open-source cPanel alternative for Debian 13 — the case for leaving cPanel and what Jabali replaces it with.
- DirectAdmin migration — same pipeline, DirectAdmin
da backup-userarchive as input. - HestiaCP migration — same pipeline,
v-backup-userarchive as input. - WHM migration — server-wide dump, iterates the cPanel pipeline per-account.