So you want to host your own email.

A complete guide to running Mailcow on your own server — from bare VPS to fully operational mail server.

Contents

  1. What is Mailcow?
  2. Server Requirements
  3. DNS Configuration
  4. Server Preparation
  5. Installing Mailcow
  6. TLS / SSL Certificates
  7. Firewall Rules
  8. First Login and Setup
  9. SPF, DKIM, and DMARC
  10. Reverse DNS (PTR)
  11. Spam Filtering
  12. Backups
  13. Monitoring and Maintenance
  14. Troubleshooting
  15. Multiple Domains and Aliases
  16. Sieve Filters
  17. Brute Force Protection
  18. Deliverability with Gmail and Outlook
  19. MTA-STS and DANE
  20. Full-Text Search
  21. Migrating Existing Mail
  22. Restoring from Backup
  23. Performance Tuning

1. What is Mailcow?

Mailcow (stylized mailcow: dockerized) is an open-source mail server suite that bundles every component you need into a set of Docker containers managed by Docker Compose. Instead of painstakingly installing and wiring together Postfix, Dovecot, Rspamd, ClamAV, SOGo, and a dozen other pieces by hand, Mailcow gives you a single docker compose up command that starts the entire stack.

Under the hood you get:

Everything is containerized, which makes upgrades and rollbacks far easier than a traditional bare-metal setup. That said, running a production mail server is a serious operational commitment. This guide walks you through the full setup correctly.

2. Server Requirements

Hardware / VPS specs

ResourceMinimumRecommended
CPU2 vCPU4 vCPU
RAM4 GB8 GB
Disk20 GB SSD50+ GB SSD
OSUbuntu 22.04 LTSUbuntu 22.04 / Debian 12
IPv41 dedicated IP1 dedicated IP
Your server's IP address must not be on any common blacklists before you start. IP blocks are sold and reallocated, and the previous tenant may have been a spammer — this is out of your control. Verify your IP at mxtoolbox.com/blacklists before going further. If it's listed, you'll need to either request a new IP from your provider or work through each blocklist's individual delisting process, which can take days and isn't guaranteed.

Networking requirements

Software prerequisites

3. DNS Configuration

DNS is the most consequential part of email setup. Misconfigured DNS causes mail to be rejected or silently dropped — often without any bounce or error message to the sender. Configure all of the following records before you install Mailcow, and allow up to 24–48 hours for full propagation.

A and AAAA records

Point your mail hostname to the server's IP:

NameTypeValue
mail.yourdomain.comAyour.server.ip.address
mail.yourdomain.comAAAAyour::ipv6 (if available)

MX record

The MX record tells other mail servers where to deliver mail for your domain:

NameTypePriorityValue
yourdomain.comMX10mail.yourdomain.com
MX records must point to a hostname (A/AAAA record), never directly to an IP address. Also ensure TTL is low (300s) while you're setting up so changes propagate quickly.

SPF record

SPF authorizes your server to send mail on behalf of your domain. Add a TXT record on the root of your domain:

NameTypeValue
yourdomain.comTXTv=spf1 mx ~all

The mx mechanism automatically includes whatever is in your MX record. ~all is a soft fail (recommended while testing); change to -all (hard fail) once everything is working.

DMARC record

DMARC tells receivers what to do with mail that fails SPF/DKIM. Start in monitor mode:

NameTypeValue
_dmarc.yourdomain.comTXTv=DMARC1; p=none; rua=mailto:dmarc@yourdomain.com

After a few weeks of reviewing aggregate reports, tighten to p=quarantine then p=reject.

Autoconfig / Autodiscover (optional but recommended)

These records let mail clients (Outlook, Thunderbird) configure themselves automatically:

NameTypeValue
autoconfig.yourdomain.comCNAMEmail.yourdomain.com
autodiscover.yourdomain.comCNAMEmail.yourdomain.com

Verify all records propagate before proceeding:

dig MX yourdomain.com
dig A mail.yourdomain.com
dig TXT yourdomain.com

4. Server Preparation

4.1 System update

apt update && apt upgrade -y
apt install -y curl git openssl python3

4.2 Set the hostname

The server's hostname must match your mail FQDN. This is critical — Postfix uses it in HELO/EHLO handshakes:

hostnamectl set-hostname mail.yourdomain.com

Edit /etc/hosts and ensure the line reads:

127.0.1.1   mail.yourdomain.com mail

Verify:

hostname -f
# should output: mail.yourdomain.com

4.3 Install Docker Engine

Do not use the distro-packaged docker.io. Install from Docker's official repository:

install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
  | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg

echo "deb [arch=$(dpkg --print-architecture) \
  signed-by=/etc/apt/keyrings/docker.gpg] \
  https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
  | tee /etc/apt/sources.list.d/docker.list > /dev/null

apt update
apt install -y docker-ce docker-ce-cli containerd.io \
  docker-buildx-plugin docker-compose-plugin

Verify:

docker --version
docker compose version

4.4 Disable local MTA if present

Ubuntu ships with postfix or exim4 sometimes pre-installed. They will conflict with Mailcow's containerized Postfix on port 25:

systemctl stop postfix exim4 2>/dev/null
systemctl disable postfix exim4 2>/dev/null

4.5 Check for port conflicts

ss -tlnp | grep -E ':25|:80|:443|:465|:587|:993|:995'

Nothing should be listening on those ports before you install Mailcow. If something is, identify and stop it.

4.6 Swap (if you have only 4 GB RAM)

ClamAV alone can use over 1 GB of RAM. Adding swap prevents OOM kills:

fallocate -l 2G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstab

5. Installing Mailcow

5.1 Clone the repository

cd /opt
git clone https://github.com/mailcow/mailcow-dockerized
cd mailcow-dockerized

5.2 Generate the configuration

./generate_config.sh

The script will ask for your mail hostname (e.g. mail.yourdomain.com). It creates mailcow.conf with randomized passwords and a unique installation ID.

5.3 Review mailcow.conf

nano mailcow.conf

Key settings to review:

5.4 Pull images and start

docker compose pull
docker compose up -d

First startup takes several minutes as images download and containers initialize. ClamAV in particular takes 2–5 minutes to load its virus database before it is ready.

Watch progress:

docker compose logs -f

Press Ctrl+C once things settle. Check all containers are running:

docker compose ps

All containers should show Up or healthy. If any are in Restarting, check their logs:

docker compose logs <container-name>

6. TLS / SSL Certificates

Mailcow uses acme.sh to automatically obtain Let's Encrypt certificates. It does this via HTTP-01 challenge on port 80. For this to work:

Check the acme container logs to confirm certificate issuance:

docker compose logs acme-mailcow

You should see a line like:

[...] Certificate issued successfully for mail.yourdomain.com

If it fails (e.g. rate-limited or DNS not yet propagated), the container will retry every hour. You can force a retry:

docker compose restart acme-mailcow
Let's Encrypt rate-limits failed attempts. If you trigger too many failures (e.g. by restarting acme repeatedly before DNS has propagated), you may be blocked for up to an hour. Be patient.

Certificates are stored at data/assets/ssl/. Dovecot and Postfix reload them automatically via a watcher container.

7. Firewall Rules

Use ufw to open only what is needed:

ufw allow 22/tcp       # SSH — do this first or you'll lock yourself out
ufw allow 25/tcp       # SMTP (inbound from other mail servers)
ufw allow 80/tcp       # HTTP (Let's Encrypt + redirect)
ufw allow 443/tcp      # HTTPS (admin UI + autodiscover)
ufw allow 465/tcp      # SMTPS (mail client submission, legacy)
ufw allow 587/tcp      # Submission (mail client submission, modern)
ufw allow 993/tcp      # IMAPS
ufw allow 995/tcp      # POP3S (optional)
ufw enable
Always allow port 22 before enabling ufw. If you enable ufw without allowing SSH, you will lose access to your server immediately.

Verify:

ufw status verbose

8. First Login and Setup

8.1 Admin UI

Navigate to https://mail.yourdomain.com. The default credentials are:

Change the admin password immediately. Go to Access → Edit administrator.

8.2 Add your domain

  1. Go to Configuration → Mail Setup → Domains
  2. Click Add domain
  3. Enter yourdomain.com
  4. Set quotas as appropriate
  5. Click Add domain and restart SOGo

8.3 Add mailboxes

  1. Go to Configuration → Mail Setup → Mailboxes
  2. Click Add mailbox
  3. Fill in the local part (e.g. alice) and set a password
  4. Click Add

The full email address will be alice@yourdomain.com.

8.4 Mail client settings

ProtocolServerPortSecurity
IMAPmail.yourdomain.com993SSL/TLS
SMTPmail.yourdomain.com587STARTTLS
SMTP (alt)mail.yourdomain.com465SSL/TLS

Username is the full email address (alice@yourdomain.com).

9. SPF, DKIM, and DMARC

You set up SPF and DMARC in the DNS section. DKIM is generated by Mailcow and must be added to DNS manually.

9.1 Get your DKIM key

  1. Go to Configuration → Configuration → ARC/DKIM Keys
  2. Your domain should already have a key. If not, click Add key
  3. Copy the DKIM TXT record value shown

9.2 Add DKIM to DNS

The key is typically named dkim._domainkey.yourdomain.com (the selector prefix may vary):

NameTypeValue
dkim._domainkey.yourdomain.comTXT(paste the full value from the admin UI)

DKIM public keys routinely exceed 255 characters — the maximum length for a single DNS TXT string. Most DNS providers silently truncate or reject oversized values without any error. The fix is to split the key into multiple quoted strings within the same TXT record, but the exact syntax varies by provider. In BIND zone file format it looks like "part1" "part2"; in web UIs you may need to paste each chunk on its own line. Always verify with dig TXT dkim._domainkey.yourdomain.com and confirm the full key is returned before assuming it worked.

9.3 Verify everything

Send a test email to mail-tester.com. A score of 10/10 means SPF, DKIM, and DMARC are all passing. Aim for at least 9/10 before going live.

You can also verify individual records:

# SPF
dig TXT yourdomain.com | grep spf

# DKIM
dig TXT dkim._domainkey.yourdomain.com

# DMARC
dig TXT _dmarc.yourdomain.com

10. Reverse DNS (PTR Record)

A PTR record maps your IP address back to your hostname. Many receiving mail servers — including Gmail and Outlook — reject or silently discard mail from IPs without a matching PTR record.

PTR records are controlled by whoever owns the IP address — your VPS provider, not your DNS registrar. Log into your VPS provider's control panel, find the networking or IP settings for the server, and look for "Reverse DNS" or "PTR record". Set it to mail.yourdomain.com. Every major provider supports this, but the UI location varies — search your provider's documentation if you can't find it.

Verify (after propagation):

dig -x your.server.ip.address
# should return: mail.yourdomain.com
The PTR record must resolve back to your mail hostname, and your hostname's A record must resolve back to the IP. This bidirectional match (forward-confirmed rDNS / FCrDNS) is what receiving servers check.

11. Spam Filtering

Mailcow uses Rspamd for spam filtering. It works well out of the box, but understanding the basics helps you tune it.

Rspamd UI

Access the Rspamd web UI at https://mail.yourdomain.com/rspamd. The password is in mailcow.conf under RSPAMD_PASSWORD.

Training with ham and spam

Rspamd improves with training. Move messages to/from your Junk folder to train the Bayesian filter — Mailcow automatically feeds these moves to Rspamd via the Dovecot sieve script.

Greylisting

Rspamd includes greylisting: it temporarily rejects mail from unknown senders, forcing a retry. Legitimate mail servers always retry; spam bots usually do not. This is enabled by default and significantly reduces spam volume.

Checking scores

Every delivered message has an X-Spamd-Result header showing its Rspamd score and which checks fired. Inspect these headers in your mail client to understand why a message was marked as spam or passed through.

12. Backups

Self-hosting means you are responsible for your backups. Mailcow has no built-in automated backup system. If your disk fails, if you accidentally run docker compose down -v, or if your VPS provider loses your data, recovery depends entirely on what you've backed up and how recently. Build your backup process before you start using the server for real mail.

What to back up

Backing up volumes

# Stop containers first for a consistent backup
docker compose down

# Back up the mail volume
docker run --rm \
  -v mailcowdockerized_vmail-vol-1:/data \
  -v /backup:/backup \
  alpine tar czf /backup/vmail-$(date +%F).tar.gz /data

# Back up the MySQL database
docker compose up -d mysql-mailcow
docker compose exec mysql-mailcow mysqldump \
  --all-databases \
  -u root \
  -p"$(grep DBROOT mailcow.conf | cut -d= -f2)" \
  > /backup/mysql-$(date +%F).sql

docker compose up -d

Offsite backup

Storing backups on the same server is not a backup — it protects only against accidental deletion, not hardware failure or provider outage. Use rclone to sync backups to an S3-compatible object store, Backblaze B2, or another remote location:

rclone copy /backup remote:mailcow-backups --progress

13. Monitoring and Maintenance

Checking mail queues

# View Postfix queue
docker compose exec postfix-mailcow mailq

# Flush queue (retry all deferred messages)
docker compose exec postfix-mailcow postqueue -f

Log access

# All container logs
docker compose logs -f --tail=100

# Postfix SMTP logs only
docker compose logs postfix-mailcow -f

# Dovecot IMAP logs
docker compose logs dovecot-mailcow -f

Updating Mailcow

Run the built-in update script. Read the release notes on GitHub first — occasionally there are breaking changes:

cd /opt/mailcow-dockerized
./update.sh

The script pulls new images, recreates containers, and applies any necessary database migrations. It takes a few minutes and causes brief downtime.

Checking your IP reputation

Getting blacklisted after a clean start is more common than it sounds — a forwarded spam message, a compromised account, or simply sharing a subnet with a bad actor can land your IP on a list. Each blocklist has its own delisting process, some automated, some manual with no guaranteed turnaround. Run this check monthly:

Disk usage

Mail accumulates. Check disk usage regularly:

df -h /
docker system df

14. Troubleshooting

Mail not being delivered to external addresses

  1. Check port 25 is open outbound: telnet smtp.gmail.com 25 — if it hangs, your provider is blocking it
  2. Check the Postfix queue: docker compose exec postfix-mailcow mailq
  3. Look at Postfix logs for bounce/deferral reasons
  4. Verify your PTR record is set and matches your hostname
  5. Check your IP at MXToolbox for blacklistings

External mail not arriving

  1. Verify MX record: dig MX yourdomain.com
  2. Check that port 25 is open inbound (firewall)
  3. Check Postfix and Rspamd logs for rejections
  4. Verify the domain is added in the Mailcow admin UI

TLS certificate not issuing

  1. Confirm port 80 is open inbound
  2. Confirm DNS A record for your hostname resolves to this server's IP
  3. Check logs: docker compose logs acme-mailcow
  4. Wait for DNS propagation before retrying

Container keeps restarting

docker compose ps                          # identify the container
docker compose logs <container> --tail=50  # read the error

Common causes: port conflict with a host service, insufficient RAM (especially ClamAV), misconfigured mailcow.conf.

Locked out of admin UI

# Reset admin password from the host
docker compose exec mysql-mailcow mysql -u root \
  -p"$(grep DBROOT mailcow.conf | cut -d= -f2)" mailcow \
  -e "UPDATE administrator SET password = '{SSHA256}$(echo -n 'newpassword' | openssl dgst -sha256 -binary | base64)' WHERE username = 'admin';"

15. Multiple Domains and Aliases

Adding additional domains

Mailcow supports hosting mail for as many domains as you like. Each domain needs its own MX, SPF, DKIM, and DMARC records — repeat the DNS setup from section 3 for each one. In the admin UI, go to Configuration → Mail Setup → Domains and add each domain. After adding a new domain, generate a DKIM key for it under Configuration → ARC/DKIM Keys — keys are per-domain, not shared.

Domain aliases

A domain alias makes every mailbox on one domain receive mail sent to the same local part on another domain. For example, if you alias oldcompany.com to newcompany.com, mail to alice@oldcompany.com is automatically delivered to alice@newcompany.com. Add domain aliases under Configuration → Mail Setup → Domain aliases. You still need correct DNS (MX record) for the aliased domain.

Mailbox aliases

Individual address aliases redirect one address to another. Go to Configuration → Mail Setup → Aliases. Common uses: info@, support@, noreply@ all routed to a real mailbox. You can also set a catch-all by using @yourdomain.com as the alias address — any address at that domain not matching a real mailbox will land in the catch-all destination. Be cautious with catch-alls; they attract significant spam.

Forwarding

Forwarding copies or redirects incoming mail to an external address. Configure it per-mailbox under Configuration → Mail Setup → Mailboxes → Edit. Be aware that forwarding to Gmail or Outlook is a deliverability minefield: if you forward a spam message that passes your server's checks, the receiving server may blacklist your IP for relaying spam. Use Sieve filters (next section) for finer control.

16. Sieve Filters

Sieve is a scripting language for mail filtering that runs server-side inside Dovecot, before mail reaches your inbox. It can sort, reject, forward, and auto-reply to messages based on any header or content. Mailcow exposes Sieve through two interfaces: a simple rules UI in the user panel, and a raw script editor for advanced use.

Accessing the user panel

Each mailbox has its own login at https://mail.yourdomain.com. Log in with the full email address and mailbox password. Under Filters, you can create basic rules without writing Sieve directly.

Writing Sieve scripts directly

For anything beyond basic sorting, write the script yourself. In the user panel, go to Filters → Sieve script. Example — move newsletter mail to a folder:

require ["fileinto", "imap4flags"];

if header :contains "List-Unsubscribe" "" {
    fileinto "Newsletters";
    stop;
}

Auto-reply (vacation responder):

require ["vacation"];

vacation
    :days 7
    :subject "Out of office"
    :addresses ["alice@yourdomain.com"]
    "I'm away until 15 March. I'll reply when I'm back.";

Reject mail from a specific address:

require ["reject"];

if address :is "from" "spammer@example.com" {
    reject "Not accepted.";
    stop;
}

Global Sieve scripts

Admins can apply a Sieve script to all mailboxes on the server. This is useful for global spam handling or compliance archiving. Edit data/conf/dovecot/global_sieve_before (runs before user filters) or data/conf/dovecot/global_sieve_after (runs after). Restart Dovecot after changes:

docker compose restart dovecot-mailcow

17. Brute Force Protection

Mailcow ships with Fail2ban pre-configured as a container (netfilter-mailcow). It monitors logs from Postfix, Dovecot, SOGo, and the admin UI, and automatically bans IPs that exceed login failure thresholds using nftables rules on the host.

Viewing active bans

docker compose exec netfilter-mailcow fail2ban-client status
docker compose exec netfilter-mailcow fail2ban-client status postfix-sasl

Unbanning an IP

If you lock yourself out:

docker compose exec netfilter-mailcow fail2ban-client set postfix-sasl unbanip YOUR.IP.ADDRESS

Whitelisting IPs

Add trusted IPs to the whitelist in mailcow.conf:

SNAT_TO_SOURCE=
IPV4_NETWORK=172.22.1
# Add to SKIP_LETS_ENCRYPT section:
SPAMHAUS_DQS_KEY=

More precisely, edit data/conf/fail2ban/custom.conf and add:

[DEFAULT]
ignoreip = 127.0.0.1/8 ::1 YOUR.TRUSTED.IP.HERE

Then restart the netfilter container:

docker compose restart netfilter-mailcow

Tuning thresholds

Default ban threshold is 10 failures in 10 minutes, ban duration 1 hour. To adjust, create data/conf/fail2ban/custom.conf:

[DEFAULT]
maxretry = 5
findtime = 600
bantime = 86400

18. Deliverability with Gmail and Outlook

Passing SPF, DKIM, and DMARC is necessary but not sufficient for reliable delivery to Gmail and Outlook. Both providers maintain their own reputation systems that take weeks or months of consistent sending to warm up. Setting up their postmaster tools gives you visibility into how they see your server.

Google Postmaster Tools

  1. Go to postmaster.google.com
  2. Add your domain and verify ownership via a DNS TXT record
  3. After a week of sending mail that recipients interact positively with (open, reply, not mark as spam), your domain reputation dashboard will populate

The dashboard shows your domain reputation (Bad / Low / Medium / High), IP reputation, spam rate, delivery errors, and DKIM/DMARC compliance. A "Low" or "Bad" reputation means Gmail is silently sending your mail to spam or rejecting it. There is no quick fix — only sustained good sending behaviour over time improves it.

Microsoft SNDS and JMRP

For deliverability to Outlook, Hotmail, and Live accounts:

If your IP is already blocked by Microsoft (you'll see 550 5.7.606 or SC-001 in bounce messages), submit a delisting request at sender.office.com.

Warming up a new IP

Large providers are suspicious of IPs that appear from nowhere and immediately send high volumes. If you're sending bulk mail or newsletters, start with small daily volumes and ramp up gradually over several weeks. For transactional mail (receipts, notifications), the volume is typically low enough that warm-up isn't an issue, but reputation still builds slowly.

19. MTA-STS and DANE

MTA-STS

MTA-STS (Mail Transfer Agent Strict Transport Security) is a standard that lets you publish a policy telling other mail servers that they must use TLS when delivering to you, and that they should validate your certificate. Without it, a man-in-the-middle can downgrade an inbound connection to plaintext. With a modern TLS certificate (which Mailcow provides via Let's Encrypt), enabling MTA-STS is straightforward.

You need to serve a policy file over HTTPS at exactly https://mta-sts.yourdomain.com/.well-known/mta-sts.txt, and add a DNS TXT record to activate it.

The policy file content:

version: STSv1
mode: enforce
mx: mail.yourdomain.com
max_age: 604800

Host this file on any web server (a Cloudflare Worker, a GitHub Pages site, or a separate nginx on your mail server all work). Then add two DNS records:

NameTypeValue
mta-sts.yourdomain.comCNAME or A(wherever you're hosting the policy file)
_mta-sts.yourdomain.comTXTv=STSv1; id=20240101000000

The id value is a change indicator — update it (any string works, a timestamp is conventional) whenever you modify the policy file so sending servers know to re-fetch it.

Also add a TLS Reporting record so you receive reports when other servers fail to establish TLS with you:

NameTypeValue
_smtp._tls.yourdomain.comTXTv=TLSRPTv1; rua=mailto:tlsrpt@yourdomain.com

DANE / TLSA

DANE (DNS-based Authentication of Named Entities) publishes your TLS certificate fingerprint in DNS as a TLSA record, secured by DNSSEC. This lets sending servers verify your certificate without relying on the certificate authority system. It requires DNSSEC to be enabled on your domain — without DNSSEC, DANE records are not trustworthy and will be ignored. If your domain registrar supports DNSSEC, generate the TLSA record from your certificate:

# Get the fingerprint from your current certificate
openssl x509 -in /opt/mailcow-dockerized/data/assets/ssl/cert.pem \
  -pubkey -noout \
  | openssl pkey -pubin -outform DER \
  | openssl dgst -sha256 -binary \
  | xxd -p -c 256

The resulting hash is the value for a TLSA record at _25._tcp.mail.yourdomain.com with parameters 3 1 1. DANE adoption among sending servers is limited but growing — worth setting up if you already have DNSSEC.

20. Full-Text Search

By default, IMAP search in Dovecot can only search message headers and metadata. Searching the body of messages (as mail clients do when you use their search box) requires Dovecot to open and scan every message individually — which is slow on large mailboxes.

Mailcow includes an optional Solr container for full-text indexing. It is disabled by default because it consumes significant RAM (512 MB minimum, more with large mailboxes).

Enabling Solr

In mailcow.conf, set:

SKIP_SOLR=n

Then recreate the affected containers:

docker compose up -d

Solr starts indexing existing mail in the background. Depending on mailbox size, initial indexing can take hours. Monitor progress:

docker compose logs solr-mailcow -f

Reindexing a mailbox

If a mailbox becomes out of sync:

docker compose exec dovecot-mailcow doveadm fts rescan -u alice@yourdomain.com
docker compose exec dovecot-mailcow doveadm index -u alice@yourdomain.com -q INBOX

21. Migrating Existing Mail

If you're moving from another mail provider, imapsync is the standard tool for copying mail between IMAP servers. It preserves folder structure, flags (read/unread, starred), and message dates.

Installing imapsync

apt install -y imapsync

Running a migration

imapsync \
  --host1 imap.oldprovider.com --ssl1 \
  --user1 alice@olddomain.com --password1 'oldpassword' \
  --host2 mail.yourdomain.com --ssl2 \
  --user2 alice@yourdomain.com --password2 'newpassword' \
  --automap \
  --skipcrossduplicates \
  --useuid

--automap handles common folder name differences (e.g. "Sent Items" vs "Sent"). --skipcrossduplicates avoids copying messages that already exist in the destination. --useuid uses IMAP UIDs for deduplication, which is more reliable than message-ID matching.

For large mailboxes, run imapsync in a screen or tmux session so a disconnected SSH session doesn't interrupt it:

screen -S migration
imapsync [options]
# Ctrl+A then D to detach; screen -r migration to reattach

Syncing incrementally

Run imapsync multiple times before your DNS cutover. The first run copies everything (slow). Subsequent runs only sync new and changed messages (fast). On cutover day: update your MX record, wait for propagation, then do one final sync to catch mail that arrived at the old server during the transition.

Migrating contacts and calendar

Mail is only part of it. If your old provider supports CardDAV/CalDAV export, export your contacts as a .vcf file and your calendar as an .ics file, then import them into SOGo via the webmail interface at https://mail.yourdomain.com.

22. Restoring from Backup

Having backups is only half of it. Know your restore procedure before you need it.

Restoring the mail volume

# Stop Mailcow
cd /opt/mailcow-dockerized
docker compose down

# Remove the existing volume (destructive — only do this if restoring fully)
docker volume rm mailcowdockerized_vmail-vol-1

# Recreate the volume and restore
docker volume create mailcowdockerized_vmail-vol-1
docker run --rm \
  -v mailcowdockerized_vmail-vol-1:/data \
  -v /backup:/backup \
  alpine tar xzf /backup/vmail-2024-01-01.tar.gz -C /

Restoring the database

# Start only MySQL
docker compose up -d mysql-mailcow

# Restore the dump
docker compose exec -T mysql-mailcow mysql \
  -u root \
  -p"$(grep DBROOT mailcow.conf | cut -d= -f2)" \
  < /backup/mysql-2024-01-01.sql

# Start the rest
docker compose up -d

Restoring to a new server

If the original server is gone:

  1. Provision a new server and install Docker (section 4)
  2. Clone the Mailcow repository: git clone https://github.com/mailcow/mailcow-dockerized /opt/mailcow-dockerized
  3. Copy mailcow.conf from your backup to /opt/mailcow-dockerized/mailcow.conf
  4. Restore the mail volume and database as above
  5. Update DNS to point to the new server's IP
  6. Set the PTR record on the new server's IP at your new VPS provider
  7. Start Mailcow: docker compose up -d

The Let's Encrypt certificate will be re-issued automatically once DNS propagates. Expect 15–60 minutes before HTTPS is fully working on the new server.

23. Performance Tuning

ClamAV memory usage

ClamAV is the largest consumer of RAM in a default Mailcow installation. Its virus database loads entirely into memory and occupies roughly 700 MB–1 GB. If you're running on a server with less than 6 GB RAM and don't need virus scanning (e.g. the server is for personal use and you trust your senders), disable it in mailcow.conf:

SKIP_CLAMD=y

Then recreate the containers: docker compose up -d. Rspamd will still filter spam; you simply lose the virus scanning layer.

MySQL / MariaDB tuning

For servers with more RAM, increase the InnoDB buffer pool to allow more of the database to be cached in memory. Create data/conf/mysql/custom.cnf:

[mysqld]
innodb_buffer_pool_size = 256M
innodb_buffer_pool_instances = 1
innodb_log_file_size = 64M
max_connections = 50

Restart MySQL: docker compose restart mysql-mailcow. For most personal and small-team setups the defaults are fine; this matters at higher mailbox counts.

Rspamd worker processes

Rspamd uses a normal worker for scanning and a controller for the web UI. On a high-volume server, you can increase the number of scanner workers. Create data/conf/rspamd/local.d/worker-normal.inc:

count = 4;

Restart Rspamd: docker compose restart rspamd-mailcow. Each worker uses roughly 100–200 MB RAM.

Dovecot connection limits

The default per-IP connection limit in Dovecot is 10. Mail clients that open many IMAP connections (some versions of Outlook, or mobile clients syncing multiple folders simultaneously) can hit this. Raise it in data/conf/dovecot/extra.conf:

mail_max_userip_connections = 30

Restart Dovecot: docker compose restart dovecot-mailcow.

Log rotation

Docker container logs are written to /var/lib/docker/containers/ and are not rotated by default. On a busy server they can fill your disk over weeks or months. Configure log rotation in /etc/docker/daemon.json:

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "50m",
    "max-file": "5"
  }
}

Restart Docker: systemctl restart docker. Then restart Mailcow: docker compose up -d. Note that this applies to new containers — existing containers need to be recreated to pick up the new log settings.