> ## Content Index
> Fetch the complete content index at: https://blog.sudowl.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# Server Migration: Build a Full Test Copy Before You Touch DNS
- URL: https://blog.sudowl.com/server-migration-test-copy-before-dns/
- Published: 2026-11-02T10:00:00.000Z
- Updated: 2026-11-05T17:38:13.000Z
- Description: Moving a forum, WordPress, Nextcloud and mail off a dead Plesk box: pull with ssh -A, test via hosts file, doveadm imapc for mail, then the delta cutover.
- Author: Lukas
- Tags: Linux, Ubuntu

I had two projects left on an old Plesk box with a dead license. One is a community forum I run on XenForo. The other is a small client with WordPress, a Nextcloud and three mailboxes. Both had to move to my new server with KeyHelp.

I didn't want a "move it and pray" weekend. So I built a complete copy on the new box first, tested it from my laptop with a hosts file, and wrote down the cutover steps. The copy is done and tested. The DNS switch is still waiting on a domain transfer, so this post ends with the checklist, not with "it went fine".

**TL;DR:** Pull everything from the new server with `ssh -A`, so nothing gets written on the old box. Files with rsync, databases with a `mysqldump | mysql` pipe, mail with Dovecot's `doveadm` and the imapc backend. Import the old Let's Encrypt certs so HTTPS works in the test. Test with hosts file entries. Nextcloud's `occ` won't start with a broken DB config, so fix `config.php` with a small PHP script. For the cutover: maintenance mode, final delta, DNS, cron, stop Postfix on the old box.

## The layout on the new box

One KeyHelp client account per project: one for the forum, one for the client. Inside each account one database per app: WordPress, Nextcloud, XenForo. Separate accounts mean separate system users, so the forum can't read the client's files and the other way round. More about the panel in [KeyHelp as a free Plesk alternative](/keyhelp-free-plesk-alternative/).

The old box still serves everything. The Plesk CLI refuses to work without a license, but SSH, the web server, MariaDB and mail keep running. That's enough to get data out. What still works in that state is in [Plesk license expired: what still works](/plesk-expired-license-what-still-works/).

## Pull, don't push

```bash
# on my laptop
ssh -A root@203.0.113.20          # new box, agent forwarding on

# on the new box, pulling from the old one
rsync -aH --info=progress2 \
  root@203.0.113.10:/var/www/vhosts/forum.example.com/httpdocs/ \
  /home/users/forum/www/forum.example.com/

```

`-A` forwards my SSH agent. The new box can log in to the old box with the key on my laptop, and no private key ever lands on either server. Everything runs from the new side, so the old box only gets read. If the copy goes wrong, I delete it on the new box and start over. The old box doesn't care.

After rsync, fix the owner to the KeyHelp account's system user. Files copied as root and owned by root give you 403s and broken uploads.

## Databases with a pipe

```bash
ssh root@203.0.113.10 \
  'mysqldump -uadmin -p"$(cat /etc/psa/.psa.shadow)" --single-transaction forumdb' \
  | mysql -u forum_xf -p forum_xf

```

The dump streams straight into the new database. No dump file on the old disk, no copy step. `--single-transaction` gives a consistent snapshot for InnoDB tables without locking the site.

Then point each app at its new database. For XenForo that's `src/config.php`, for WordPress `wp-config.php`. Nextcloud is where it got annoying.

## Gotcha: occ can't fix the config that stops occ

The normal way to change Nextcloud settings is `occ config:system:set`. But `occ` boots Nextcloud first, and booting means connecting to the database from `config.php`. The copied config still had the old DB host, name, user and password. So `occ` crashed before it could do anything. You can't use the tool to fix the thing that stops the tool from starting.

The fix: edit `config.php` with a tiny PHP script. It loads the file, changes the values and writes it back.

```php
<?php
// fix-nc-config.php, run with: php fix-nc-config.php
$file = '/home/users/client/www/cloud.client.example.com/config/config.php';
require $file;   // defines $CONFIG

$CONFIG['dbhost']        = 'localhost';
$CONFIG['dbname']        = 'client_nc';
$CONFIG['dbuser']        = 'client_nc';
$CONFIG['dbpassword']    = 'new-db-password';
$CONFIG['datadirectory'] = '/home/users/client/nextcloud-data';
$CONFIG['trusted_domains'] = ['cloud.client.example.com'];

file_put_contents($file, "<?php\n\$CONFIG = " . var_export($CONFIG, true) . ";\n");

```

Run it as the account's user, or fix the owner afterwards. Keep a copy of the original `config.php` next to it. `datadirectory` matters as much as the DB values. If it still points to the old path, Nextcloud starts but can't find any files. After that `occ` worked again.

I keep this script. It has to run again after the final sync, because the final rsync brings the old `config.php` back.

## Mail with doveadm and imapc

My last mail move used imapsync in Docker, see [moving mailboxes with imapsync](/migrate-mailboxes-imapsync-outlook-autodiscover/). There's no imapsync package in Ubuntu 24.04\. This time I skipped Docker. The new box already runs Dovecot, and `doveadm` can pull from any IMAP server with its imapc backend.

General shape, per mailbox:

```bash
doveadm -o imapc_host=203.0.113.10 \
        -o imapc_port=993 \
        -o imapc_ssl=imaps \
        -o imapc_ssl_verify=no \
        -o imapc_user=office@client.example.com \
        -o imapc_password='old-password' \
        -o mail_prefetch_count=20 \
        sync -1 -R -u office@client.example.com imapc:

```

`-u` is the local mailbox on the new server. `imapc:` is the remote side, configured by the `-o` options. `-R` reverses the direction, so it pulls from the remote into the local mailbox. The exact option names changed between Dovecot versions, so check `doveadm --version` and the docs for your version. I use `sync -1`, a one-way sync that only adds what's missing and doesn't delete anything on the target. That's why I can run it again at cutover for the delta. `backup` would make the target an exact copy and wipe anything extra. `imapc_ssl_verify=no` is only there because the old box's cert didn't match the IP I connect to. If you connect by hostname with a valid cert, leave it on.

If the old server's cert doesn't match the name or IP you connect to, the SSL check fails. Connect by the hostname the cert was issued for.

Afterwards I compared message counts per folder on both sides. They matched exactly, plus one on the new side. That one is the welcome mail KeyHelp drops into every new mailbox. Good to know before you panic about a mystery message.

```bash
doveadm mailbox status -u office@client.example.com messages '*'

```

## Bring the old certificates along

Testing via hosts file only works over HTTPS if the new server has a valid cert for the name. Let's Encrypt can't issue one yet, because the domain still points to the old box. So I copied the existing certs and keys from the old server and imported them into KeyHelp as custom certificates.

Now the browser sees a valid cert for `forum.example.com` on the new IP. No warnings, no "accept risk" clicking, and you test the real thing including redirects and HSTS. After the DNS switch I replace them with KeyHelp's own Let's Encrypt certs. The old ones expire anyway.

## Test through the hosts file

On my Windows laptop, `C:\Windows\System32\drivers\etc\hosts`, edited as admin:

```text
203.0.113.20  forum.example.com
203.0.113.20  client.example.com
203.0.113.20  www.client.example.com
203.0.113.20  cloud.client.example.com
203.0.113.20  mail.client.example.com

```

Then `ipconfig /flushdns` and close the browser fully, because browsers keep their own DNS cache. Only my laptop sees the new server. Everyone else still uses the old one.

What I tested:

- forum: browse, log in, open threads, check attachments and avatars load
- WordPress: front page, login, admin
- Nextcloud: login, files there, open a few
- mail: log in to a mailbox on the new server, check folders, send a test mail

When you're done, comment the lines out. Forgetting a hosts entry is a fun way to lose an hour after the real switch.

## The cutover checklist

This part hasn't run yet. It's what I'll do once the domain transfer is through.

**Days before**

1. [Lower the TTL](/lower-dns-ttl-before-migration/) to 300 for every record that moves. Wait for the old TTL to run out.
2. Write down every cron job on the old box for these accounts. That list becomes the new box's crontab later.

**Cutover**

1. Forum: switch "Board active" off in the XenForo admin, so nobody posts into a database that's about to be copied.
2. Nextcloud on the old box into maintenance mode:

```bash
cd /path/to/nextcloud
sudo -u www-data php occ maintenance:mode --on

```

Use the user that owns the Nextcloud files. On a plain Ubuntu install that's `www-data`. On Plesk and KeyHelp it's the account's own system user.

1. Final database dumps with the same pipe as above, into the existing new databases.
2. Final rsync. Same command, it only copies what changed, so it's fast.
3. Run the Nextcloud config script again. The rsync just brought the old `config.php` back.
4. Final mail sync with doveadm for all three mailboxes.
5. Switch the A and AAAA records to the new server.
6. Enable the cron jobs on the new box from the prepared list.
7. Stop Postfix on the old box. Once MX points to the new server, there must be no second place that accepts mail for the domain. Otherwise some sender with a stale cache delivers to the old box and nobody ever reads it.
8. Turn the board back on, maintenance mode off on the new Nextcloud, test again without the hosts file.
9. Replace the imported certs with KeyHelp's Let's Encrypt.

Keep the old box running for a while after. If something's missing, it's still there to pull from.

## Why I do it this way

The test copy is most of the work. Once it runs, the cutover is a list of steps I already did once: dump, rsync, config fix, mail sync. Nothing on the night is new, which is the whole point. And if the test copy shows a problem, like the Nextcloud config thing, I find it on a quiet afternoon and not with the forum offline.

If the old server is on its way out anyway, back up the new one before you switch. I use [plain rsync backups](/rsync-backup-linux/) for that. And if half your data is in one backup and half in another, [this restore story](/restore-site-files-and-database-from-different-backups/) is what happens when you skip it.

---

### Related posts

- [KeyHelp: The Free Plesk Alternative I Moved To (and Its API Traps)](https://blog.sudowl.com/keyhelp-free-plesk-alternative/)
- [Running PHP 7.4, 8.3 and 8.4 Side by Side on One Server](https://blog.sudowl.com/multiple-php-versions-one-server/)
- [systemd Timers Instead of cron](https://blog.sudowl.com/systemd-timers-instead-of-cron/)