# Transforming a cPanel server

> Turn the cPanel server you already have into a CorePanel server, on the same machine. Nothing is copied, the IP does not change, and the sites are offline for seconds rather than hours — 28 of them on the server we timed. One guided command, five stages, and a way back until you close it yourself.

Source: https://www.corepanel.net/docs/cpanel-transform/
Last updated: 2026-09-04
Part of the CorePanel documentation — https://www.corepanel.net/docs

---

This turns **the cPanel server you already have** into a CorePanel server. Not a copy on
a second machine — this machine. Home directories, mailboxes and the MySQL data
directory stay exactly where they are and CorePanel adopts them, so the sites are offline
for **seconds rather than hours**, whether the server holds 20 GB or 2 TB.

On the server we timed — 45 accounts, 51 domains, 84 mailboxes, 43 databases — the sites
were unreachable for **28 seconds**, and the whole run took ten minutes. That window grows
with the number of *files* on the server, not with the size of the data, so a much larger
server is a minute or two rather than twenty. It is not a figure you have to take on
trust either: PLAN prices it for **your** server, from your server, before you agree to
anything.

![The transformation in five stages: CHECK and PLAN read the server and change nothing, PREPARE installs CorePanel beside a still-serving cPanel, CUTOVER is the only offline window, and VERIFY proves every domain, mailbox, database and zone answers. Below them, a bar shows cPanel serving until the cutover and CorePanel serving after it, and an arrow shows that a rollback stays available until cleanup is run](https://www.corepanel.net/_astro/cpanel-transform-stages.DjP2CmxR.svg)

If you would rather move the accounts onto a **different** server, that is the other
route — see [Coming from cPanel](https://www.corepanel.net/docs/from-cpanel) for how the two compare, and
[Importing cPanel accounts](https://www.corepanel.net/docs/cpanel-import) for how it works.

> **This tool is in beta**
>
> It runs end to end on real cPanel servers and the timings above are measured on one, but
> the set of servers it has met is still small and nearly every new one teaches it
> something. Run the current build — `corepanel-transform version` says which you have — and
> read the [changelog](https://www.corepanel.net/docs/transform-changelog) before you book a window: it lists what
> changed in each release, including the notes that apply to a server transformed with an
> older build.
## Before you start

| | |
|---|---|
| **The OS** | AlmaLinux, Rocky or RHEL **8, 9 or 10**. On CentOS 7, run cPanel's own `elevate-cpanel` first, aiming at AlmaLinux 9. |
| **Root** | Every stage runs as root, on the cPanel server itself. |
| **A backup** | A hypervisor snapshot of the whole VM is the cheapest kind. The tool asks you to type `I-HAVE-A-BACKUP` before it changes anything. |
| **An hour, unhurried** | The offline part is seconds. The sitting around it is not something to rush. |

One habit worth adopting: **stop creating accounts in WHM** for the duration. It is not
forbidden, it is just one more thing to reconcile.

The tool also reports anonymously as it goes — which stage ran, how long it took, how
long the sites were actually offline, and the id of the step that stopped it if one did.
Never a hostname, a domain, an account name or an error message. It says so once, before
the first stage, and `--no-telemetry` on any command (or `COREPANEL_NO_TELEMETRY=1` in
the environment) turns it off; the exact field list is in
[Anonymous usage statistics](https://www.corepanel.net/docs/telemetry#the-migration-tools-report).

---

# The tutorial

## Step 1 — Ask the server whether it can be done

Run this **on the cPanel server**, as root:

```bash
curl -fsSL https://get.corepanel.net/check | sh
```

It downloads a single binary, checks it against its published checksum, runs it, prints a
report and deletes itself. It **reads and nothing else** — no installation, nothing
configured, nothing stopped. It does look up where each domain points and ask each of
this server's own sites for its homepage, the way a visitor would, so the check can tell
you which sites are broken *before* you book a window; `--no-dns` turns both off if you
are scanning a copy of a server. Run it on a production server in the middle of the day.

What comes back is an inventory of the server, a score, and a verdict in one of three
states:

- **READY TO TRANSFORM** — go ahead.
- **READY TO TRANSFORM, WITH ITEMS TO REVIEW** — go ahead, once you have read the
  warnings. Fix them *before* you book the window, not during it.
- **CANNOT BE TRANSFORMED IN PLACE** — the report names each blocker and its way out.

The full page on this stage, with an example report and what every finding means, is
[The cPanel migration check](https://www.corepanel.net/docs/migration-check).

## Step 2 — Install the tool

The same URL installs the binary it just verified:

```bash
curl -fsSL https://get.corepanel.net/check | sh -s -- --install
```

That puts `corepanel-transform` in `/usr/local/bin`. Nothing else on the server changes.

## Step 3 — Run it

```bash
corepanel-transform
```

With no arguments it walks the whole transformation as one guided sitting. Before each
stage it prints what that stage will do to **this** server — what goes offline, for how
long, the steps it runs, and whether it can be reversed — and then it asks. **Enter**
goes on, **q** stops.

It goes in five stages:

| | Stage | |
|---|---|---|
| 1 | **CHECK** | reads this server again and says whether it can be transformed |
| 2 | **PLAN** | writes the ordered steps and the offline window |
| 3 | **PREPARE** | installs CorePanel beside cPanel — **nothing goes offline** |
| 4 | **CUTOVER** | cPanel stops, CorePanel takes over — **the offline window** |
| 5 | **VERIFY** | proves every domain, mailbox, database and zone answers |

Two stages ask you to type a word rather than press Enter, and both are deliberate:

- **PREPARE** asks for `I-HAVE-A-BACKUP`.
- **CUTOVER** asks for `TRANSFORM`.

> **Stopping costs nothing**
>
> Every step writes down what it did before the next one starts. Answer **q** at any
> prompt, or lose the SSH session entirely, and running `corepanel-transform` again picks up
> exactly where it stopped — a stage already finished is skipped rather than repeated.
If your server has **more than 20 websites**, the free Personal edition cannot hold it,
and PREPARE will say so. The guided run offers the fix on the spot: a **free 14-day
trial** of Pro or Business, no card and no key. It asks before minting one, because a
trial is bound to this server's address. You can also do it yourself:

```bash
corepanel license trial --edition business --email you@example.com
```

## Step 4 — Read the verification, then look yourself

VERIFY checks every domain, mailbox, database and zone against the inventory taken
**before** anything happened, and reports per account. Read it before you tell anyone the
move is done.

Then spend five minutes looking with your own eyes:

```bash
corepanel account list                    # every account is here
curl -sI https://example.com/             # and once in a real browser
doveadm auth test user@example.com        # a mailbox logs in
dig SOA example.com @127.0.0.1            # the zone answers
```

Then open the panel itself. A transformed server does not ask you to configure it first.
It opens on its dashboard, not on the initial configuration assistant, because PREPARE has
already read the answers out of cPanel — on an in-place transformation they are the only
correct ones:

| What the panel needs | Where the run takes it from |
|---|---|
| Main server address | `ADDR` in `/etc/wwwacct.conf` — the machine keeps its address, which is why no DNS is re-pointed |
| Server hostname | `HOST`, and it is *applied* as well as recorded: the panel's own site, the login banner and Postfix's HELO name |
| Nameservers | `NS` through `NS4`, in cPanel's own order — the first is the one every SOA names |
| The address each nameserver answers on | resolved from the delegation your server has been serving all along, so a nameserver that lives on another machine stays on it |
| Administrator's contact address | `CONTACTEMAIL` |

So the panel is already at your server's own hostname over HTTPS, or at
`https://<server-ip>` if you would rather not wait for anything.

Two details are worth knowing:

- **The contact address is recorded, not acted on.** It is not registered as the
  certificate (ACME) contact and it does not rename the panel's administrator account.
  Open **Server → Configuration Wizard** in the panel when you have a minute: confirming the
  address there is what commits it to both, and the same screen asks for the
  administrator's *name*, which cPanel does not hold and the run therefore never invents.
- **A nameserver that does not resolve** is recorded at this server's own address — it is
  what cPanel already names in the SOA of every zone this machine serves — and the run
  says so on screen. That line is worth reading: a delegation that does not resolve was
  already broken before the transformation.

> **If the run predates this**
>
> A server transformed with an older build opens the initial configuration assistant on
> first login, asking for a contact address, a hostname and nameservers it was handed
> half an hour earlier. Nothing is wrong with the server: the run adopted the identity and
> left the contact address and the nameserver addresses blank, and the panel counts a
> configuration with either of those missing as incomplete. Fill the four steps in once and
> it does not come back.
### The panel's own login

The cutover prints it — the step before the health gate, so it arrives whatever the gate
goes on to say — and the run repeats it as the **last thing on the screen**, whether it
finished or stopped:

```
SIGNING IN TO THE PANEL

  panel     https://your-server-hostname
  user      coreadmin
  password  <generated>
```

The password is generated by the run and kept in **`/root/.corepanel_password`**, readable
by root only — the same file a hand-installed CorePanel uses. Change it in the panel
whenever you like; nothing reads that file back.

Why the run has to hand you one at all: a CorePanel installed by hand is set up with
`corepanel system init`, which asks for the panel domain, the administrator email and a
password before the server has any accounts on it. A transformation never passes through
that moment — the identity comes out of cPanel and the accounts are registered while
cPanel is still serving — so the run sets the administrator's first password itself.

It only ever sets it **once**. Running that step again on a panel that already has a
password leaves it alone and just tells you where to sign in, so an administrator who has
already been in and changed the password is never locked out by a second pass — which is
what makes it safe to put in front of the gate that can send you round again.

> **If the run predates this**
>
> A server transformed with an older build has a panel with no password at all — the
> administrator exists, but nothing ever gave it a credential. One command fixes it:
> `corepanel system init --admin-email you@example.com --no-interactive` prints the address,
> the user and a password, and writes the password to `/root/.corepanel_password`. Leave
> `--domain` out: the transformation already set the panel domain, and passing it again only
> re-does that work.
## Step 5 — Live with it for a few days

**cPanel is not removed.** It is stopped and disabled, and its packages, its
`/usr/local/cpanel` and its licence are all still on the disk. That is what keeps the way
back open, and leaving it there costs nothing but disk space.

While you are in that window:

```bash
corepanel-transform status      # what this run did, step by step
corepanel-transform rollback    # gives the server back to cPanel (asks you to type ROLLBACK)
```

A rollback gives back a **whole** cPanel — mail, FTP and DNS included — and because the
data was never copied, **mail delivered and database rows written since the cutover
survive it**. There is nothing to reconcile: there is one copy of the data, and both
panels read the same bytes.

## Step 6 — Remove cPanel, another day

When you are sure — days later, not the same evening:

```bash
corepanel-transform cleanup     # asks you to type ERASE-CPANEL
```

This is the point of no return, and it is the one thing the guided run never offers you.
It refuses to run unless VERIFY has passed, the report found nothing broken, and
something is answering on ports 80 and 443 *right now*.

**The panel remembers this for you.** Until cPanel is actually gone, the dashboard carries
a card saying it is still installed, how many of its packages are left and roughly what
they occupy — with this command on it, ready to copy, from the first day. It disappears by
itself once the cleanup has run, because what it goes by is what is on the machine. See
[Dashboard](https://www.corepanel.net/docs/dashboard#cpanel-is-still-installed).

That is the whole tutorial. Everything below is the detail behind it.

---

# What actually happens

## Why the window is short

**The data never moves.** Home directories, Maildirs and the MySQL data directory are
adopted where they already are — same paths, same uids, same bytes on the same disk. The
IP does not change either, so DNS delegation is untouched and there is no propagation to
wait for.

What the transformation does is swap **which set of daemons serves that data**. That is
why the offline window is measured in seconds regardless of the server's size — stopping
one set of daemons and starting another costs what it costs whether they were serving
20 GB or 2 TB — and why a rollback afterwards does not lose the writes made in between.

The one step that *can* scale with data size is the database, and the plan prices it from
your server before you agree to anything:

| What you have | What happens | How long |
|---|---|---|
| MariaDB, same major | Adopted in place: a config swap and a restart | Seconds |
| An older MariaDB | `mariadb-upgrade` over the same data directory | Minutes |
| MySQL 5.7 / 8.0 | Dumped and restored per database | This is where an afternoon can hide |

## PREPARE, in detail

cPanel keeps serving every site, mailbox and database throughout this stage. What lands
is only what genuinely coexists: CorePanel itself (its web server installed but
**masked**, so there is no fight for port 80), the PHP stack under its own prefix, and
the panel on a port of its own.

Then every account is **registered, not created**: the system user is adopted with its
uid and its password hash, the home directory is adopted where it is, and the site is
provisioned around them — PHP-FPM pool, vhost, DKIM key, DNS zone. Mailboxes, FTP logins,
databases, certificates and cron jobs all come across with their existing credentials.

Mail, FTP and DNS are *not* installed here, and that is not an oversight: cPanel ships its
own builds of those same daemons at the same paths, so installing ours would replace
binaries under a running service. They are cutover work.

### The PHP your sites are actually on

Before a single account is registered, PREPARE reads the interpreters cPanel is running
right now and makes this server match them.

**Versions.** Every PHP version some site on the server is on gets installed, whether or
not a fresh CorePanel would have it. This is not a nicety. An account is given the PHP
version CorePanel *has*, falling back to the newest one installed — so on a server where
only 8.4 was present, every site that ran cPanel's 8.1 came back on 8.4, three minor
versions up, and nothing said so. A version CorePanel cannot install at all — a 5.6, a
7.0, anything nothing packages any more — is named in the output along with the version
those sites will land on instead, so you can test them while cPanel is still serving.

**Sites with no version of their own.** A cPanel site that was never moved off the
server's default does not record a version at all — its vhost says `inherit`, and the
version it runs is whatever `/etc/cpanel/ea4/php.conf` says the server's default is. That
is the state of every account nobody has touched the PHP of, so on most servers it is the
common case rather than the exception. The transform reads that file and resolves it, so
those accounts count exactly like the ones that name a version: their PHP gets installed,
and their extensions get carried across. Without that step they carry no version at all,
which lands them on the newest one installed — and takes their extensions with it.

**Extensions.** Each source interpreter is asked what it loads, the answer is compared
with what the interpreter replacing it loads, and the difference is installed from the
[extension catalogue](https://www.corepanel.net/docs/php/extensions). On a typical cPanel server that is `imap`,
`soap`, `bcmath`, `posix` and the **ionCube Loader** — the last of which matters more
than the rest put together, because software encoded with ionCube does not fail when the
loader is missing. It answers `200 OK` with a page saying it cannot run, which is exactly
what a health check reads as a working site. WHMCS is the common case.

An extension the catalogue does not carry cannot be installed for you, and is listed by
name with the interpreter it came from. Everything this step did — and everything it
could not do — is checked again in VERIFY, per version, while the rollback window is
still open.

This is also where cPanel's `exclude=` line loses its `php*` entry, because with it in
place `dnf` reports every CorePanel PHP package as not existing. The rest of that line —
the entries protecting cPanel's own dovecot, exim and FTP builds — stays until the
cutover, and a rollback restores all of it.

Two things are deliberately deferred to the window, and reported as pending rather than
done: **tightening the home directories** (doing it now would take every site offline,
because cPanel's Apache reaches docroots differently) and **applying disk quotas**
(enforcing a limit against an already-full home would suspend a live account
mid-afternoon).

If a single account fails to import, its adoption undoes itself — pool, vhost, DKIM, cert
registration — and never the user, the home, or a byte inside it. Fix it and run again.

## CUTOVER, in detail

The steps are ordered so that **mail goes first** — senders retry for days, so it is the
forgiving one — and **web goes last and comes back first**, because it is the clock
everyone is watching.

1. **Every site is asked what it answers, while cPanel is still serving it.** Nothing is
   changed and nothing is stopped; the reading is written down, and the health gate at the
   end compares against it. See [below](#the-reading-taken-before-the-window) for why it
   is worth a step of its own.
2. cPanel's own services stop.
3. Exim's queue is flushed and drained; whatever will not go is listed for you.
4. Mail and FTP stop.
5. The databases are taken over, in the mode the plan named. **On a server where that
   restarts MariaDB, the visible clock starts here** — every site with a database behind
   it is serving an error for the length of the restart, and the plan says so before you
   agree to the window.
6. Apache stops, and cPanel's own scheduled jobs are neutralised with it — an update run
   firing mid-cutover would restart everything just stopped. **On every other server the
   clock starts here**, and either way the run prints which step opened it.
7. Anything else that was serving or protecting the old stack is stood down — a proxy in
   front of Apache, a WAF, the host firewall.
8. **The server's outbound address is pinned to its IPv4**, in `/etc/gai.conf`. This
   follows the step above for a reason: on a dual-stack machine, with the firewall down
   and no policy in that file, the C library prefers IPv6 for every destination that
   offers both — so a server that has always been seen at its IPv4 starts being seen at
   its IPv6, and everything licensed against the address (WHMCS, Softaculous, LiteSpeed,
   JetBackup) begins failing its licence check hours later, when the verdict it cached
   runs out. A server with no IPv6 route out is left alone, and so is one whose
   `/etc/gai.conf` already says something — the run tells you what to re-check instead.
9. The work that can only happen with nothing running: the home directories are tightened
   to 0700 (cPanel leaves them traversable so its Apache can reach the sites; CorePanel
   does not need that), the disk quotas are applied, and cron is paused so neither runs
   against a job halfway through its own.
10. CorePanel starts: PHP pools, then the web server. **The clock stops here.**
11. Mail, FTP and DNS are installed and started — cPanel's builds come off, ours go on.
    Postfix's submission service (587) is left **as permissive as your Exim was**: cPanel
    offers AUTH there without requiring TLS first, and applications already on the server
    — a billing system, a WordPress plugin — are configured against that. The reading is
    taken in step 1, while Exim is still answering; a server that already required TLS
    keeps requiring it, and STARTTLS is offered either way, so mail clients go on
    encrypting.
12. The DNS zones are published, with the same records, on the same port 53.
13. The panel is opened for its administrator: the address and a generated password, put
    on screen. This comes **before** the gate on purpose — see below.
14. A health gate probes the result from outside: one HTTP request per site, a mailbox
    login, a database login, an SOA query per zone, an FTP login — and every site's answer
    against what step 1 recorded.

There is no DNS ceremony anywhere in that list. Same IP, same delegation.

You do not have to hold that list in your head while it runs. Every step says which of
how many it is and what the plan expects it to cost, and from the moment the sites go
down the tool keeps the clock on screen — how long they have been down, and how much of
the window the plan still owes:

```
  6/15 Stop cPanel's web server — about 40 s
       the sites go down here · the plan books about 2 m
       Apache stopped; cPanel's cron is neutralised for the window
       took 12.4 s, well under the 40.0 s the plan estimated
       …
  9/15 Keep this server's outbound address on its IPv4 — about 1 s
       sites down 12.4 s · about 80 s left in the window
       outbound address pinned to IPv4 in /etc/gai.conf
       took 0.0 s
 10/15 Tighten the homes and apply quotas — about 15 s
       sites down 12.4 s · about 80 s left in the window
       45 homes tightened to 0700, median 59 ms, worst jsmith at 1.2 s
       took 5.1 s, well under the 15.0 s the plan estimated
 11/15 Start CorePanel — about 25 s
       sites down 17.5 s · about 65 s left in the window
       php-fpm and corehttpd are up; 51 vhosts loaded
       took 10.5 s (the plan estimated 25.0 s)
       the sites are back — down 28.0 s, and the plan booked about 2 m
```

That last line is the number the whole transformation is judged on, and it is measured
rather than estimated. It is also the one figure the tool reports back to us, so the
estimates above it get better with every server that runs one.

Resuming into a half-finished cutover says so instead of restarting the clock at zero:
the sites went down in an earlier sitting, and the figure on screen says which outage it
is measuring.

If the health gate finds something wrong it **names it and offers the rollback rather
than taking it** — the step changes nothing, so you can fix what it found and run it
again with the sites live.

**A stopped gate does not leave you locked out.** By the time it runs, this server is
already CorePanel's: the sites are being served, the accounts are registered, mail and
DNS are ours, and stopping there undoes none of that. So the step *before* the gate is the
one that hands over the panel — the address and the password, printed again under the
error, last on the screen. Fixing what the gate named is usually a few clicks in that
panel, and then you run `corepanel-transform` again: it re-enters the step that stopped
and no other. The rollback is still there if you would rather take it, but it is a choice
now rather than the only move left.

### The reading taken before the window

The first step of the cutover asks every site on the server for its homepage while cPanel
is still serving it, and writes down what came back. It changes nothing, it can be run as
often as you like, and it exists because **"the site answers" is not "the site works".**

A page can report its own failure with a perfectly successful status code. Software sold
in encoded form — WHMCS and most billing and licensing systems are — ships a stub that
checks for its loader and, when the loader is missing, prints a paragraph saying so and
stops. PHP considers that a request it finished successfully, so the web server sends
**HTTP 200** with the notice where the page should be. A health gate that reads status
codes sees a site in perfect order.

With the reading in hand the gate asks a better question — *is this the same page?* — and
answers three things it could not answer before:

- **A site that broke during the cutover is named, with what the page now says.** A
  missing loader, a missing extension: the gate quotes the sentence off the page rather
  than reporting a status code that looks fine.
- **A site that was already broken is not blamed on the run.** Somebody's unpatched
  template engine has been printing a fatal error since last month; cPanel served that as
  a 200 because suPHP prints errors into the page, and CorePanel serves the same fault as
  an honest 500. Without the reading, that reads as a site the transformation killed — and
  as an invitation to roll a whole server back over it. The step names those sites at the
  start of the window, in capitals, before anything is stopped.
- **A site that stopped redirecting is reported.** An application that sent visitors to a
  login page and now hands them a page instead has stopped reaching its own code.

What is deliberately **not** compared is how big the page is. On a real cPanel server a
third of the homepages answer 200 with an empty body, so a page that is blank in perfect
health gets *bigger* when it breaks — and a homepage carrying a session token or a cart
count differs between two requests a minute apart. "It got smaller" is not evidence, and
nothing here treats it as any.

If the reading is missing — an older plan, an interrupted run — the gate says so and
falls back to judging status codes, rather than passing in silence.

### What PHP wrote while the gate was asking

A page can fail without the failure being visible anywhere in what it sends back.
CorePanel runs PHP-FPM with `display_errors` off, so a fatal error prints nothing into
the page — and if the script had already sent enough output for the buffer to flush, the
response is **HTTP 200 with the page cut short mid-sentence**. Under the buffer size the
same fault is an honest 500 the gate can see; above it, nothing in the response says
anything is wrong.

So the gate also reads the one place a fatal always lands: the account's own PHP error
log. It notes where the log stands, asks that account's sites for their homepages, and
reports anything PHP recorded in between — with the file and the line.

**This is always a warning and never a failure**, and the reason is what the
transformation is for. It keeps a site as it was. The code that threw the error is the
customer's own, unchanged by the run, and it was throwing the same thing on cPanel — where
suPHP printed it into the page instead of logging it, which is why nobody had to look. A
site that was already bad stays bad, its owner knows, and rolling a whole server back over
somebody's plugin is not the answer.

The same check notices when the log is unwritable, which matters more than it sounds: an
empty log and a log nothing can write look identical, and only one of them means there
were no errors.

## What carries over, and what does not

**Carried, verbatim or equivalent:** system users with their uids and password hashes ·
home directories in place · Maildirs including Dovecot's indexes · mailboxes, forwarders
and their hashes · FTP logins · domains, subdomains, aliases and addon domains ·
per-domain PHP version · DNS zones · SSL certificates and keys · databases, users and
grants · cron jobs · hosting packages and per-account limits · resellers, as sellers, one
level deep, **with the two ceilings they were sold on and the plans they sell**.

### Your resellers come across with their limits

cPanel keeps four files about a reseller. This run reads three of them — who owns whom,
the reseller list itself, and `reseller-limits.yaml` — and each reseller is created in
CorePanel **before** any of their accounts is registered, carrying the two totals CorePanel
enforces: how many accounts they may sell, how much disk in total, and whether they may
oversell that disk. A reseller capped at 25 accounts and 50 GB on cPanel is capped at 25
accounts and 50 GB here, and the [ceilings refuse](https://www.corepanel.net/docs/accounts/quotas#limits-above-the-account-the-resellers-totals)
rather than decorate: the twenty-sixth account is turned away, whoever asks for it.

A reseller who had limits but no accounts still comes across. They exist on the source, and
an import that only looked at accounts would never have heard of them.

**Their hosting plans come with them.** cPanel has no owner column for a package — it puts
the owner in the name, as `acmehost_starter` — so each plan whose prefix names one of your
resellers is filed under that reseller as plain `starter`, which is what makes it theirs to
edit. Two resellers' `starter` and your own are three different plans; a plan is unique per
owner, not per server. A prefix naming no reseller is left alone, because `gold_plus` on a
server with no reseller called `gold` is a plan somebody named with an underscore.

Three things a cPanel reseller carries have **no equivalent here, and the run names each
one as it files them** rather than dropping them in silence:

| cPanel | What happens |
|---|---|
| The per-reseller **privilege list** (`kill-acct`, `add-pkg`, `create-dns`…) | Reported, not applied. A CorePanel reseller is all-or-nothing *within their own accounts*, so a reseller who could not terminate accounts on cPanel can here — this is the one thing a migration **widens**, which is why the run prints it per reseller |
| A **total bandwidth** ceiling | Reported. CorePanel measures bandwidth per account and enforces no reseller total |
| A restriction on **which packages** they may sell | Reported. CorePanel has no such notion |

The privilege list is the only one of the three that is **kept** rather than just printed.
It is stored on the reseller, and it comes back at the moment it can still change your mind:
the reseller that arrives has no panel login and can do nothing at all, so nothing widens
until you give it one. The day you run `corepanel admin create … --seller acmehost`, that
command answers with what the source allowed and what this login grants instead. Afterwards,
`corepanel seller show acmehost` prints the set again — see
[the CLI reference](https://www.corepanel.net/docs/cli/#giving-an-imported-reseller-its-first-login-prints-a-note).
A reseller cPanel granted *everything* is not mentioned, because nothing was withheld from
them to begin with.

The reseller also ends up with **two logins where cPanel gave them one**: an administrator
login for the panel, and their hosting account's own credentials for their own websites.
cPanel made those the same username; here they are two objects.

**Not carried, and the check says so beforehand:** the undeliverable remainder of the
Exim queue · traffic statistics and old web logs, which restart at zero · mailing lists ·
autoresponders and mail filters · Passenger/Node applications · PostgreSQL · Roundcube
contacts and settings · third-party products (Imunify, JetBackup, Softaculous, LiteSpeed,
CloudLinux) · a small set of `.htaccess` directives the check lists per domain · custom
Apache includes.

**Changed by design:** Apache becomes CorePanel's web server, Exim becomes Postfix, EA4's
PHP becomes CorePanel's PHP stack, and mail and FTP authentication moves to CorePanel's
own broker.

## If your billing system talks to WHM

A cPanel server with a WHMCS behind it is a server *plus* years of billing automation
pointed at the WHM API. PREPARE carries those credentials across — as digests, so the
plaintext never leaves the billing system that has been sending it for years — and leaves
the API **closed**, because :2087 is the most attacked port in shared hosting and opening
it is a decision.

The guided run asks once, after VERIFY. Otherwise:

```bash
corepanel whmcompat enable     # Business edition
corepanel whmcompat selftest   # drives a real WHM API request end to end
```

See [WHMCS](https://www.corepanel.net/docs/whmcs) for what the shim covers.

## The rollback, in detail

The way back is a command, not a document:

```bash
corepanel-transform rollback
```

It works the procedure out **from this server** rather than replaying a file written a
week ago, so it cannot send you after a unit that has since gone. It stops and disables
what CorePanel is holding, puts cPanel's services back — including reinstalling cPanel's
own mail, FTP and DNS packages, whose configuration cPanel then rebuilds itself — and
stops the panel **last**, once cPanel is answering again.

It also refuses when it should. Giving mail and FTP back to cPanel makes cPanel's account
files the truth again, and those stopped being updated at the cutover. If a mailbox was
created, an FTP account added or a password changed under CorePanel since, the rollback
compares both sides, names the difference and stops rather than discard it silently.
`--force` goes ahead and prints exactly what is being given up.

The one thing it cannot compare is DNS, and it says so every time rather than checking it
badly: a zone edited in CorePanel since the cutover is in CorePanel's store, and cPanel
would answer from the copy it had at the cutover.

**The window has no timer.** It closes when you run `cleanup`, and not before.

## After cleanup

`cleanup` removes the cPanel packages, its repositories, `/usr/local/cpanel`,
`/var/cpanel` (archived to a tarball first — it is a few megabytes of text and the only
remaining answer to what this server was) and the licence. It also clears two things
nobody would think to look for: cPanel's global `dnf` exclusion, which hides every
CorePanel PHP package behind what looks like a broken repository, and the
`/usr/local/bin/php` that has just gone with cPanel's PHP CLI — every cPanel-style cron
line reading `php script.php` would otherwise have no php at all, so cleanup puts it back
pointing at CorePanel's.

It deliberately leaves cPanel's own service database users alone. Nothing announced them,
and a `DROP USER` is not undoable.

Three pieces of ordinary server setup are left to you afterwards, because each one is a
visible change the transformation had no business making on its own:

- the **FTP passive port range**, which is CorePanel's and not cPanel's — check it against
  your firewall
- the [**host firewall**](https://www.corepanel.net/docs/security/host-firewall) and, if you want them, disk
  quotas at the filesystem level
- **SELinux** back to enforcing, if the server was not already

## When something goes wrong

| | |
|---|---|
| The run was interrupted | `corepanel-transform` again, or `corepanel-transform resume`. State is on disk after every step. |
| A step failed | It says which one, and what is still standing. Every step before it is recorded as done: running again re-enters that step and no other. |
| You want to see what happened | `corepanel-transform status` |
| A site serves but behaves oddly | Almost always `.htaccess`. The check listed the directives per domain before you started. |
| You want out | `corepanel-transform rollback`, any time until `cleanup`. |

The subcommands are all still there for automation and for anyone who already knows the
order — `check`, `plan`, `run`, `resume`, `status`, `rollback`, `cleanup` — and they take
the same flags and ask the same words as the guided run.

## What changed, and when

Every release that touched the tool is listed in the
[Transform changelog](https://www.corepanel.net/docs/transform-changelog), with what it fixed and what it means for
a server transformed before it. Upgrade before you start rather than mid-run:

```bash
corepanel-transform version
curl -fsSL https://get.corepanel.net/check | sh -s -- --install
```
