All documentation
  1. Home
  2. Docs
  3. From cPanel/WHM

cPanel/WHM to KLYRN

KLYRN asks the source to package one account with the cPanel tooling already on it, streams the result here, and turns it into a plan you read before anything is written. Start with what to expect; this page is the cPanel-specific half.

Read this before anything else

No archive produced by a real cPanel server has ever been fed to KLYRN's parser. The fixtures in the test suite were written from cPanel's published documentation by the same people who wrote the parser, and an archive built by test/vps/make-cpmove.sh in pkgacct's layout from a real WordPress site has been run end to end. A stand-in source exercises SSH, the root check, detection, the pkgacct invocation, streaming, cleanup and the whole pipeline. It does not exercise pkgacct.

The parser is written to report rather than guess, so anything it does not understand becomes a warning on the plan instead of a silent omission. That makes the gap survivable. It is not a reason to pretend it is not there: keep the old server running and compare the two sites yourself before DNS moves.

What you need before you start

Three routes, chosen by what you have:

Root on the live server
KLYRN confirms it is cPanel, checks free space, runs the server's own pkgacct, streams the archive here and deletes the temporary copy from the source. Any account on the box.
klyrn import ssh --host old.example.com --user root --account acmeuser
The cPanel account's own login
No root. KLYRN asks cPanel's own API to package that one account, waits for it, streams the result and removes it. The account can only package itself, and saying otherwise is refused with “connected as …, who can only package their own account”.
klyrn import ssh --host old.example.com --user acmeuser --password '…'
An archive you already have
A cpmove- or backup-<date>_<user> archive, uploaded in the panel with a progress bar or given to the CLI by path. .tar.gz, .tgz, .tar.xz, .tar.zst and .tar are accepted; a nested homedir.tar is handled.
klyrn import cpanel /root/backup-acme.tar.gz

And, whichever route:

  • A cPanel username: the Unix account, not the domain. It must match [a-z][a-z0-9]{0,15}.
  • Room on the source for the temporary archive; the job logs how much is free where it will be written before it starts.
  • Room here. A collection refuses below 1 GB free, and an archive import refuses when there is not room for about twice the archive's size.
  • A password or a private key. Encrypted private keys are not supported yet.
  • A decision about mail, before the cut-over rather than after it.

The cPanel routes do not use the two-phase engine. Pre-copy, delta and resume are reached by the generic SSH route (a Linux server over SSH), which takes a document root and its databases and takes a delta at cut-over. The cPanel routes produce one archive and apply it, which is the only thing possible when the source's own packaging tool is what produces the data. A very large cPanel account can be migrated the other way, at the cost of the metadata only the archive carries.

What migrates

Read from the archive's own layout, which is why this route knows things the generic one cannot:

The account
cp/<user>: the main domain, the contact address, the plan name, the source IP and the home directory. An archive with no DNS= line is reported as “main domain unknown” rather than guessed at.
Every domain the account served
userdata/main lists them and userdata/<domain> describes each: the document root, the PHP version, the server aliases, whether it had SSL. Addon, subdomain and parked entries all become sites, and the helper subdomain behind an addon domain is not turned into a site of its own.
PHP per domain
From each domain's own phpversion, normalised from cPanel's own notation: ea-php81 becomes 8.1, alt-php74 becomes 7.4, and inherit resolves from the parent rather than becoming a guess. A version this server does not have is warned about on the plan.
Databases and their users
mysql/<db>.sql for the dumps and mysql.sql for the grants. CREATE USER … IDENTIFIED BY PASSWORD and GRANT lines are parsed including quoted and backtick-escaped database names, and users are recreated with their original password hashes, so the credentials already in your application's configuration keep working.
Scheduled tasks
cron/<user>, rewritten to run as the new account's user. Environment lines are not entries and are skipped.
Files
The document root of each domain, copied from the extracted archive and handed to the account, with the read permission nginx needs.
The shape of each site
WordPress, Laravel, plain PHP or static, so the site is created as the right type rather than as a guess. A WordPress wp-config.php is re-pointed at its new database and the original kept beside it as wp-config.php.klyrn-import.bak.

An account renamed on the way in (because a system user of that name already exists here, or the cPanel name is not usable) has its database names re-prefixed and its wp-config.php updated, and the plan carries the note that other configuration files may need editing by hand.

What does not migrate

Mail does not migrate. KLYRN hosts no mail. The analysis counts the mail domains under va/ and the mailboxes under the home directory's mail/, and records them explicitly: “mail is not migrated in this version: 6 mail domain(s), 24 mailbox(es) recorded for export”. The number is in front of you before you move rather than after. A migration that left mailboxes behind finishes as Complete with warnings, never Complete, and the panel says how many and why. Your MX records are not touched.

  • DNS zones. The archive's dnszones/*.db are read, and KLYRN is not a nameserver, so there is nothing to take over. The plan says: after import, point the A records at this server's address.
  • Certificates. apache_tls/<domain> is recorded and not installed; KLYRN issues its own once the name resolves here.
  • Email forwarders, autoresponders and filters. They stay with whatever answers for your mail.
  • Apache behaviour nginx does not have. .htaccess is read and classified, not ignored: every directive line is converted, marked as needing review, or listed as not possible. See what happens to .htaccess.
  • Bandwidth history and logs. pkgacct is run with --skiplogs --skipbwdata, so they are not even packaged.

What downtime to expect

The source stays live throughout. What decides the window is not the copy: it is the gap between the moment the archive is made and the moment DNS moves, because everything written on the old server in between is not in the archive.

Packaging
On the source, with the source's own tool, while it keeps serving. pkgacct can take a long while for a large account; the job logs the archive's size and how long it took. Without root, cPanel's own packaging is watched by process id, and KLYRN gives up after six hours rather than waiting forever.
Streaming
Straight to this server, with progress, and the byte count checked against the size the source reported. The temporary copy is deleted from the source afterwards.
Analysis and apply
Nothing exists here until apply. The site is live on this server the moment it finishes, and nothing points at it yet.
The real window
From the archive being made to DNS moving. For a busy site that means either a short gap, or an accepted amount of lost writes, or a quiet hour. There is no delta pass on this route to close it.

If that window is the problem, migrate the site as a Linux server over SSH instead: that route pre-copies while the source is live and takes a final delta at cut-over, and its final sync is the only downtime.

The preflight checks, and what each refusal means

The connection check writes nothing at all (no migration row, no job, no history entry) and it identifies a source by reading files that exist, never by running the panel's own programs. What it finds for a cPanel box is /usr/local/cpanel/cpanel and the version in /usr/local/cpanel/version.

“the source does not look like a cPanel server (no /usr/local/cpanel/version)”
Nothing is run on a server that has not proved it is the panel you chose. For another panel, use the generic route; for an archive you already have, upload it.
“/scripts/pkgacct is missing on the source server”
The packaging tool is cPanel's, not KLYRN's. Without it there is nothing to run.
“no user ‘acme’ on the source server”
Use the cPanel username (the Unix account), not the domain.
“this login cannot package a cPanel account: uapi is not available on the source”
The non-root route needs cPanel's own uapi. Connect as root instead, or migrate the files and databases directly.
“cPanel refused to create the backup”
The error text is cPanel's own. The account may be over quota, or the host may have disabled backups.
“cPanel finished but no backup-….tar.gz appeared”
cPanel writes the backup into the home directory first. Check the disk quota on the source.
“this archive is not safe to extract”
The archive is read as real tar headers before anything running as root is pointed at it: a symlink or hard link whose target leaves the tree, a character or block device, a named pipe, or a member type KLYRN will not extract, each named. A nested homedir.tar is given the same reading, because it is the same stranger's file.
“this does not look like a cPanel account archive (no cp/<user> file found)”
The layout was not recognised. klyrn import archive <file> detects the kind from the archive itself, which is worth trying before assuming the file is wrong.
“a site with this domain already exists on this server”
On the plan, against that domain. Apply a subset with --domains, or remove the existing site first.
“database <name> already exists on this server”
Same shape, on the database rows. --databases takes a subset.

Pin the source's host key with --fingerprint SHA256:…, or in the connection form. A key that does not match stops the connection during the handshake, before the password is offered, and the refusal names both fingerprints. On 0.3.5‑beta.1 an unpinned connection trusts whatever key answers: recording the key on the first connection, refusing it when it changes, approving it, and the page that lists the record are built and arrive in a later beta.

Analysis and application are separate commands on purpose:

klyrn import show 1        # every domain, database, user and cron entry, with conflicts
klyrn import apply 1
klyrn import apply 1 --account acme --domains example.com --databases acme_wp
klyrn import apply 1 --no-cron --no-ssl

The cut-over

  1. Settle mail before you touch anything elseKLYRN never changes an MX record and runs no mail server, so there are exactly two safe answers: leave MX pointing at the old server and keep it running, or move the mailboxes to a mail provider first, while the old server is still there to fall back on. The archive told you how many mailboxes there are. Moving MX to KLYRN would mean every message refused.
  2. Apply, then look at the siteIt is live here and nothing points at it. The report hands you the curl --resolve and hosts-file lines to see it with your own eyes while the old server still serves the public.
  3. Read the verificationIn the panel, on the migrated site: eleven checks on the new server, each passed, warning or action required, each carrying its evidence. It does not connect to the source and cannot tell you the two pages render the same: that comparison is yours, and a blank page returns HTTP 200.
  4. Lower the TTL at your DNS providerHours before the cut-over, not at it.
  5. Change the recordsThe import result ends with the exact records to set. With a connected provider, klyrn dns point writes them.
  6. Let the certificate arriveklyrn site ssl example.com, or wait: the scheduler retries every ten minutes once the name resolves here.
  7. Re-run the verification and check the mail rowAn MX record pointing at this server is marked action required and named. That check exists because it is the most expensive mistake available at a cut-over.
  8. Keep the old server for a weekLonger, if it is still answering for mail.

How to roll back

KLYRN never deletes or suspends the source account. It runs the tools the server already has, streams the result, and removes only the temporary archive it asked the source to create. Nothing is installed on the old server and nothing is left behind on it. That is what makes rollback possible.

  1. Point DNS backThe old server has been serving throughout and has every file, database and mailbox it had before.
  2. Leave it runningEspecially if it still answers for mail.
  3. Remove what landed hereklyrn import delete <id> removes the migration record and its working files, and leaves the sites and databases it created alone; delete those separately and deliberately. An archive you gave by path is never removed; one uploaded through the panel is.

Before apply there is nothing to roll back at all: the plan is a document, and no account, site or database exists until you apply it.

Troubleshooting

The download stopped part way
“download interrupted after 4.1 GB”, and the source archive is deliberately left in place at the path the message names. Run the migration again.
“downloaded 4.1 GB of 6.2 GB”
The byte count is checked against what the source reported. A short file is deleted here rather than analysed.
“cPanel has been packaging for over 6 hours; giving up”
The non-root route waits for cPanel's own process. Check the account on the source, then run the migration again.
“could not remove … on the source”
A warning, not a failure: the copy arrived, and a temporary archive is still sitting on the old server. Delete it yourself.
“user … had no recoverable password hash; a new password was set”
The grant line carried a password in a form KLYRN will not reuse. The user exists with a new password; reset it in Databases to see it, and update the application's configuration.
“document root … not in the archive; site created empty”
The archive's metadata named a document root that is not inside it. The site exists so you can put the files there; nothing was guessed.
“no documentroot recorded; will use public_html/<domain> if present”
An addon or subdomain entry with no document root in its own file. The plan says which assumption was made, per domain.
“not enough free space to extract this archive”
The message names roughly what is needed: about twice the archive's size, because it is extracted before it is read.
“parked (alias) domain: add it as an alias of the main site after import”
A parked domain is a name, not a website. It is listed rather than turned into a second site.

Every route in one place: Migration. What is proven and what is not: What to expect.