Skip to content

Migrate WordPress from IONOS to cPanel: the manual move and cutover

IONOS runs its own control panel instead of cPanel, so this guide shows you how to migrate WordPress from IONOS to a cPanel/WHM server by hand, file by file and table by table. You will finish with the site and every mailbox proven on the new server before a DNS record moves.

This guide is for the admin or developer moving a WordPress site off IONOS shared or Managed WordPress hosting onto a cPanel/WHM server they control. IONOS runs its own control panel rather than cPanel, so no cPanel-format account archive, called a cpmove file, exists for WHM’s Transfer Tool to restore. The short answer is that you copy the files and the database by hand, rebuild the account and every mailbox on cPanel, and move DNS last. Mail is the part that most often hurts the client, because IONOS Mail is a separate product that stays behind when the files and the database leave. By the end the site serves from your own server, tested on its real domain name before a single DNS record changes.

What you’ll accomplish

  • A confirmed answer to where WordPress actually lives on IONOS, which is often a subfolder rather than the top of the home directory.
  • A complete copy of the files and the database on your cPanel account, checked rather than assumed.
  • A wp-config.php pointing at the local database, because IONOS gives the database a hostname of its own instead of localhost.
  • Every mailbox and forwarder rebuilt on cPanel before the MX records move. MX means mail exchanger, the DNS record naming the server that receives a domain’s mail.
  • A cutover you can rehearse, with the site answering on its real domain name before the first DNS change.

Prerequisites

On the IONOS side you need the login for the contract that owns the hosting, not only a WordPress administrator account. Every IONOS plan offers SFTP, which is file transfer over an SSH connection, and most Linux hosting plans also offer SSH, a remote shell on the server. Confirm SSH by connecting rather than by reading a feature list.

You also need the database credentials. IONOS reaches MariaDB or MySQL through phpMyAdmin, and through the mysqldump client where SSH exists. Write down the database hostname exactly as wp-config.php shows it, because on IONOS it is usually a hostname of its own rather than localhost.

On the destination you need a cPanel account for the domain, a MySQL database and database user, SSH access, and wp on the box. wp is WP-CLI, the WordPress command-line tool, documented in the WP-CLI command reference.

Match the PHP version before you copy anything. Read it from the IONOS panel’s PHP setting for that domain, then set the same one in cPanel’s MultiPHP Manager, which drives EA4. EA4 is EasyApache 4, the build system supplying the PHP versions a cPanel server offers. A site on PHP 8.2 landing on an account that defaults to 7.4 fails in ways that look like a broken upload.

Finally, list every mailbox, forwarder and autoresponder on the domain while the panel is open. You will use that list twice.

How it works

A migration moves two things that travel well and three that do not. The files and the database copy across with ordinary tools. Mailboxes, DNS records and the TLS certificate each have to be rebuilt on the other side. TLS is transport layer security, the encryption behind an https address.

Diagram showing which parts of an IONOS site the file and database copy carries to cPanel, and which parts stay behind and have to be rebuilt

Nothing restores itself here, and the control panel is why. IONOS built its own, so there is no cPanel account archive to hand to WHM. You get a file manager, phpMyAdmin, SFTP credentials and, on most Linux plans, SSH.

Managed WordPress adds one more layer. That product runs caching above WordPress and ships plugins that talk to the platform, and on your server they call services your account no longer has. Step 7 takes them out.

Step-by-step

Everything before the freeze in step 11 is rehearsal you can repeat. Everything after it is live.

  1. Find where WordPress lives, and read its credentials. IONOS often gives each site a subfolder, so confirm the path instead of guessing:

    pwd
    ls -1
    wp core version
    ls -1 wp-content/mu-plugins/
    wp config get DB_HOST
    wp config get DB_NAME
    wp config get DB_USER

    pwd prints the real document root, commonly shaped like /homepages/NN/dNNNNNNNNN/htdocs with a per-site folder below it. Keep that string, because a plugin may have stored it in the database. ls -1 should show wp-admin, wp-content and wp-includes side by side, and if it does not you are one directory too high. Watch DB_HOST: anything but localhost means the database sits on a separate IONOS host, and step 6 changes it. Without SSH, read the domain’s target folder in the panel and open wp-config.php in the file manager.

  2. Export the database. With SSH and WP-CLI:

    wp db export ionos.sql --add-drop-table

    Expect Success: Exported to 'ionos.sql'. With SSH but no WP-CLI, use the client directly with the values from step 1:

    mysqldump -h DBHOST -u DBUSER -p DBNAME --single-transaction --default-character-set=utf8mb4 --add-drop-table > ionos.sql

    It asks Enter password: and prints nothing else when it works. Either way, check the tail:

    tail -3 ionos.sql

    The last line should read -- Dump completed. A truncated dump imports without complaint and quietly loses the end of your largest table. Without SSH, use phpMyAdmin: pick the database, choose Export, then Custom, and tick the option that adds DROP TABLE statements.

  3. Package the files. From the document root you confirmed in step 1:

    ls -a | grep htaccess
    tar -czf ~/ionos-files.tar.gz wp-content wp-config.php .htaccess
    ls -lh ~/ionos-files.tar.gz

    Run the grep first. Managed WordPress sites often have no .htaccess, and tar stops with Cannot stat: No such file or directory if you name a missing file. tar is silent on success, so check the size ls -lh reports against the disk usage the panel shows. Core files stay behind, because step 5 downloads a clean copy.

  4. Build the cPanel side before you copy anything. Create the account in WHM, then the database and database user in cPanel’s MySQL Database Wizard. Set the PHP version in MultiPHP Manager to the one from the prerequisites. Then create every mailbox, forwarder and autoresponder from your list, keeping the same local part. Empty mailboxes cost nothing while MX still points at IONOS, and they are what keeps mail from bouncing later.

  5. Move the bytes. Pull rather than push, running these on the cPanel server as the account user:

    wp core download --version=WPVERSION --skip-content --path=/home/CPUSER/public_html
    rsync -avz -e 'ssh -p 22' IONOSUSER@IONOSHOST:DOCROOT/wp-content/ /home/CPUSER/public_html/wp-content/
    scp -P 22 IONOSUSER@IONOSHOST:ionos.sql IONOSUSER@IONOSHOST:wp-config.php /home/CPUSER/

    Use the version step 1 printed, and the host and port the IONOS SSH panel shows, which is 22 unless it says otherwise. --skip-content leaves wp-content empty so rsync fills it. rsync ends with a sent ... received ... summary, and scp prints a progress line per file ending at 100%. Never run either as root, or the ownership problem in Troubleshooting becomes yours. With SFTP only:

    echo 'get -r DOCROOT ionos-files' | sftp -b - IONOSUSER@IONOSHOST

    That prints a Fetching ... line per directory and exits quietly.

  6. Import the database and point wp-config.php at localhost. Put the old wp-config.php in the document root first, then correct it:

    cd /home/CPUSER/public_html
    wp db import /home/CPUSER/ionos.sql
    wp config set DB_HOST localhost
    wp config set DB_NAME CPUSER_wp
    wp config set DB_USER CPUSER_wpuser
    wp config set DB_PASSWORD 'THEPASSWORDYOUSET'
    wp db check

    The import answers Success: Imported from '/home/CPUSER/ionos.sql'. Each wp config set answers with the constant and its new value, and wp db check prints one line per table ending in OK. Leave the IONOS hostname in place and the site keeps reading the old database across the network, which looks like a working migration until the contract ends.

  7. Remove the platform code Managed WordPress left behind. List what is there before deleting anything, because the set differs by account age:

    ls -1 wp-content/mu-plugins/
    wp plugin list --fields=name,status
    ls -l wp-content/object-cache.php wp-content/advanced-cache.php
    grep -n "WP_CACHE" wp-config.php

    Anything named for the host, or for its assistant or performance tooling, belongs to the platform you are leaving. Remove each by name:

    wp plugin deactivate PLUGINNAME
    wp plugin delete PLUGINNAME
    rm -f wp-content/object-cache.php wp-content/advanced-cache.php
    wp config delete WP_CACHE

    Expect Success: Deactivated 1 of 1 plugins. then Success: Deleted 1 of 1 plugins. The rm clears drop-in caching files pointing at services this server does not run. wp config delete WP_CACHE answers with the constant it removed, and you skip it if the grep found nothing.

  8. Fix stored paths, and URLs only if the domain changes. When the domain stays the same, the site URL needs no rewriting, and a search-replace on it churns rows for nothing. The absolute path is different, because some plugins store it. Replace the string pwd gave you in step 1, dry run first:

    wp search-replace '/homepages/NN/dNNNNNNNNN/htdocs' '/home/CPUSER/public_html' --all-tables --dry-run --report-changed-only
    wp search-replace '/homepages/NN/dNNNNNNNNN/htdocs' '/home/CPUSER/public_html' --all-tables --precise

    The dry run prints per-table counts and a closing Success: N replacements to be made line. --all-tables walks tables a plugin created under a different prefix, which the default scan skips. --precise makes WP-CLI unserialise and re-serialise in PHP instead of using raw SQL, which keeps serialised arrays valid when a replacement changes a string’s length. Run the same pair on the old and new domain only if the domain is changing.

  9. Copy the mail down while IONOS still holds it. The mailboxes from step 4 are empty and MX still points at IONOS, so both sides are readable at once. imapsync copies messages over IMAP, the protocol a mail client uses to read mail kept on the server:

    imapsync --host1 IONOSIMAPHOST --user1 you@example.com --password1 OLDPASS --host2 localhost --user2 you@example.com --password2 NEWPASS

    Take IONOSIMAPHOST from the incoming server name on the IONOS mail settings page. It finishes with a transfer summary and a line reading Detected 0 errors. For two or three mailboxes, adding both accounts to a desktop mail client and dragging folders across works as well.

  10. Prove the site on its real name before DNS moves. On your workstation, add a line to /etc/hosts reading NEWIP example.com www.example.com. Your browser then reaches the new server while the rest of the world still sees IONOS. curl can do the same for a single request:

    curl -sI --resolve example.com:80:NEWIP http://example.com/ | head -1

    Expect HTTP/1.1 200 OK, or a 301 if the site redirects to https. Then get the certificate issued. AutoSSL is cPanel’s free certificate service, and it proves you control the domain through DCV, or domain control validation. HTTP validation cannot pass while the domain still resolves to IONOS, so use the DNS method and add the TXT record it asks for in the IONOS DNS panel. The cPanel documentation portal covers those choices. Then retest over TLS:

    uapi SSL installed_hosts | grep -i domains
    curl -sI --resolve example.com:443:NEWIP https://example.com/ | head -1

    The apex domain, www, and every subdomain the site serves must appear in the first output. The second should return HTTP/2 200 with no certificate warning.

  11. Lower the TTL, freeze, sync the delta, then move the records. TTL is time to live, the number of seconds a resolver may keep a cached answer. Lower the A, AAAA and MX records to 300 at least 24 hours before the flip, in whichever panel holds the zone. For most of these domains that panel is IONOS, because IONOS is also the registrar. Check what resolvers hand out:

    dig +noall +answer example.com @1.1.1.1
    dig +noall +answer MX example.com @1.1.1.1

    The second column of each answer line is the TTL in seconds, and it has to read 300 before you go further. Then put the IONOS site into maintenance mode so no order, comment or form entry lands after your final export. Re-run steps 2, 3 and 5 to catch the delta, re-import, and move MX first, then A and AAAA.

    Timeline of the IONOS to cPanel cutover, from building the account and mailboxes through validating on a temporary hostname, lowering the TTL, moving MX and A records, verifying, and decommissioning last

    Finish by flushing the rules the old host shaped:

    wp rewrite flush --hard
    wp cache flush

    Expect Success: Rewrite rules flushed. and Success: The cache was flushed. The --hard form writes the WordPress block into .htaccess as well as the copy in the database, which matters when the site arrived without an .htaccess at all.

Validate it works

About an hour after the flip, check from inside the account:

wp option get siteurl
wp option get home
wp db check
wp core verify-checksums

Both options must return the final domain, every table must report OK, and checksums should print Success: WordPress installs verify against checksums. Then put your twenty busiest URLs in a file and sweep them:

while read -r u; do code=$(curl -o /dev/null -s -w '%{http_code}' "$u"); echo "$code $u"; done < top-urls.txt

Every line should read 200, or 301 where you redirect on purpose. Then confirm resolution and mail routing from a public resolver:

dig +short example.com @1.1.1.1
dig +short MX example.com @1.1.1.1
curl -sI https://example.com/ | head -1

The first must return your cPanel server’s address, the second your intended mail host, and the third HTTP/2 200 with no certificate error. Send a real message to each mailbox and reply from it, then exercise mail the site sends: a password reset, a contact form, an order confirmation. Watch the error log and Search Console for 72 hours before you touch the IONOS contract.

Troubleshooting

The site loads, but edits on the new server never appear. wp-config.php still carries the IONOS database hostname, so WordPress reads and writes the old database across the network. Run wp config get DB_HOST. If it answers anything but localhost, redo step 6 and re-import: content added since the import lives in the old database.

The import stops on an unknown collation. A dump written by a newer MySQL carries a collation the destination does not know. Rewrite it before importing:

sed -i 's/utf8mb4_0900_ai_ci/utf8mb4_unicode_ci/g' ionos.sql

A warning from a host-named plugin appears above the page. Step 7 missed something in wp-content/mu-plugins/, which WordPress loads on every request with no off switch in the admin screens. Delete the file and reload. Output printed before headers also breaks the Set-Cookie that logs you in, so this often looks like a login failure.

Mail bounces or disappears after cutover. If the mailboxes were not created before MX moved, mail arrives at a server with nowhere to put it. If MX still points at IONOS while the domain lives on your cPanel account, cPanel treats itself as the local mail exchanger and swallows the message. Set the domain to Remote Mail Exchanger in cPanel’s Email Routing screen until MX really moves.

Uploads fail with 403 and WordPress cannot write to wp-content. You copied the files as root, so they belong to root:

chown -R CPUSER:CPUSER /home/CPUSER/public_html
find /home/CPUSER/public_html -type d -exec chmod 755 {} \;
find /home/CPUSER/public_html -type f -exec chmod 644 {} \;

When this is the wrong approach

If the site is leaving Managed WordPress and someone expects the same speed on arrival, measure first. That product serves much of the traffic from a cache above WordPress, so the numbers you remember were recorded with PHP largely bypassed. On a plain cPanel account that layer does not exist until you build it, with LiteSpeed Cache on a LiteSpeed server or a page-caching plugin on Apache.

If the domain’s mail is the business, split the move in two. Migrate the website to cPanel, leave mail at IONOS for now, and set the domain to Remote Mail Exchanger so cPanel stops delivering locally. That takes the riskiest lane out of the cutover.

If IONOS is also the registrar and you intend to transfer the registration, do not do both in the same week. A registrar transfer has its own multi-day window and its own failure modes. Move the hosting, prove it, then start the transfer. Keep the IONOS package paid for at least 30 days after the flip, and clean up the old server after the migration once mail and web are confirmed elsewhere.

If your source is a managed platform with a documented export path rather than IONOS, the work has a different shape. Migrating WordPress from Kinsta to cPanel covers that case.

Running WordPress that earns real money deserves an operator — Stack Harbor runs that stack under Enterprise WordPress; book an infrastructure consult.