Slot 42 Migration field notes
Cloudways to cPanel Migration: The Complete Manual Guide
Cloudways ships no cPanel-compatible backup, so this move is manual: rsync the app, mysqldump over SSH, rebuild configs and pull mailboxes out of Rackspace.
There is no automated path from Cloudways to cPanel. Cloudways is a managed stack, not a control panel in the cPanel sense, and it produces no cpmove archive and nothing WHM’s Transfer Tool can consume. You build every one of these by hand. The part that catches people is not the files or the database. It is the mail.
Below is the sequence I use. It assumes you have the Cloudways master credentials for the server and root or a reseller account on the destination cPanel box.
Why no tool does this for you
WHM restores accounts from archives produced by /scripts/pkgacct. That format carries the home directory, the MySQL dumps, the DNS zones, the Exim configuration, and the /var/ metadata file all in one structure. Cloudways never generates any of that. Its own backups are server-level snapshots that only restore back into Cloudways.
So you extract three things separately: application files, databases, and mailboxes. The first two live on the Cloudways server. The third does not. That is the part most guides skip.
Step 1: get a clean copy of the application files
You have two options, and I use both on anything I care about.
The platform backup is the safety net. In the Cloudways console, under the application’s Backup and Restore tab, turn on Local Backups and take one manually. That writes an archive into the application’s own directory, so you can pull it with the same SSH credentials you use for everything else.
Then take the working copy over the wire. Find the application path first, because Cloudways does not use /home/<user>:
ssh master_abcdefghij@203.0.113.10
ls -la /home/master/applications/
Each application is a directory named with an opaque application ID, not your domain. Confirm which is which by checking the document root:
for d in /home/master/applications/*/; do
echo "== $d"
ls "$d/public_html" | head -5
done
The real document root is /home/. That is what you copy. Pull it directly to the destination cPanel server so the bytes only move once:
# run this ON the destination cPanel server
rsync -avz --numeric-ids --delete \
-e "ssh -p 22" \
master_abcdefghij@203.0.113.10:/home/master/applications/abcdefghij/public_html/ \
/home/newuser/public_html/
Skip --numeric-ids if you like; the UIDs will not match anyway and you are going to fix ownership at the end. What matters is -a for permissions and timestamps, and running it twice, once before cutover and once during, so the second pass only carries the delta.
Step 2: dump the database
Cloudways does not expose MySQL publicly. The credentials are in the application’s Access Details panel in the console: database name, username, password. They only work from the server itself, so either dump on the source and copy the file down, or open a tunnel.
Dumping on the source is simpler. Put the credentials in an option file rather than in the command. A password given as -p'DBPASSWORD' is visible to every other user on the box in ps auxwwww and lands in your shell history on the way there.
Write that option file with mktemp, not to ~/.my.cnf. There may already be a ~/.my.cnf on that account, holding credentials the platform’s own scripts use, and cat > over it is not something you can undo:
umask 077
CNF=$(mktemp)
cat > "$CNF" <<'EOF'
[client]
user=abcdefghij
password=DBPASSWORD
EOF
mysqldump --defaults-extra-file="$CNF" \
--single-transaction --quick --routines --triggers \
abcdefghij > ~/app.sql
rm -f "$CNF"
gzip ~/app.sql
--defaults-extra-file has to be the first option on the line; MySQL’s client library parses the defaults options before anything else and ignores one that turns up later. A bare -p with no value also works and prompts you instead, which is fine for one command and tiresome inside a loop.
--single-transaction keeps InnoDB tables consistent without locking the site. If any table is still MyISAM, that flag will not protect it, so check before you trust the dump.
If you would rather pull it straight through, tunnel port 3306:
ssh -N -L 3307:127.0.0.1:3306 master_abcdefghij@203.0.113.10 &
mysqldump --single-transaction --quick \
-h 127.0.0.1 -P 3307 abcdefghij | gzip > app.sql.gz
Step 3: build the cPanel account and land the files
Create the account first so the home directory, the UID, and the Apache vhost exist:
whmapi1 createacct \
username=newuser \
domain=example.com \
plan=default \
contactemail=admin@example.com
Then fix ownership on everything rsync just wrote. Skip this and you get 500 errors with nothing helpful in the log, because the PHP handler runs as the account user and cannot read files owned by root:
chown -R newuser:newuser /home/newuser/public_html
Ownership is the part that was actually wrong. Resist the urge to follow it with a blanket chmod across the tree. rsync -a brought the source’s modes over intact, and a full-mode rewrite is not a repair either way it points: chmod 755 on every directory reopens the 0700 ones somebody locked deliberately, and chmod 644 on every file republishes a 0660 config to the whole server. Fix the specific problem you found and normalise nothing.
The specific problem worth fixing after a copy is group or world writability, on directories every bit as much as on files. Look at it before you touch it:
find /home/newuser/public_html \( -type d -o -type f \) -perm /go=w \
-printf '%m %p\n' | head -50
chmod go-w takes off exactly those bits and nothing else. Owner bits stay where they were, the executable bit survives on CGI and shell scripts, and a 0700 directory has no group or world write bit in the first place, so it never appears in the list and never gets touched:
find /home/newuser/public_html \( -type d -o -type f \) -perm /go=w \
-exec chmod go-w {} +
Secrets are the one place a fixed mode is the right answer, because there is no legitimate reason for anyone but the owner to read them:
find /home/newuser -maxdepth 4 -type f \( -name '.env' -o -name 'wp-config.php' \) \
-exec chmod 600 {} +
If the server runs SELinux or you have moved files between filesystems, restore contexts as well:
/scripts/restorecon_home newuser 2>/dev/null || restorecon -R /home/newuser
Step 4: import the database
Create the database and user through UAPI so cPanel’s own mapping tables stay correct. Creating them directly in MySQL leaves them invisible in the account’s interface:
uapi --user=newuser Mysql create_database name=newuser_wp
uapi --user=newuser Mysql create_user name=newuser_wp password='StrongPassHere'
uapi --user=newuser Mysql set_privileges_on_database \
user=newuser_wp database=newuser_wp privileges='ALL PRIVILEGES'
Then load the dump:
zcat /root/app.sql.gz | mysql newuser_wp
Step 5: rewrite the application config
The database name, user, and password all changed. So did the absolute paths, because Cloudways used /home/ and cPanel uses /home/.
For WordPress:
cd /home/newuser/public_html
sudo -u newuser wp config set DB_NAME newuser_wp
sudo -u newuser wp config set DB_USER newuser_wp
sudo -u newuser wp config set DB_PASSWORD 'StrongPassHere'
sudo -u newuser wp config set DB_HOST localhost
Cloudways installs Redis and Varnish by default and many stacks there carry an object-cache drop-in and a Varnish purge plugin. Neither has anything to talk to on a stock cPanel box:
rm -f /home/newuser/public_html/wp-content/object-cache.php
sudo -u newuser wp plugin deactivate breeze varnish-http-purge 2>/dev/null
For Laravel or anything reading .env, edit /home/ and then clear the compiled config, which otherwise keeps serving the old credentials:
sudo -u newuser php artisan config:clear
sudo -u newuser php artisan cache:clear
Finally, hunt for hardcoded paths that no longer resolve:
grep -rIl "/home/master/applications" /home/newuser/public_html | head -50
Cron entries are the usual offender. Cloudways cron jobs live in the platform console, not in a crontab you can copy, so open Application Settings → Cron Job Management and transcribe them by hand into crontab -u newuser -e. Rewrite both the PHP binary path and the script path while you are in there.
Step 6: the email problem
Cloudways has no built-in email hosting. None. Outgoing SMTP is an add-on relay, and actual mailboxes are sold as a paid Rackspace Email add-on. So the mail you are migrating is not on the Cloudways server at all. It is on Rackspace’s IMAP servers, and it will stay there after your DNS cutover unless you move it.
The sequence is: create the mailboxes in cPanel first, copy the mail while both sides are live, then flip MX.
Put every password this step needs into its own file first, one line each, owner-readable only. umask 077; mkdir -p / gets you the right modes without thinking about it, and the Rackspace side goes in info.src, the new cPanel side in info.dst.
uapi --user=newuser Email add_pop \
email=info domain=example.com password="$(cat /root/mailmig/secrets/info.dst)" quota=2048
email takes the local part when domain is supplied, which is the pairing cPanel’s own example uses.
Be clear about what that $(cat ...) does and does not buy you, because it is easy to talk yourself into a safety you have not got. The shell expands the substitution before it executes uapi, so the password is in the process arguments regardless, and UAPI’s documented command line is parameter-value pairs only: there is no stdin or input-file mode to route around it. For the length of that one call the password sits in /proc/<pid>/cmdline, readable by anyone with a shell on the server. What the file genuinely gives you is a password that is not in your shell history, not in the terminal scrollback, and not in the script you are about to commit somewhere.
Close the window instead of denying it. Create the mailboxes while the destination is still yours alone, before a single customer has shell access on it, which on a server you built for this migration is the normal state anyway. If it is a live shared box with other people’s shells on it, assume the password was seen and rotate it at cutover rather than handing this one to the user. On CloudLinux the problem goes away properly, because CageFS gives every user a /proc view containing only their own processes. A stock cPanel box does not.
Then sync each mailbox from Rackspace into cPanel with imapsync. Rackspace’s IMAP endpoint is secure on port 993:
imapsync \
--host1 secure.emailsrvr.com --port1 993 --ssl1 \
--user1 info@example.com --passfile1 /root/mailmig/secrets/info.src \
--host2 mail.example.com --port2 993 --ssl2 \
--user2 info@example.com --passfile2 /root/mailmig/secrets/info.dst \
--automap
--passfile1 and --passfile2 read the password from the first line of a mode-600 file, which is what the imapsync manual asks for: --password1 puts the string into the process arguments, where ps auxwwww hands it to anyone with a shell on the box.
There is no --skipcrossduplicates here on purpose. It drops a message that has already been copied into some other folder, which is the right call coming out of Gmail, where one message with three labels shows up in three folders. Rackspace has no labels. A message sitting in two Rackspace folders is in two folders because someone put it there, and the flag would silently keep only one copy.
Run it once before cutover and once after, so anything delivered to Rackspace during the DNS propagation window still lands in the new mailbox. The full procedure, including the folder-mapping cases where --automap guesses wrong, is in the imapsync email migration runbook.
Get a real mailbox inventory from the Rackspace control panel before you start. Nobody remembers the three forwarding-only addresses until they stop working.
Step 7: DNS cutover
Lower the TTL on the records you are about to change at least 24 hours ahead. If the zone is sitting at 14400 seconds, dropping it to 300 on the morning of the migration does nothing, because resolvers still hold the old value for another four hours.
example.com. 300 IN A 198.51.100.25
www.example.com. 300 IN A 198.51.100.25
example.com. 300 IN MX 0 mail.example.com.
mail.example.com. 300 IN A 198.51.100.25
Change A records and MX in the same window if mail is moving with the site. Leave the TTL low for another day or two, then put it back.
Step 8: SSL
Do not try to move certificates. Once the A record resolves to the cPanel server, AutoSSL will issue and install on its own, and you can force the run instead of waiting for the nightly cycle:
/usr/local/cpanel/bin/autossl_check --user=newuser
tail -f /var/cpanel/logs/autossl/*.log
If AutoSSL fails, it is almost always because a subdomain in the account still points at Cloudways and the DCV check cannot complete. Fix the DNS or remove the subdomain from the account.
Verification checklist
| Check | Command or action |
|---|---|
| Site loads on the new IP | curl -I --resolve example |
| Correct DB is in use | sudo -u newuser wp db check |
| No leftover Cloudways paths | grep -rIl "/ |
| Ownership is clean | find / |
| Mail sends | Send from webmail, check headers for the new server |
| Mail receives | Send in from an external address after MX flips |
| Mailbox counts match | Compare imapsync’s final totals per folder |
| Cron jobs exist | crontab -u newuser -l |
| SSL is valid | openssl s_client -connect example |
| Old server still up | Keep Cloudways running 7 days before you destroy it |
Keep the source alive until you have watched a full billing or business cycle run on the new box. I have never regretted paying for a week of overlap.
FAQ
Can I use WHM’s Transfer Tool against a Cloudways server?
No. The Transfer Tool needs root SSH on the source and expects cPanel to be installed there. Cloudways gives you a master user, not root, and there is no cPanel to talk to. Manual is the only route.
How do I find the application ID for my domain?
The Cloudways console shows it under the application’s Access Details, and it matches the directory name under /home/. If you have SSH only, grep the vhost or check which public_html contains the site you recognise.
Will my Cloudways staging site come across?
Only if you copy it deliberately. Staging applications are separate applications with their own IDs and their own databases. Treat each one as its own migration or drop it.
Do I have to move email at all if the client is happy with Rackspace?
No, and sometimes that is the right call. Leave the MX records pointing at Rackspace and migrate only the site. Just be explicit about it, because the client will otherwise assume email moved with everything else and will keep paying two bills without knowing why.
What about the Cloudways SMTP add-on for outgoing mail?
Applications configured to send through an external relay will keep trying to reach it. Either update the SMTP plugin settings to use the cPanel server’s local mail, or point them at whatever relay the client keeps.
Where this fits
Cloudways to cPanel is a hand-built migration every time, and the mail leg is a second project inside the first. I do these end to end as server migration work, including the Rackspace mailbox sync and the DNS window. If you are heading in the other direction between managed platforms, the RunCloud and Cloudways email migration notes cover the same mail gap from the other side.