Skip to content

Importing cPanel accounts

CorePanel can take over hosting accounts from an existing cPanel/WHM server and recreate them natively — files, mail, databases, DNS, cron and SSL — while preserving the original passwords wherever the hash scheme allows, so most users never have to reset anything.

There are two ways to run an import, and they take different inputs:

Admin panel (UI)CLI
InputLive pull over SSH from the source WHMA staged cpmove/backup archive already on the server
ScaleOne or many accounts (batch)One account per invocation
DNS cut-overOptional, automaticNot performed
ProgressLive, reconnectablePrinted to the terminal
Best forMigrating a whole server, hands-offScripting, air-gapped transfers, a single archived account

Both paths feed the same import engine, so the scope of what gets imported and how credentials are handled is identical.

Everything below is provisioned automatically from one cPanel account:

  • System account — the Linux user, its password hash (when compatible), and optionally its original UID/GID.
  • Closed logins — a mailbox or FTP account the source had locked arrives closed: it is created inactive, and the password cPanel kept behind the lock marker is preserved, so reopening it later restores the login its owner already knows instead of replacing it. These are not listed as passwords needing a reset — there is nothing to reset, and acting on such a list would reopen an access somebody deliberately shut.
  • Suspension state — an account cPanel had suspended is imported suspended, with the source’s own suspension date. That is not a detail of bookkeeping: importing it active would put a site somebody deliberately took down — for non-payment, for abuse — back on the internet. Its sites serve the suspension page and its logins stay closed until you lift it, which any edition can do.
  • Primary domain — virtual host, managed DNS zone and a fresh DKIM key.
  • PHP version — CorePanel runs one PHP version per account (the FPM pool belongs to the account’s system user), so the primary domain’s source version sets the account’s: exact X.Y if installed, otherwise the newest minor of the same major, otherwise the server default. Any downgrade is noted in the report — and so is every domain that ran a different version on cPanel, because it now runs the account’s. A site left on its old server’s default records no version at all (cPanel writes inherit), and an archive does not carry that server’s configuration — so there is nothing to read it from. Such a domain is reported by name, and the account’s version is then decided by the rule above without it: another of its domains that does name one, otherwise the newest version installed here, which can be several releases newer than the site ran. Check it after the import and move the account with corepanel account update <account> --php <version> if it needs another one. A whole-server transform has the source server in front of it and reads the answer instead of guessing.
  • Secondary domains — addon domains, domain aliases (parked) and subdomains, each with its own vhost. cPanel does not store an addon as a domain: it creates an internal subdomain of the primary (addon.comaddon.example.com) and keeps the real vhost under that name. CorePanel imports the addon as a first-class domain carrying that vhost’s document root, PHP version and certificate, and does not recreate the internal hostname — the report notes it rather than standing up a second site on the same files.
  • Domains parked on an addon — a domain parked on top of an addon rather than on the account’s main site. cPanel files it as a second addon entry pointing at the same internal subdomain, so nothing but the name says which of the two is the site. CorePanel imports it as an alias of that addon: it serves the addon’s document root and its mail is redirected there, and the report says which addon it belongs to.
  • Nested subdomains — a subdomain whose prefix is more than one label (dev.blog.example.com), imported as it is: one vhost, one record in the zone that owns it. Note a nested name is not covered by the parent’s wildcard certificate — a wildcard spans a single label — so CorePanel issues one of its own for it.
  • Mailboxes — with their messages (full Maildir) and passwords preserved.
  • Mail extras — forwarders, autoresponders and mail filters (translated to CorePanel’s Sieve model).
  • FTP accounts — the main account plus any extra ones, passwords preserved.
  • MySQL — databases with their data, plus database users (their MySQL password hash is re-applied, so app config keeps working).
  • Cron jobs — installed as panel-managed cron.
  • SSL certificates — cert, chain and key installed via the certificate manager.
  • DNS records — parsed from the source BIND zones and merged into the new zone; A/AAAA records that pointed at the old server IP are rewritten to this server’s IP, while MX, TXT/SPF, DKIM, CNAME and CAA are preserved.
  • Home directory — the full site tree, restored and re-owned (with SELinux contexts fixed).
  • WordPress installations — the import ends by scanning the account it just created, so every WordPress it brought over is already listed (and manageable) in WordPress when the job finishes. cPanel has no catalog of its own to copy: the sites are found on disk, exactly as the manual Scan does.
  • .htaccess rules — read where they are, not converted. Permalink blocks, front controllers, Deny from all files and password-protected directories keep working without anything being turned on. See .htaccess Support for what is applied and what is reported instead.

Every site keeps serving from the path it had on the source server. The importer does not move the files into CorePanel’s own layout, it re-points the vhosts at where the restored home actually put them:

On cPanelOn CorePanel after the import
~/public_html (primary)~/public_html
~/public_html/addon.com (addon, cPanel nests them)~/public_html/addon.com
~/public_html/blog (subdomain, named after the label)~/public_html/blog
~/dl.example.com (a site edited by hand to sit beside public_html)~/dl.example.com

Only the home directory prefix changes, and only when the account gets a different username here (a name conflict on import). A site provisioned by CorePanel afterwards uses CorePanel’s own layout — ~/domains/<name> — but nothing already imported is rewritten to match it.

This matters because absolute paths are baked into the sites themselves: wp-config.php, compiled caches, .htaccess include paths, and the cron jobs the importer restores alongside them. Moving the tree would break all of those silently. CorePanel instead extends the two things that make a directory servable — its SELinux file context and the PHP pool’s open_basedir — to cover the imported path.

This is a one-shot import, not a two-way sync, and it only reads cPanel archives:

  • Other panels (Plesk, DirectAdmin) — cPanel only.
  • The cPanel reseller tree — you decide which CorePanel seller each account belongs to. Its package is resolved for you from the plan name when a package of that name exists.
  • cPanel-specific state with no CorePanel equivalent — cpbackup jobs, Softaculous installs, cPanel API tokens.
  • A mailbox on a domain that has no mail identity on this server (e.g. a mailbox under an alias or a subdomain) — it is reported as dropped, never silently lost.
  • Per-domain PHP versions — CorePanel gives the whole account one version (see above). An addon still running an older PHP on cPanel moves onto the account’s version, and the report names every domain this affects so you can check those sites first. The account’s version can be changed afterwards if the importer’s choice turns out to be the wrong one.

CorePanel imports the original password hashes verbatim whenever they can be verified later — no plaintext is ever needed or shown.

CredentialPreserved when the hash is…Otherwise
System / SSH / main FTPSHA-512 $6$, SHA-256 $5$, MD5 $1$, bcrypt $2a/2b/2y$, yescrypt $y$Random password set, reset required
Mailbox & extra FTPSame crypt families as aboveRandom password set, reset required
MySQL usersmysql_native_password / caching_sha2_password (re-applied at the MySQL layer)

The system login is the strictest case: RHEL’s crypt(3) verifies the hash at SSH/FTP login, so a scheme it can’t check (e.g. argon2) is treated as non-preservable — the account gets a random password and is flagged for a reset, rather than becoming an account that can never log in. When a credential can’t be preserved, the login still exists — the user just has to set a new password. Those users are listed under Password resets required in the report.

Procedure — Admin panel (live pull over SSH)

Section titled “Procedure — Admin panel (live pull over SSH)”

Open Migration → Import account from cPanel in the panel. The import runs entirely on the CorePanel server, so you can close the browser and reconnect to it later.

Enter the source WHM/cPanel hostname or IP, the SSH port, and root SSH credentials. The password is used only for the connection and is never stored.

Step 1 — connect to the source cPanel/WHM server with root SSH credentials

CorePanel lists every account on the source server with its primary domain, plan and disk usage. Verify the server’s host-key fingerprint matches the real source, search/filter, and tick one or many accounts (Select all acts on the current filter). Suspended accounts are flagged.

Step 2 — pick one or more accounts from the source server inventory

Step 3 — target package, UID/GID preservation, DNS cut-over and conflict policy

  • Target hosting package — pick one to attach every imported account to it. Leave it unset and CorePanel resolves each account’s own cPanel plan by name, so accounts that came from different plans land on the matching CorePanel packages instead of all on one.

    Either way, the limits an account arrives with are the ones the source recorded for that account — disk, bandwidth, mailboxes, databases, FTP, aliases, subdomains and addon domains — not the package’s. Both panels copy limits into the account when it is created and let you edit a single account afterwards, so an account whose database limit was raised by hand keeps the number it was running under. The package only supplies what the source left unsaid. Applications are the exception: cPanel’s Passenger limit is a different product, so imported accounts get no applications until you grant them.

  • Preserve UID / GID (recommended) — keep the source system user/group ids for a full-fidelity migration.

  • Point DNS to this server — when each account finishes, CorePanel logs into the source server (still authoritative during the switch) and repoints only the A records that pointed at the old IP, so live traffic follows the migration without waiting for a registrar change. Domains whose DNS lives elsewhere are left untouched and flagged as external.

  • If the account or domain already existsAbort (fail) or Skip (do nothing).

Confirm the summary and start. Accounts are imported one at a time on the server.

Step 4 — review the migration summary before starting

Each account shows a phase tracker (Connect → Transfer → Restore → Done), the transfer size, a live count of created / reset / deferred / dropped / failed resources, and the full per-resource log. The import keeps running on the server if you navigate away — reopen the page and pick it up from Recent imports.

Step 5 — live per-account import progress with the resource report

Use the CLI when you already have a cpmove-<user>.tar.gz / backup-*.tar.gz archive (or an extracted account directory). The archive path is resolved on the CorePanel server, so stage it there first — the conventional location is /var/lib/corepanel/import/.

A dry-run parses the archive and prints the plan — resource counts, credential compatibility, conflicts and warnings — without touching the system:

Terminal window
corepanel account import /var/lib/corepanel/import/cpmove-acme.tar.gz --dry-run
cPanel import preview (dry-run)
==============================
Source: cpanel 110.0.5 @ web-oldhost-01.example.net
Account: acme (uid 1042, gid 1042)
Domain: acme-store.com
Plan: business
Contact: admin@acme-store.com
Resources:
domains=5 mailboxes=8 forwarders=4 databases=2 db-users=2
dns-records=27 ftp=3 cron=1 certificates=1
Credentials:
system password: preserved
mailbox/ftp: 9 preserved, 2 reset (of 11)
Domains:
- acme-store.com primary [ssl]
- shop.acme-store.com subdomain
- dev.shop.acme-store.com subdomain
- acme-labs.io addon
- acme-labs.net alias of acme-labs.io
Conflicts: none
Terminal window
corepanel account import /var/lib/corepanel/import/cpmove-acme.tar.gz \
--package 3 --preserve-uid --report /root/import-acme.json

Flags:

FlagPurpose
--dry-runParse and validate only; print the plan and touch nothing
--seller <id>Target seller id (default: the primary seller)
--package <id>Target hosting package; its limits drive the account
--preserve-uidKeep the source UID/GID via useradd -u/-g
--on-conflict abort|skipPolicy when the user/domain already exists (default abort)
--report <path>Write the full JSON import report to this path

The command prints the per-resource result and exits non-zero if the job status is failed, so it fits cleanly into migration scripts:

cPanel import
=============
Job: #42
Status: partial
Account: acme (id 17)
Domain: acme-store.com
Resources:
[created ] account:acme — system password preserved ($6$)
[created ] domain:acme-store.com — primary domain, vhost and managed DNS zone
[created ] mailbox:sales@acme-store.com — mailbox password preserved
[reset ] mailbox:old@acme-store.com — mailbox password could not be preserved; a reset is required
[created ] db:acme_shop — database restored
Password resets required (1):
! old@acme-store.com

A running or finished job can also be polled with core.GetImportJob and listed with core.ListImportJobs — the same jobs the panel’s Recent imports shows.

Each resource ends in one of these states:

StatusMeaning
createdImported successfully, credential preserved
reset requiredImported, but the password couldn’t be preserved — the user must set a new one
deferredRecognized and handled in a later phase (not lost)
droppedPermanently not imported (e.g. a package limit was hit, or a mailbox with no mail identity here)
skippedAlready existed, or skipped by the conflict policy
failedAn error occurred provisioning this resource

The overall job is completed only when every resource is created/deferred with no forced resets; any reset, dropped or failed resource makes it partial.

Warnings sit beside those statuses and can also downgrade a job to partial — an unmapped document root (see Where the sites end up) does, because the domain exists but its site is not serving.

  1. Hand out password resets. Everyone listed under Password resets required has a working login but a randomized password — send them a reset.
  2. Check DNS. If you didn’t use Point DNS to this server, update the A records (or the registrar’s nameservers) so the domains resolve to CorePanel. Domains flagged external always need a manual change.
  3. Verify the account. Confirm the site serves over HTTPS, mail authenticates (STARTTLS + IMAP login), and the databases contain their data. An account that arrived suspended is the exception: its site correctly shows the suspension page and none of its logins work. The report says so, and the accounts list badges it — lift the suspension if it should be serving. The report’s wordpress line says how many installations were cataloged; if it reads deferred, the scan could not run — the sites are unaffected, run corepanel wp scan <account> to list them.
  4. Re-run if needed. Imports are idempotent — re-running against an existing imported account resumes only the missing resources; it never duplicates them.