A step-by-step, battle-tested guide to migrating years of email from Zimbra 8.8.x on CentOS 7 to Carbonio Community Edition on Ubuntu 24.04 — with zero data loss.
If you're running Zimbra Open Source Edition on CentOS 7, you're sitting on two ticking time bombs. CentOS 7 reached end-of-life in June 2024 — no more security patches, no more updates. And Zimbra's open-source edition? Synacor stopped releasing it after version 8.8.15. Zimbra 9 and 10 are commercial-only, requiring paid licenses.
This guide documents a real production migration we performed: moving a multi-domain mail server with ~30 accounts and 3.5GB of mail from Zimbra 8.8.11 on CentOS 7 to Carbonio Community Edition on Ubuntu 24.04. Every command in this guide was run on a live system. Every gotcha documented here was encountered and solved in real time.
Who this guide is for: System administrators running Zimbra OSE who need a migration path that preserves all email, passwords, and configurations without requiring a commercial license.
What is Carbonio CE? Carbonio Community Edition is a free, open-source email and collaboration platform built by Zextras — the same team behind the popular Zextras Suite for Zimbra. It's the direct spiritual successor to Zimbra OSE, built on the same underlying stack (Postfix, OpenLDAP, Jetty), with a modern web interface and active development.
Zimbra OSE is dead. The last open-source release was 8.8.15. If you want Zimbra 9 or 10, you need a Network Edition license — roughly $960/year for every 25 mailboxes. For a small hosting company or organization running 30-50 accounts, that's an ongoing cost for something that used to be free.
CentOS 7 is dead. No security patches since June 2024. Every day your mail server runs on it is a day you're exposed to unpatched vulnerabilities.
Carbonio CE is the natural successor. Same CLI tools (zmprov, zmmailbox), same directory structure (/opt/zextras/), same LDAP backend. If you know Zimbra, you already know 90% of Carbonio. The migration path is well-documented, and the community is growing as Zimbra OSE users make the switch.
Before touching anything, document what you have. On your existing Zimbra server, run these as the zimbra user (use the -l flag to bypass any SSL issues with expired certificates):
# List all domains
zmprov -l getAllDomains
# List all accounts
zmprov -l gaa
# Export all accounts with password hashes
zmprov -l gaa -v | grep -E "^(# name|userPassword:)" > /opt/zimbra/backup/passwords_full.txt
# Export aliases
zmprov -l gaa -v | grep -E "^(# name|zimbraMailAlias)" > /opt/zimbra/backup/aliases.txt
# Export distribution lists
zmprov -l gadl > /opt/zimbra/backup/distlists.txt
# Check mailstore size
du -sh /opt/zimbra/store/
# Verify IMAP is enabled
zmprov -l gs $(hostname) zimbraImapServerEnabled
Save all of this output somewhere safe — off the server. This is your migration blueprint.
Operating System: Carbonio CE officially supports Ubuntu 22.04 and Ubuntu 24.04. Rocky Linux works but isn't officially supported and may require manual dependency fixes. We strongly recommend Ubuntu 24.04 LTS for the smoothest installation.
Migration Method: We'll use imapsync — it works over the IMAP protocol, is version-agnostic, and allows you to run migrations while the old server is still live. It's resumable, incremental, and never deletes anything on either side by default.
Hostname Strategy: Use a temporary hostname (e.g., webmail.yourdomain.com) for the new server during setup and testing. Keep the old server running on the production hostname until you're ready to cut over.
Start with a fresh Ubuntu 24.04 LTS Server installation. Ensure it has a public IP address, at least 4GB RAM, and sufficient storage for your mailstore plus room to grow.
hostnamectl set-hostname mail.example.com
Carbonio requires a clean hosts file with no IPv6 entries:
cat > /etc/hosts << EOF
127.0.0.1 localhost
203.0.113.10 mail.example.com mail
EOF
Verify:
hostname -f # Should return: mail.example.com
hostname -i # Should return: 203.0.113.10
locale | grep LANG
Must show en_US.UTF-8. If not, configure it before proceeding.
If you're on a VM and want to prevent cloud-init from resetting your network configuration:
touch /etc/cloud/cloud-init.disabled
Zextras repository:
wget -O- "https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x5dc7680bc4378c471a7fa80f52fd40243e584a21" | gpg --dearmor | sudo tee /usr/share/keyrings/zextras.gpg > /dev/null
sh -c 'echo "deb [arch=amd64 signed-by=/usr/share/keyrings/zextras.gpg] https://repo.zextras.io/release/ubuntu noble main" > /etc/apt/sources.list.d/zextras.list'
PostgreSQL 16 repository:
echo "deb https://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list
wget -O- "https://www.postgresql.org/media/keys/ACCC4CF8.asc" | gpg --dearmor | sudo tee /usr/share/keyrings/postgres.gpg > /dev/null
chmod 644 /usr/share/keyrings/postgres.gpg
sed -i 's/deb/deb [signed-by=\/usr\/share\/keyrings\/postgres.gpg] /' /etc/apt/sources.list.d/pgdg.list
Update and verify:
apt update
apt-cache search carbonio | head -5
apt-cache search postgresql-16 | head -3
apt install -y postgresql-16
systemctl enable postgresql
systemctl start postgresql
# Generate a secure password
PGPASS="$(openssl rand -base64 24)"
echo "Save this PostgreSQL password: $PGPASS"
# Create the Carbonio database
su - postgres -c "psql --command=\"CREATE ROLE carbonio_adm WITH LOGIN SUPERUSER PASSWORD '$PGPASS';\""
su - postgres -c "psql --command=\"CREATE DATABASE carbonio_adm owner carbonio_adm;\""
su - postgres -c "psql --command=\"ALTER SYSTEM SET listen_addresses TO '*';\""
su - postgres -c "psql --command=\"ALTER SYSTEM SET max_connections = 500;\""
su - postgres -c "psql --command=\"ALTER SYSTEM SET shared_buffers = 5000;\""
echo "host all all 0.0.0.0/0 md5" >> /etc/postgresql/16/main/pg_hba.conf
systemctl restart postgresql
Important: Save the PostgreSQL password. You'll need it during the Carbonio bootstrap.
apt install -y \
service-discover-server \
carbonio-directory-server \
carbonio-proxy \
carbonio-webui \
carbonio-mta \
carbonio-appserver \
carbonio-storages-ce \
carbonio-message-broker \
carbonio-user-management
carbonio-bootstrap
This interactive process will detect your domain from the hostname and present a configuration menu. Go into option 1 (Common Configuration) to set the admin password. Save it. Then press y to apply.
service-discover setup-wizard
Enter your server's IP with subnet mask (e.g., 203.0.113.10/30) and create a cluster credentials password. Save this password.
pending-setups -a
From our experience, several packages are needed that aren't always pulled in automatically:
apt install -y carbonio-catalog carbonio-memcached carbonio-admin-console-ui carbonio-webui
pending-setups -a
Important: The carbonio-admin-console-ui and carbonio-admin-ui packages conflict with each other. If you install carbonio-admin-ui by mistake, it will remove carbonio-admin-console-ui and your admin panel sidebar will be empty. If this happens:
apt install -y carbonio-admin-console-ui
After installing the correct UI packages, prevent them from being auto-removed:
apt-mark manual carbonio-admin-login-ui carbonio-auth-ui carbonio-calendars-ui carbonio-contacts-ui carbonio-login-ui carbonio-mails-ui carbonio-search-ui carbonio-shell-ui
On Ubuntu 24.04, Carbonio services are managed via systemd, not zmcontrol:
systemctl list-units 'carbonio-*' --type=service --state=running
You should see 30+ services running. Access the admin panel at https://your-server-ip:6071 with the admin credentials you set during bootstrap.
On the new Carbonio server, add each domain from your old server (the default domain is created during bootstrap):
su - zextras -c "zmprov cd yourdomain.com"
su - zextras -c "zmprov cd anotherdomain.com"
Create all user accounts (skip system accounts like galsync, spam, ham, virus-quarantine):
su - zextras << 'EOF'
zmprov ca user@yourdomain.com 'TempPass123!'
zmprov ca support@yourdomain.com 'TempPass123!'
zmprov ca billing@yourdomain.com 'TempPass123!'
EOF
This is critical — it allows users to keep their existing passwords. Copy the passwords_full.txt file from the old server to the new server, then run:
while IFS= read -r line; do
if [[ "$line" =~ ^"# name "(.+) ]]; then
acct="${BASH_REMATCH[1]}"
elif [[ "$line" =~ ^"userPassword: "(.+) ]]; then
hash="${BASH_REMATCH[1]}"
echo "Setting password for $acct"
su - zextras -c "zmprov ma '$acct' userPassword '$hash'" 2>/dev/null
fi
done < /tmp/passwords_full.txt
System accounts that don't exist on the new server will silently fail — that's expected and harmless.
Based on your alias export, create them on the new server:
su - zextras -c "zmprov aaa user@yourdomain.com alias@yourdomain.com"
Without display names, outgoing emails may show generic names in recipients' inboxes:
su - zextras -c "zmprov ma user@yourdomain.com displayName 'John Smith'"
imapsync isn't in the Ubuntu 24.04 repositories. Install it manually:
apt install -y libauthen-ntlm-perl libcgi-pm-perl libcrypt-openssl-rsa-perl \
libdata-uniqid-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 libhtml-parser-perl \
libjson-webtoken-perl libmail-imapclient-perl libparse-recdescent-perl \
libproc-processtable-perl libmodule-scandeps-perl libreadonly-perl \
libregexp-common-perl libsys-meminfo-perl libterm-readkey-perl \
libtest-mockobject-perl libtest-pod-perl libunicode-string-perl \
liburi-perl libwww-perl libtest-nowarnings-perl libtest-deep-perl \
libtest-warn-perl make cpanminus
cpanm JSON::WebToken
wget -O /usr/local/bin/imapsync https://raw.githubusercontent.com/imapsync/imapsync/master/imapsync
chmod +x /usr/local/bin/imapsync
imapsync --version
Before migrating, increase the maximum message size to avoid errors with large emails:
su - zextras -c "zmprov mcf zimbraMtaMaxMessageSize 52428800"
su - zextras -c "zmprov mcf zimbraFileUploadMaxSize 52428800"
su - zextras -c "zmprov mcf zimbraMailContentMaxSize 52428800"
Run a dry test on a single account first:
imapsync --host1 OLD_SERVER_IP --ssl1 --sslargs1 SSL_verify_mode=0 \
--authuser1 admin@mail.example.com --user1 user@example.com --password1 'ADMIN_PASS' \
--host2 localhost --ssl2 --sslargs2 SSL_verify_mode=0 \
--authuser2 zextras@example.com --user2 user@example.com --password2 'NEW_ADMIN_PASS' \
--dry --addheader
The --dry flag means no data is copied — it just tests authentication. If both sides authenticate, you're ready.
cat > /root/migrate.sh << 'SCRIPT'
#!/bin/bash
ACCOUNTS="
user@yourdomain.com
support@yourdomain.com
billing@yourdomain.com
"
for acct in $ACCOUNTS; do
echo "========================================"
echo "Syncing: $acct"
echo "Started: $(date)"
echo "========================================"
imapsync --host1 OLD_SERVER_IP --ssl1 --sslargs1 SSL_verify_mode=0 \
--authuser1 admin@mail.example.com --user1 "$acct" --password1 'ADMIN_PASS' \
--host2 localhost --ssl2 --sslargs2 SSL_verify_mode=0 \
--authuser2 zextras@example.com --user2 "$acct" --password2 'NEW_ADMIN_PASS' \
--addheader
echo "Finished: $acct at $(date)"
echo ""
done
echo "ALL ACCOUNTS MIGRATED"
SCRIPT
chmod +x /root/migrate.sh
Always run inside a screen session to survive disconnections:
screen -S migration
bash /root/migrate.sh 2>&1 | tee /root/migration.log
Detach with Ctrl+A then D. Reattach with screen -r migration.
imapsync deserves special mention for anyone nervous about migrating years of email:
After migration completes, check for errors:
grep -i "Detected [1-9]" /root/migration.log
Common errors you'll see are "maximum message size exceeded" for messages larger than the default 10MB limit. If you set the size limit to 50MB as shown above and re-run the script, imapsync will pick up only the failed messages.
apt install -y certbot
Stop nginx temporarily for the standalone challenge:
systemctl stop carbonio-nginx.service
certbot certonly --standalone -d mail.example.com -d mail.anotherdomain.com
Carbonio's certificate verification requires the full chain including the root CA:
wget -O /opt/zextras/ssl/carbonio/isrg-root-x1.pem https://letsencrypt.org/certs/isrgrootx1.pem
DOMAIN="mail.example.com"
cp /etc/letsencrypt/live/$DOMAIN/privkey.pem /opt/zextras/ssl/carbonio/commercial/commercial.key
cp /etc/letsencrypt/live/$DOMAIN/cert.pem /opt/zextras/ssl/carbonio/commercial/commercial.crt
cat /etc/letsencrypt/live/$DOMAIN/chain.pem /opt/zextras/ssl/carbonio/isrg-root-x1.pem > /opt/zextras/ssl/carbonio/commercial/commercial_ca.crt
chown zextras:zextras /opt/zextras/ssl/carbonio/commercial/*
Verify the certificate chain:
su - zextras -c "/opt/zextras/bin/zmcertmgr verifycrt comm \
/opt/zextras/ssl/carbonio/commercial/commercial.key \
/opt/zextras/ssl/carbonio/commercial/commercial.crt \
/opt/zextras/ssl/carbonio/commercial/commercial_ca.crt"
Deploy it:
su - zextras -c "/opt/zextras/bin/zmcertmgr deploycrt comm \
/opt/zextras/ssl/carbonio/commercial/commercial.crt \
/opt/zextras/ssl/carbonio/commercial/commercial_ca.crt"
Restart services:
systemctl restart carbonio-proxy.target carbonio-mta.target carbonio-appserver.target
Create a deploy hook so certificates are automatically deployed to Carbonio when renewed:
cat > /etc/letsencrypt/renewal-hooks/deploy/carbonio-deploy.sh << 'SCRIPT'
#!/bin/bash
DOMAIN="mail.example.com"
cp /etc/letsencrypt/live/$DOMAIN/privkey.pem /opt/zextras/ssl/carbonio/commercial/commercial.key
cp /etc/letsencrypt/live/$DOMAIN/cert.pem /opt/zextras/ssl/carbonio/commercial/commercial.crt
cat /etc/letsencrypt/live/$DOMAIN/chain.pem /opt/zextras/ssl/carbonio/isrg-root-x1.pem > /opt/zextras/ssl/carbonio/commercial/commercial_ca.crt
chown zextras:zextras /opt/zextras/ssl/carbonio/commercial/*
su - zextras -c "/opt/zextras/bin/zmcertmgr deploycrt comm \
/opt/zextras/ssl/carbonio/commercial/commercial.crt \
/opt/zextras/ssl/carbonio/commercial/commercial_ca.crt"
systemctl restart carbonio-proxy.target carbonio-mta.target carbonio-appserver.target
SCRIPT
chmod +x /etc/letsencrypt/renewal-hooks/deploy/carbonio-deploy.sh
Test the renewal:
certbot renew --dry-run
If your mail server is publicly accessible (and it is), brute force attacks against IMAP, POP3, and SMTP authentication are inevitable. We've seen competitors deliberately brute-force login attempts to trigger account lockouts on Zimbra servers — disabling legitimate access.
apt install -y fail2ban
Create the Carbonio filter:
cat > /etc/fail2ban/filter.d/carbonio.conf << 'EOF'
[Definition]
failregex = \[ip=;\] account - authentication failed for .* \(no such account\)$
\[ip=;\] security - cmd=Auth; .* error=authentication failed for .*, invalid password;$
;oip=;.* security - cmd=Auth; .* protocol=soap; error=authentication failed for .* invalid password;$
\[oip=;.* SoapEngine - handler exception: authentication failed for .*, account not found$
WARN .*;ip=;ua=CarbonioWebClient .* security - cmd=AdminAuth; .* error=authentication failed for .*;$
;oip=;.* security - cmd=AdminAuth; .* error=authentication failed for .*
zm lookup: .* user not found:.* client: :\d+, server:
An error occurred in mail zmauth: .* client: :\d+, server:
NOQUEUE: reject: RCPT from .*\[\]: 550 5.1.1 .*: Recipient address rejected:
ignoreregex =
EOF
Create the jails:
cat > /etc/fail2ban/jail.d/carbonio.conf << 'EOF'
[carbonio]
enabled = true
filter = carbonio
backend = polling
logpath = /opt/zextras/log/audit.log
/opt/zextras/log/nginx.log
/var/log/mail.log
maxretry = 5
bantime = 3600
findtime = 600
action = iptables-multiport[name=carbonio, port="25,80,110,143,443,465,587,993,995,6071", protocol=tcp]
[carbonio-smtp]
enabled = true
filter = carbonio
backend = polling
logpath = /var/log/mail.log
maxretry = 3
bantime = 7200
findtime = 600
action = iptables-multiport[name=carbonio-smtp, port="25,465,587", protocol=tcp]
EOF
Critical: The backend = polling line is essential. On Ubuntu 24.04, fail2ban's default inotify backend does not properly monitor Carbonio's log files. Without backend = polling, your jails will load but never detect any failures. This cost us significant debugging time.
Start fail2ban:
systemctl enable fail2ban
systemctl restart fail2ban
fail2ban-client status
You should see three jails: sshd, carbonio, and carbonio-smtp. When an IP gets banned from the carbonio jail, it's blocked on all mail ports simultaneously (25, 80, 110, 143, 443, 465, 587, 993, 995, and 6071).
If you have service accounts that need to connect reliably (like a billing system pulling email via POP3), create a COS that disables password lockout. Combined with fail2ban blocking brute force at the IP level, this gives you both security and reliability:
su - zextras -c "zmprov cc ServiceAccountCOS zimbraPasswordLockoutEnabled FALSE"
Get the COS ID and assign it:
# Get the COS ID
su - zextras -c "zmprov gc ServiceAccountCOS | grep zimbraId"
# Assign to service accounts
su - zextras -c "zmprov ma serviceaccount@example.com zimbraCOSId YOUR_COS_ID"
Before switching over, prepare all DNS records:
A Record:
mail.example.com → YOUR_NEW_SERVER_IP
MX Record (usually doesn't need changing if it already points to mail.example.com):
example.com MX 10 mail.example.com
SPF Record:
v=spf1 mx a ip4:YOUR_NEW_SERVER_IP/32 -all
PTR (Reverse DNS):
YOUR_NEW_SERVER_IP → mail.example.com
DKIM: Generate on the new server:
su - zextras -c "/opt/zextras/libexec/zmdkimkeyutil -a -d example.com"
This outputs a TXT record to add to DNS. Remove any old DKIM records from the previous server.
DMARC:
_dmarc.example.com TXT "v=DMARC1; p=quarantine; rua=mailto:admin@example.com; ruf=mailto:admin@example.com; fo=1; pct=100; adkim=s; aspf=s"
Send a test email to Gmail and check the original message headers:
SPF: PASS with IP YOUR_NEW_SERVER_IP
DKIM: 'PASS' with domain example.com
DMARC: 'PASS'
All three should show PASS. If they do, your migration is complete.
Symptom: Outgoing email fails with "451 4.7.1 Service unavailable - try again later." The mail log shows amavis processes starting and immediately exiting.
Cause: Carbonio's bundled libssl.so.3 requires OpenSSL 3.3.0 symbols, but Ubuntu 24.04's system libcrypto.so.3 only provides 3.0.x. The version mismatch causes amavis to crash.
Fix: Create a systemd override to use Carbonio's own OpenSSL libraries:
systemctl edit carbonio-mailthreat.service
Add between the comment markers:
[Service]
Environment="LD_LIBRARY_PATH=/opt/zextras/common/lib"
Then:
systemctl daemon-reload
systemctl restart carbonio-mailthreat.service
Verify amavis is staying alive:
systemctl status carbonio-mailthreat.service
You can confirm the fix was necessary by checking:
strings /opt/zextras/common/lib/libcrypto.so.3 | grep OPENSSL_3.3.0
Symptom: amavisd refuses to start with "Config file should not be owned by EUID 996."
Fix: Amavis has a security check that refuses to load a config owned by the same user running it. The config must be owned by root:
chown root:zextras /opt/zextras/conf/amavisd.conf
Symptom: You can log into the admin panel but the page shows an error.
Cause: Usually the carbonio-catalog package is not installed, or there's a conflict between carbonio-admin-ui and carbonio-admin-console-ui.
Fix:
apt install -y carbonio-catalog carbonio-admin-console-ui
pending-setups -a
systemctl restart carbonio-nginx.service
Symptom: The admin panel loads and you can log in, but the sidebar has no navigation items and the CREATE button doesn't work.
Cause: The carbonio-admin-console-ui package was removed when carbonio-admin-ui was installed (they conflict).
Fix:
apt install -y carbonio-admin-console-ui
Then mark UI packages to prevent auto-removal:
apt-mark manual carbonio-admin-login-ui carbonio-auth-ui carbonio-calendars-ui \
carbonio-contacts-ui carbonio-login-ui carbonio-mails-ui \
carbonio-search-ui carbonio-shell-ui
Symptom: Some messages fail to transfer with "BAD maximum message size exceeded."
Fix: Increase the message size limit before migration:
su - zextras -c "zmprov mcf zimbraMtaMaxMessageSize 52428800"
su - zextras -c "zmprov mcf zimbraFileUploadMaxSize 52428800"
su - zextras -c "zmprov mcf zimbraMailContentMaxSize 52428800"
Re-run the migration script — imapsync only transfers what's missing.
Symptom: fail2ban-client status carbonio shows the jail is active but Total failed: 0 despite known failed login attempts in the logs.
Cause: On Ubuntu 24.04, fail2ban's default inotify file monitoring backend doesn't properly watch Carbonio's log files.
Fix: Add backend = polling to the jail configuration:
[carbonio]
enabled = true
filter = carbonio
backend = polling
...
Symptom: zmcontrol status runs but shows no services.
Cause: On Ubuntu 24.04, Carbonio uses systemd exclusively for service management. The legacy zmcontrol start/stop/restart commands are not available.
Fix: Use systemd instead:
systemctl list-units 'carbonio-*' --type=service --state=running
After your cutover is complete, work through this checklist:
Migrating from Zimbra OSE to Carbonio CE is not just feasible — with the right approach, it's straightforward. The combination of imapsync for safe, incremental email transfer and Carbonio's Zimbra-compatible CLI tools means the learning curve is minimal.
The hardest part isn't the migration itself — it's the decision to stop procrastinating and do it. Your CentOS 7 box isn't getting any younger, and every day without patches is a liability.
What we covered in this guide:
The entire migration for our ~30-account, 3.5GB setup was completed in a single working session. Larger deployments will take proportionally longer for the imapsync phase, but the process is identical.
Need infrastructure for your email server migration? SwissLayer provides managed hosting solutions with enterprise-grade network security, including Cisco ACL-protected environments, private VLANs, and Swiss data privacy standards. Whether you're migrating from Zimbra or building from scratch, we've got the infrastructure to support it.