Plesk migration: move subscriptions from Plesk to Jabali Panel
Last updated
The Plesk ingest path. Status: production-supported. Unique among the panel’s migration sources: SSH pull-based, not archive upload — no pkgacct-style tarball to produce, no manual file transfer.
How it differs from cPanel / DirectAdmin / HestiaCP
| Source | Discovery | Data transfer |
|---|---|---|
| cPanel | Upload cpmove-<user>.tar.gz | Archive |
| DirectAdmin | Upload da backup-user tarball | Archive |
| HestiaCP | Upload v-backup-user output | Archive |
| Plesk | SSH into source, run plesk bin | Streamed on Restore |
| WHM | Upload multi-account cpmove dump | Archive |
The Plesk source is enumerated live: the panel opens an SSH session, calls plesk bin subscription --list, and per subscription reads --info for domains, databases, DNS, mail, WordPress. This means:
- No
pkgacctequivalent needed. Nothing to produce, ship, or delete on the source. - Discovery is read-only. Every command is a
--list/--info/--format jsoncall. The source panel stays fully operational. - Freeze writes at restore time, not discovery time. Analyze the source days ahead of cutover; only cut over DNS + freeze writes when you press Restore.
Prerequisites
- SSH access to the source Plesk host (default port 22). Non-standard ports supported.
- Principal:
rootor a sudo-capable admin. Required becauseplesk bin subscription --listmust see every subscription regardless of owner. - Credentials: SSH password OR SSH private key (base64-encoded in the migration secret store).
- Source Plesk version: any Plesk Obsidian (17.x, 18.x) on Linux. Plesk on Windows is not supported (different backend).
- Network reachability: destination Jabali host must reach the source over SSH. Private-IP sources require the
migration_allow_private_hostsserver setting enabled.
Operator workflow
- Add the source in
/jabali-admin/migrations:- Kind:
plesk - Host:
<source-plesk-fqdn-or-ip> - Port:
22(or custom) - Auth: SSH password or private key
- Kind:
- Discover — the panel connects via SSH and lists every subscription.
- Select subscriptions — pick one, several, or all. Per-subscription: pick the target Hosting Package.
- Analyze — reads domains, DBs, DNS, mail, WordPress from the source. No writes.
- Restore — synthesizes the cpmove-shape archive on the destination, ingests each asset. Per-phase progress shown in the UI.
- Communicate generated mail passwords to mailbox owners.
- Repoint DNS at the registrar.
- Issue SSL via the per-domain SSL toggle.
Cutover playbook
Same shape as the cPanel cutover playbook; the only difference is step 3, where you press Restore in the UI instead of running pkgacct + rsync.
- T-48h — lower DNS TTLs to 300s at the registrar.
- T-1h — freeze writes on the source Plesk subscription (put in maintenance).
- T-45min — press Restore on the pre-analyzed subscription in
/jabali-admin/migrations. - T-30min — restore completes; verify domains + DBs + mailboxes in the panel.
- T-15min — smoke-test the new host with
curl --resolve. - T-0 — repoint A/AAAA/MX at the registrar.
- T+1h — issue SSL.
- T+24h — restore original TTLs.
What gets migrated
| Asset | Behavior |
|---|---|
| Subscriptions | Become panel users. Login name = subscription name (Plesk’s subscription primary key). |
| Home directories | Copied over SSH from /var/www/vhosts/<domain>/ on the source. |
| Domains and subdomains | Created as panel Domain rows. Plesk’s alias-vs-subdomain distinction is preserved. |
| DNS zones | Read from plesk bin dns --info <domain>, translated to PowerDNS rows. |
| MariaDB / MySQL databases | Restored with users and password hashes preserved where compatible. |
| PostgreSQL databases | Restored into the panel-managed PostgreSQL instance. Plesk is one of only two supported sources for Postgres migration (the other is HestiaCP). |
| Email accounts | Recreated in Stalwart with generated passwords. Cleartext or Courier/Dovecot hashes from Plesk are not portable. |
| Forwarders, catch-all, autoresponders | Translated to Stalwart equivalents where the semantics map. |
| WordPress installs | Detected via plesk ext wp-toolkit --list -format json. Re-registered with the panel’s WordPress tooling on the destination. |
| Customers | Surface as suggested user assignments; operator can merge or split. |
| Service plans | Surface as suggested Hosting Package mappings. |
| SSL certificates | Not migrated — reissued via Let’s Encrypt on the destination. |
What is not migrated
- Plesk extensions. Kaspersky Anti-Virus, Fail2Ban configs, JetBackup, Node.js extension, Grafana extension — none translate. Equivalent capability: AppSec, CrowdSec, the panel’s built-in backups, Docker Apps.
- Plesk on Windows. Different backend (IIS + MSSQL + Exchange). Jabali is nginx + PHP-FPM + MariaDB + Stalwart. Migrate Windows Plesk with per-service exports and hand-import.
- Reseller structure. Flattened; no reseller construct exists in Jabali.
- Plesk firewall rules. Re-implement as UFW + CrowdSec.
Troubleshooting
Discovery fails with plesk: command not found.
The SSH principal is unprivileged and /usr/local/psa/bin/ is not in PATH. Log into the source, verify sudo plesk bin subscription --list works, and use a sudo-capable admin for the migration credentials.
Discovery fails with subscription "root" not found.
The panel probes plesk bin subscription --info <ssh-principal> to see if the login name matches a subscription. When you connect as root, this probe returns nothing — the panel then falls back to ListAccounts and prompts you to pick one. If the source has zero subscriptions, this errors out because there’s nothing to migrate.
PostgreSQL restore fails: role "<db-user>" does not exist.
Plesk stores Postgres role names case-sensitively; the panel restores them as-is. If your app connects with a lowercased role name, either fix the app connection string or CREATE ROLE "<original-case>" WITH LOGIN ENCRYPTED PASSWORD 'x' on the destination.
WordPress install detected but shows the “hello world” page after restore.
wp-config.php was pointing at the source’s DB_HOST=localhost:/var/run/mysqld/mysqld.sock; the panel-managed socket lives at a different path. Update DB_HOST in wp-config.php to 127.0.0.1 or the panel’s socket, then reload.
“SSH connect: dial tcp: i/o timeout” against a private-IP source.
The migration_allow_private_hosts server setting defaults to false to block SSRF-style abuse. Enable under Server Settings → Migration → Allow private hosts, then retry.
Audit
Standard per-phase audit rows. Per-domain creation writes domain.create; per-database restore writes database.restore. The SSH session origin (host + user) is recorded on every audit row for post-migration forensics.
Related reading
- cPanel migration — same restore pipeline; different discovery model (archive upload).
- HestiaCP migration — also supports PostgreSQL.
- Plesk alternative: migrate to a free hosting panel on Debian — the case for leaving Plesk and what Jabali replaces it with, with the SSH-pull migration walkthrough end-to-end.