Moving websites from one #ISPConfig server to another is fairly straightforward, but email deserves a little more care. You do not want to switch servers only to discover that years of old messages, sent mail, folders, or attachments were left behind.

I recently had to retire an older ISPConfig server running #Ubuntu 22.04 and Apache and move the important email accounts to a newer ISPConfig server running Ubuntu 26.04 and Nginx.

The important thing to understand is that Apache versus Nginx has very little to do with the email migration itself. ISPConfig commonly handles email through #postfix and #Dovecot. What we really need to migrate is the contents of the #IMAP mailboxes.

For this job I used imapsync. It allowed me to copy the mail directly from one Dovecot server to another while leaving the original mailbox completely intact.

Why I Chose imapsync

There are several ways to migrate mail between servers. You could copy the contents of /var/vmail directly, migrate ISPConfig databases, or attempt to move the entire server configuration.

That was unnecessary in my case because I only had a handful of email accounts that actually needed their old mail preserved. The rest could simply start with empty mailboxes on the new server.

Using IMAP had several advantages:

  • The old server remains untouched.
  • The destination server can already be in production.
  • Mailbox folders are copied automatically.
  • Read and unread status can be retained.
  • Sent, Trash, Junk, Drafts, and custom folders can be transferred.
  • The process can be safely run multiple times.
  • Only new or changed messages need to be synchronized during later runs.

The Server Configuration

My old ISPConfig mail server was:

server.oldexample.com
Ubuntu 22.04
Apache
Postfix
Dovecot

The new server was:

server.newexample.com
Ubuntu 26.04
Nginx
Postfix
Dovecot

The important part is that both servers provide IMAP access.

You Only Need imapsync on One Machine

One useful thing about imapsync is that it does not need to be installed on both mail servers.

I installed it only on the new server.

OLD SERVER
Dovecot / IMAP
      |
      |
      v
   imapsync
running on the
 NEW SERVER
      |
      v
NEW Dovecot / IMAP

The old server simply behaves like any other remote IMAP server.

First Check Dovecot on the New Server

Before attempting any migration, make sure Dovecot is actually listening.

ss -ltnp | grep -E ':993|:143'

Normally you should see listeners for ports 993 and 143.

In my case, nothing appeared.

Checking Dovecot revealed the problem:

systemctl status dovecot --no-pager -l

Dovecot was installed but had failed to start.

The error was:

Fatal: Error in configuration file /etc/dovecot/dovecot.conf:
The first setting must be dovecot_config_version

The server had Dovecot 2.4 installed, while ISPConfig had previously generated configuration intended for an older Dovecot version.

Do Not Manually Hack the Dovecot Configuration

Instead of manually modifying individual Dovecot configuration directives, I updated ISPConfig and allowed ISPConfig to regenerate the service configuration correctly.

I ran:

ispconfig_update.sh

During the ISPConfig update I chose:

Reconfigure Permissions in master database? yes

Reconfigure Services? yes

ISPConfig Port [8080]:
Press ENTER

Create new ISPConfig SSL certificate? no

Reconfigure Crontab? yes

The important option for the Dovecot repair was:

Reconfigure Services? yes

ISPConfig then regenerated the configuration for:

  • Postfix
  • Dovecot
  • SpamAssassin
  • Rspamd
  • BIND
  • Pure-FTPd
  • Nginx
  • Database services
  • Other ISPConfig-managed services

Verify Dovecot After the ISPConfig Update

First validate the configuration:

doveconf -n >/dev/null
echo $?

A result of:

0

means Dovecot considers the configuration valid.

Then:

systemctl status dovecot --no-pager -l

I now had:

Active: active (running)

Finally:

ss -ltnp | grep -E ':993|:143'

Dovecot was now listening on:

0.0.0.0:993
0.0.0.0:143

[::]:993
[::]:143

At that point the new mail server was ready.

Installing imapsync on Ubuntu

I installed imapsync on the new Ubuntu server rather than modifying the old server.

First install the required Perl modules and utilities:

apt update

apt-get install -y \
libauthen-ntlm-perl \
libclass-load-perl \
libcrypt-openssl-rsa-perl \
libcrypt-ssleay-perl \
libdata-uniqid-perl \
libdigest-hmac-perl \
libdist-checkconflicts-perl \
libencode-imaputf7-perl \
libfile-copy-recursive-perl \
libfile-tail-perl \
libio-compress-perl \
libio-socket-inet6-perl \
libio-socket-ssl-perl \
libio-tee-perl \
libjson-webtoken-perl \
libmail-imapclient-perl \
libmodule-scandeps-perl \
libnet-dbus-perl \
libnet-dns-perl \
libnet-ssleay-perl \
libpar-packer-perl \
libproc-processtable-perl \
libreadonly-perl \
libregexp-common-perl \
libsys-meminfo-perl \
libterm-readkey-perl \
libtest-fatal-perl \
libtest-mock-guard-perl \
libtest-mockobject-perl \
libtest-pod-perl \
libtest-requires-perl \
libtest-simple-perl \
libunicode-string-perl \
liburi-perl \
libtest-nowarnings-perl \
libtest-deep-perl \
libtest-warn-perl \
make time cpanminus

Before installing a large group of packages on a production server, it is a good idea to simulate the installation first:

apt-get --simulate install PACKAGE_NAMES_HERE

I specifically checked that the proposed installation showed:

0 upgraded
0 to remove

That meant the installation was adding dependencies rather than replacing important ISPConfig components.

Download imapsync

cd /root

wget -N https://imapsync.lamiral.info/imapsync

chmod 755 imapsync

Test it:

/root/imapsync --version

My installed version reported:

2.324

I then installed it into the local system path:

cp /root/imapsync /usr/local/bin/imapsync

chmod 755 /usr/local/bin/imapsync

Verify:

imapsync --version

Create the Mailbox on the New ISPConfig Server First

Before copying any mail, create the destination mailbox through ISPConfig.

For example:

max@example.com

The account must exist on both servers before imapsync can log into them.

The passwords do not have to be identical.

Keep Passwords Out of Shell History

Instead of putting passwords directly into the imapsync command, I loaded them into environment variables.

For the old mailbox:

read -rsp "OLD mailbox password: " IMAPSYNC_PASSWORD1
echo
export IMAPSYNC_PASSWORD1

For the new mailbox:

read -rsp "NEW mailbox password: " IMAPSYNC_PASSWORD2
echo
export IMAPSYNC_PASSWORD2

The password does not appear while typing and does not become part of the command stored in shell history.

Perform a Dry Run First

Before copying actual mail, I tested one mailbox:

imapsync \
  --host1 server.oldexample.com \
  --user1 'max@example.com' \
  --ssl1 \
  --host2 server.newexample.com \
  --user2 'max@example.com' \
  --ssl2 \
  --sslargs2 SSL_verify_mode=0 \
  --automap \
  --justfolders \
  --dry

The options:

--justfolders
--dry

allow us to verify authentication and folder mapping without transferring the messages.

The test successfully detected folders such as:

Drafts
INBOX
Junk
Sent
Trash

It also correctly mapped:

Trash  -> Trash
Junk   -> Junk
Sent   -> Sent
Drafts -> Drafts

Perform the Real Migration

Once the dry run succeeded, I removed:

--justfolders
--dry

The actual command became:

imapsync \
  --host1 server.oldexample.com \
  --user1 'max@example.com' \
  --ssl1 \
  --host2 server.newexample.com \
  --user2 'max@example.com' \
  --ssl2 \
  --sslargs2 SSL_verify_mode=0 \
  --automap

My first test mailbox contained 1,081 messages.

The final imapsync report stated:

Final difference host2 - host1 : 0 messages, 0 bytes

The sync looks good,
all 1081 identified messages in host1 are on host2.

Detected 0 errors

Exiting with return value 0

That is exactly what we want to see.

The Old Mailbox Is Not Deleted

Nothing in the command above removes messages from the old server.

After the migration:

OLD SERVER
1,081 messages

NEW SERVER
1,081 messages

This makes the process considerably safer because the original server remains a backup until the migration is completely finished.

It Is Okay if the New Server Has Extra Messages

Another mailbox produced:

Final difference host2 - host1 : 1 messages, 906 bytes

The sync looks good,
all 38 identified messages in host1 are on host2.

The sync is not strict,
there are 1 among 39 identified messages in host2
that are not on host1.

Detected 0 errors

This is not a migration failure.

It simply means the destination mailbox already contained one message that did not exist on the old server.

imapsync may suggest using:

--delete2

to make the destination an exact mirror of the source.

I deliberately did not do that.

For a migration, preserving mail is usually more important than forcing both servers to be identical. Using --delete2 could delete legitimate destination-only messages.

Clear the Passwords When Finished

unset IMAPSYNC_PASSWORD1
unset IMAPSYNC_PASSWORD2

Migrating Multiple Mailboxes

Once the first mailbox proved that the process worked, I created a script for the remaining accounts.

nano /root/migrate-remaining-mail.sh

Here is the script:

#!/usr/bin/env bash

OLD_HOST="server.oldexample.com"
NEW_HOST="server.newexample.com"

MAILBOXES=(
    "user1@example.com"
    "user2@example.com"
    "user3@example.com"
)

LOGDIR="/root/mail-migration-logs"

mkdir -p "$LOGDIR"
chmod 700 "$LOGDIR"

SUCCESS=0
FAILED=0

for EMAIL in "${MAILBOXES[@]}"; do

    echo
    echo "============================================================"
    echo "Migrating: $EMAIL"
    echo "============================================================"
    echo

    read -rsp "OLD password for $EMAIL: " OLD_PASSWORD
    echo

    read -rsp "NEW password [press ENTER if same]: " NEW_PASSWORD
    echo

    if [[ -z "$NEW_PASSWORD" ]]; then
        NEW_PASSWORD="$OLD_PASSWORD"
    fi

    export IMAPSYNC_PASSWORD1="$OLD_PASSWORD"
    export IMAPSYNC_PASSWORD2="$NEW_PASSWORD"

    SAFE_NAME="${EMAIL//@/_}"
    SAFE_NAME="${SAFE_NAME//./_}"

    LOGFILE="$LOGDIR/${SAFE_NAME}.log"

    imapsync \
        --host1 "$OLD_HOST" \
        --user1 "$EMAIL" \
        --ssl1 \
        --host2 "$NEW_HOST" \
        --user2 "$EMAIL" \
        --ssl2 \
        --sslargs2 SSL_verify_mode=0 \
        --automap \
        --logfile "$LOGFILE"

    RESULT=$?

    unset IMAPSYNC_PASSWORD1
    unset IMAPSYNC_PASSWORD2
    unset OLD_PASSWORD
    unset NEW_PASSWORD

    if [[ $RESULT -eq 0 ]]; then
        echo "SUCCESS: $EMAIL"
        ((SUCCESS++))
    else
        echo "FAILED: $EMAIL"
        echo "Check:"
        echo "$LOGFILE"
        ((FAILED++))
    fi

    read -rp "Press ENTER to continue to the next mailbox..."

done

echo
echo "============================================================"
echo " MIGRATION FINISHED"
echo "============================================================"

echo "Successful: $SUCCESS"
echo "Failed:     $FAILED"
echo
echo "Logs:"
echo "$LOGDIR"

Make it executable:

chmod 700 /root/migrate-remaining-mail.sh

Then run:

/root/migrate-remaining-mail.sh

Why Running imapsync Again Is Important

One of the best features of this method is that the migration can be run repeatedly.

Suppose the first migration copies:

10,000 messages

The old server continues operating for another two days and receives:

25 new messages

Running imapsync again does not blindly create another 10,025 copies.

It compares the mailboxes and synchronizes what is missing.

A later run might therefore transfer only those 25 new messages.

This allows a very safe migration strategy:

Initial bulk migration
        |
        v
Verify mailboxes
        |
        v
Change DNS
        |
        v
Allow DNS propagation
        |
        v
Run imapsync again
        |
        v
Catch remaining messages
        |
        v
Retire old server

Testing the New Mailbox With Thunderbird

Before changing public DNS, I tested the migrated mailbox using Thunderbird.

The incoming server was temporarily changed from:

server.oldexample.com

to:

server.newexample.com

The IMAP settings were:

Protocol: IMAP
Port: 993
Connection Security: SSL/TLS
Authentication: Normal password
Username: full@email-address.com

This makes it easy to visually inspect:

  • Inbox messages
  • Sent mail
  • Trash
  • Junk
  • Drafts
  • Attachments
  • Read and unread messages

DNS Settings for the New Mail Server

Moving the messages is only part of the migration. DNS must eventually direct new mail to the new server.

For example, my new mail server IP was:

75.41.70.194

Typical DNS records include:

server.example.com   A   75.41.70.194
mail.example.com A 75.41.70.194
smtp.example.com A 75.41.70.194
imap.example.com A
75.41.70.194

When using Cloudflare, mail-related hostnames should normally remain:

DNS only

rather than being placed behind the standard Cloudflare proxy.

MX Record

The domain also needs an MX record directing incoming email to the new server.

For example:

Type: MX
Name: @
Server: server.example.com
Priority: 10

The MX target should be a hostname, not an IP address.

SPF

A basic SPF record might look like:

v=spf1 a mx ip4:75.41.70.194 ~all

During the migration, if both servers may still legitimately send mail, both IP addresses can temporarily be authorized:

v=spf1 a mx ip4:NEW_SERVER_IP ip4:OLD_SERVER_IP ~all

Once the old server is retired, remove the old IP.

DKIM

DKIM requires special attention during a server migration.

The public DKIM key published in DNS must correspond to the private DKIM key being used by the new mail server.

For example:

key1._domainkey.example.com

If the DNS record contains the old server's public key while the new server signs messages using a different private key, DKIM authentication will fail.

I therefore compared the DKIM record in DNS with the key configured by ISPConfig on the new server.

DMARC

During migration I prefer a monitoring policy:

v=DMARC1; p=none;

This allows authentication problems to be identified without immediately asking receiving mail systems to reject messages.

Once SPF and DKIM have been thoroughly verified, a stricter policy can be considered.

Reverse DNS / PTR

Do not forget reverse DNS.

If the mail server is:

server.example.com
75.41.70.194

you ideally want:

server.example.com
        |
        v
75.41.70.194

75.41.70.194
        |
        v
server.example.com

The first is the normal A record.

The second is the PTR or reverse-DNS record.

PTR records are normally configured through the hosting provider that owns the IP address rather than through Cloudflare.

My Final Migration Procedure

  1. Create the destination mail domains in ISPConfig.
  2. Create the destination mailboxes.
  3. Verify Dovecot is running on the new server.
  4. Install imapsync on the new server.
  5. Perform a dry run on one mailbox.
  6. Perform the real mailbox synchronization.
  7. Verify the mailbox in Thunderbird or another IMAP client.
  8. Synchronize the remaining important mailboxes.
  9. Configure MX, SPF, DKIM, DMARC, A records, and PTR.
  10. Change DNS to direct mail to the new server.
  11. Leave the old server running temporarily.
  12. Run imapsync again to catch messages received during DNS propagation.
  13. Verify incoming and outgoing mail.
  14. Only then retire the old mail server.

Do Not Shut Down the Old Mail Server Immediately

Even after changing DNS, some remote mail systems may temporarily continue using cached DNS information.

That means email can still arrive on the old server for a period of time.

Keeping the old server online and performing another imapsync pass gives you a simple way to recover those messages.

Final Thoughts

Migrating email does not have to mean copying raw mail directories, manipulating ISPConfig database tables, or taking the mail system offline.

For a small or medium number of important mailboxes, using IMAP synchronization is a very controlled approach.

The original mailbox remains intact, the destination can be tested before DNS is changed, and the process can be repeated as many times as necessary until the final cutover is complete.

The most important lesson is to treat the migration as a synchronization rather than a one-time copy:

Copy
Verify
Switch DNS
Synchronize again
Verify again
Retire the old server

That small amount of overlap can be the difference between a clean migration and discovering later that important email disappeared during the transition.