$linuxjunkies
>

Self-Host Mail with Mailcow Dockerized

Deploy a full self-hosted mail server with Mailcow Dockerized: DNS setup, DKIM, SOGo groupware, Let's Encrypt TLS, automated backups, and monitoring.

AdvancedUbuntuDebianFedoraArch12 min readUpdated June 7, 2026

Before you start

  • A VPS or dedicated server with at least 8 GB RAM and a static IP address
  • Outbound port 25 unblocked by your hosting provider
  • A registered domain with the ability to edit DNS records and set a PTR record
  • Docker Engine 24+ with the Compose v2 plugin installed

Running your own mail server has a reputation for being a nightmare of Postfix configs, Dovecot ACLs, and Rspamd tuning. Mailcow Dockerized wraps all of that into a single Docker Compose stack—Postfix, Dovecot, Rspamd, SOGo, Nginx, and more—with a polished web UI. You still need to understand what you're doing: bad DNS means bounced mail, and a misconfigured server will get you blacklisted fast. This guide walks through a production-ready deployment from scratch.

Prerequisites and System Requirements

Mailcow is not lightweight. The official minimum spec is 6 GB RAM and 2 CPU cores, but 8 GB RAM is strongly recommended for a low-traffic server. You need a VPS or dedicated machine with a static IP, a registered domain, and reverse DNS (PTR) control through your hosting provider.

  • A clean Ubuntu 22.04 or Debian 12 server (the project tests against these primarily)
  • A fully-qualified hostname, e.g. mail.example.com, resolving to your server IP
  • Port 25 unblocked—many cloud providers (AWS, GCP, Azure) block outbound port 25 by default; request it be opened before you start
  • Docker Engine 24+ and Docker Compose v2 (the docker compose plugin, not the legacy docker-compose binary)

Install Docker Engine

Mailcow ships its own installer that checks Docker versions, but install it cleanly first.

curl -fsSL https://get.docker.com | sh
systemctl enable --now docker

Verify Compose v2 is available:

docker compose version
# Docker Compose version v2.27.0

Clone Mailcow and Generate Config

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

Run the interactive setup script. It sets your hostname and timezone, then writes mailcow.conf.

./generate_config.sh

You will be prompted for your mail server FQDN (e.g. mail.example.com) and your timezone string (e.g. Europe/Berlin). The script also selects a random HTTP/HTTPS port offset if you want Nginx on non-standard ports, which is useful when an existing web server occupies 80/443.

Open mailcow.conf and review key settings:

grep -E '^(MAILCOW_HOSTNAME|TZ|HTTPS_PORT|HTTP_PORT|SKIP_LETS_ENCRYPT)' mailcow.conf

If you have an existing reverse proxy handling TLS, set HTTPS_PORT=8443 and HTTP_PORT=8080, then proxy to those. For a standalone server, leave ports at 443/80 and keep SKIP_LETS_ENCRYPT=n.

DNS Records

Get DNS right before you pull containers. Mailcow's ACME client and incoming mail both depend on correct DNS. Add these records to your domain registrar or DNS provider:

TypeNameValueTTL
AmailYOUR_SERVER_IP300
MX@mail.example.com (priority 10)300
TXT@v=spf1 mx ~all300
TXT_dmarcv=DMARC1; p=quarantine; rua=mailto:[email protected]300
PTRYOUR_SERVER_IPmail.example.com

The DKIM record is generated after first boot—add it once Mailcow creates the key. Set your PTR record through your hosting control panel; if the option isn't there, open a support ticket.

Start the Stack

docker compose pull
docker compose up -d

First startup takes 3–5 minutes. Mailcow initialises databases, generates DKIM keys, and requests a Let's Encrypt certificate for your hostname. Watch the ACME container:

docker compose logs -f acme-mailcow

A successful run ends with a line containing OK and the certificate paths. If you see errors, DNS probably hasn't propagated yet—wait and re-run docker compose restart acme-mailcow.

Add DKIM to DNS

Once the stack is up, retrieve the auto-generated DKIM public key:

cat /opt/mailcow-dockerized/data/conf/rspamd/override.d/dkim_signing.conf

The UI path is easier: log in at https://mail.example.com with credentials admin / moohoo (change this immediately), navigate to Configuration → Domains → DKIM, and copy the DNS TXT record shown there. Add it to your DNS as a TXT record on dkim._domainkey.example.com.

Change the admin password immediately via Access → Edit administrator.

Create a Domain and Mailbox

In the web UI:

  1. Go to Configuration → Mail Setup → DomainsAdd domain. Enter example.com, set quotas as appropriate.
  2. Go to MailboxesAdd mailbox. Create your first user, e.g. [email protected].
  3. Aliases, catch-alls, and forwarding rules are all in the same panel.

SOGo Groupware

SOGo is bundled and handles CalDAV, CardDAV, and a webmail interface at https://mail.example.com/SOGo. Log in with your mailbox credentials. To connect a desktop client (Thunderbird, GNOME Calendar), use these endpoints:

  • IMAP: mail.example.com:993, TLS
  • SMTP: mail.example.com:587, STARTTLS
  • CalDAV: https://mail.example.com/SOGo/dav/[email protected]/Calendar/personal/
  • CardDAV: https://mail.example.com/SOGo/dav/[email protected]/Contacts/personal/

SOGo's per-user settings (vacation responder, delegates, ACLs) live under the gear icon in the top-right of the SOGo interface.

Backups

Mailcow ships a backup script. Run it manually first to verify it works:

cd /opt/mailcow-dockerized
bash helper-scripts/backup_and_restore.sh backup all

Backups land in /var/mailcow-backups by default. The archive includes MySQL dumps, Rspamd config, and all mail volumes. Schedule it with a systemd timer rather than cron:

cat > /etc/systemd/system/mailcow-backup.service <<'EOF'
[Unit]
Description=Mailcow backup

[Service]
Type=oneshot
WorkingDirectory=/opt/mailcow-dockerized
ExecStart=/bin/bash helper-scripts/backup_and_restore.sh backup all
EOF

cat > /etc/systemd/system/mailcow-backup.timer <<'EOF'
[Unit]
Description=Daily Mailcow backup

[Timer]
OnCalendar=*-*-* 02:30:00
Persistent=true

[Install]
WantedBy=timers.target
EOF

systemctl daemon-reload
systemctl enable --now mailcow-backup.timer

Sync backups off-site with rclone or rsync to object storage. A local-only backup on the same disk that holds your mail is not a backup.

Monitoring

Mailcow exposes a built-in watchdog container that restarts unhealthy services, but you should add external checks.

Check Container Health

docker compose ps

All containers should show healthy or running. The watchdog-mailcow container logs restarts.

Mail Queue and Rspamd

The UI at Information → Mail Queue shows stuck messages. The Rspamd dashboard at https://mail.example.com/rspamd (login via the UI under Configuration → Rspamd UI) shows spam scores, actions, and throughput graphs in real time.

External Blacklist Check

# Check your IP against common DNSBLs
dig +short YOUR_SERVER_IP.zen.spamhaus.org
dig +short YOUR_SERVER_IP.bl.spamcop.net

No output means you're not listed. If you are, follow the delisting process for that specific list before expecting reliable mail delivery.

Verify SPF, DKIM, DMARC

Send a test message to [email protected] or use mail-tester.com. You should score 9/10 or higher; anything lower points to a misconfigured DNS record.

Updates

Mailcow ships an update script that pulls new images and runs database migrations:

cd /opt/mailcow-dockerized
bash update.sh

Read the release notes before updating—breaking changes are flagged there. Take a backup immediately before running updates in production.

Troubleshooting

ACME Certificate Fails

Port 80 must be reachable from the internet for HTTP-01 challenge. Verify with curl -I http://mail.example.com from an external host. If a firewall is blocking it, open port 80 even if you redirect to HTTPS—the challenge hits the bare HTTP URL.

Outbound Mail Goes to Spam

In order of likelihood: missing or wrong DKIM TXT record, PTR record not set, SPF record too permissive or missing, server IP on a blacklist. Use mail-tester.com to identify which check is failing.

Port 25 Blocked by Provider

AWS EC2, Google Cloud, and Azure all block port 25 by default. AWS requires a service quota increase request via the console. GCP requires a similar request. There is no workaround—you must get the block lifted or use a relay like SendGrid for outbound with Mailcow receiving inbound through a different host.

Container Keeps Restarting

docker compose logs --tail=50 postfix-mailcow

Replace postfix-mailcow with the failing container name from docker compose ps. Permission errors on volumes usually mean a previous run left root-owned files; the Mailcow docs cover fixing ownership with the provided helper scripts.

tested on:Ubuntu 22.04Debian 12

Frequently asked questions

Can I run Mailcow on a server that already hosts websites on ports 80 and 443?
Yes. Set HTTP_PORT and HTTPS_PORT to alternate values in mailcow.conf and proxy requests from your existing Nginx or Caddy instance. Mailcow's documentation includes an example reverse-proxy config.
How do I migrate existing mail from another server or Gmail?
Use the imapsync tool or Mailcow's built-in sync job feature under Configuration → Sync Jobs. It connects to the source server over IMAP and copies messages folder by folder.
Is Mailcow suitable for a small business with 20–50 mailboxes?
Yes, that is a typical use case. A 4-core, 8 GB RAM VPS handles it comfortably. Make sure your hosting provider does not block port 25 and that you have dedicated IP reputation.
How do I renew TLS certificates—does it happen automatically?
The acme-mailcow container checks and renews certificates daily. As long as port 80 is reachable, renewal is fully automatic. You can force a check with `docker compose restart acme-mailcow`.
Can I use my own wildcard certificate instead of Let's Encrypt?
Yes. Set SKIP_LETS_ENCRYPT=y in mailcow.conf, then place your certificate and key at the paths defined in the Nginx config inside data/conf/nginx/. Restart the nginx-mailcow container after copying the files.

Related guides