I run Mailcow as a self-hosted email server for two clients and for one of my own domains, and the realistic answer to “should you do this” is: probably not, unless you have a specific reason. Email is the most punishing service I’ve ever self-hosted. The Mailcow stack itself is clean and well-engineered. The hard part lives entirely in DNS, IP reputation, and the operational discipline you bring to the recovery path when a major provider decides your messages look like spam.
This post is the actual deployment I ship: a Mailcow Compose stack on a 4GB VPS, with the DNS records, the rDNS request, and the deliverability checks that turn it from “the install worked” into “messages reach the inbox.” I’ll also tell you when I refuse to self-host email and route the client to Google Workspace instead, because that’s the more useful conversation than another pure how-to.
If you’ve decided self-hosting is the right call, this guide gets you from a hardened Ubuntu server to a working Mailcow instance with verified DKIM, DMARC, and a deliverability score above 9 on mail-tester. The whole thing fits in an afternoon, plus a week of warming the IP before you start sending real volume.
Why Mailcow over the alternatives
The self-hosted email field is small and crowded at the same time. Mail-in-a-Box, iRedMail, Mailu, Modoboa, Poste.io, and Mailcow each take a different position on packaging. After two years on iRedMail and three on Mailcow, I’m on Mailcow for two reasons.
The Compose architecture. Every component (Postfix, Dovecot, Rspamd, ClamAV, SOGo, Redis, MariaDB, Solr, ACME) runs in its own container with sensible defaults. Updates happen through update.sh. When something breaks, I read one container’s logs instead of grep-ing through /var/log/. iRedMail puts everything on the host with init scripts, which turns a failed update into a Saturday afternoon.
And the admin UI is genuinely good. SOGo for users, Rspamd’s web UI for spam tuning, Mailcow admin for domains and mailboxes. I haven’t had to SSH to a Mailcow box in months for routine work. The trade-off you’re accepting is opinionated defaults: “I want SpamAssassin instead of Rspamd” is not a supported request. If you need that, run the components yourself.
Prerequisites that actually matter
Mailcow’s docs ask for 6GB RAM. The realistic minimum is 4GB if you’re willing to disable Solr full-text indexing. ClamAV alone will sit at 1.2GB resident, Rspamd needs about 600MB, and the rest of the stack adds another 800MB before you’ve delivered a single message. A CX22 (2GB) will boot Mailcow, then OOM-kill ClamAV during the first signature update.
The non-negotiable prerequisites I check before I touch the install script:
- Outbound port 25 is unblocked. Most cloud providers block this by default. Hetzner unblocks on request after a brief “what are you using it for” exchange. DigitalOcean, Vultr, OVH each have their own process. AWS will not unblock under normal circumstances; if you’re on AWS, route everything through SES.
- A spare domain or subdomain you control. I run my mail on
mail.<domain>.tldand use the apex for the website. Don’t try to self-host on a domain that’s already on Google Workspace; the MX cutover is its own project. - A hardened server. SSH keys, no root login, UFW with deny-by-default, automatic security updates. My Linux server security fundamentals post is the baseline I run on every fresh box. An unhardened mail server is a credential-stuffer’s playground.
- Reverse DNS access. Your provider needs to let you set the PTR record on your IP to
mail.yourdomain.tld. If they won’t, deliverability is going to be a constant fight. - A backup destination. Before you migrate a single mailbox in, decide where the daily backup lands. I use restic to Backblaze B2 for client deployments and a separate Hetzner Storage Box for my own.
If any of these five aren’t in place, fix that first. The Mailcow install will work without them. The mail won’t deliver.
The deployment itself
The installation steps haven’t changed in years and they’re refreshingly short. Most of the work happens before and after the docker compose up.
Docker engine
Install Docker from the official Docker repository. The official Docker installation guide for Ubuntu is the only correct source. Don’t install from apt’s default repo or from a one-line curl | sh script. Mailcow’s update tooling assumes the official build, and you’ll find out at the worst possible moment if you used the distribution package.
DNS records before you start
Set these in DNS before you generate the Mailcow config. If you bring up the stack with the records wrong, the ACME challenge fails and you’ll debug the wrong thing for an hour.
A mail.yourdomain.tld <server-ip>
AAAA mail.yourdomain.tld <server-ipv6> # if you have one
MX yourdomain.tld mail.yourdomain.tld priority 10
A autodiscover.yourdomain.tld <server-ip>
A autoconfig.yourdomain.tld <server-ip>
The DKIM and DMARC records get added after Mailcow generates the DKIM key. We’ll come back to those.
Clone and configure
cd /opt
git clone https://github.com/mailcow/mailcow-dockerized
cd mailcow-dockerized
./generate_config.sh
The generator asks for the FQDN. Use mail.yourdomain.tld exactly as it appears in DNS. It produces mailcow.conf, which contains every tunable. I open it once before the first start to set the timezone and double-check the FQDN. Everything else can stay at defaults for the first deployment.
nano mailcow.conf
Start the stack
docker compose pull
docker compose up -d
First pull is roughly 2GB. On a fresh Hetzner box, the start takes 4-5 minutes. Watch docker compose logs -f for ACME retrieving the certificate. If it fails, your DNS A record is wrong or port 80 is blocked.
Once the stack is up, browse to https://mail.yourdomain.tld/admin. Default credentials are admin / moohoo; change them immediately. Add your domain under Configuration → Mail Setup → Domains, then add at least one mailbox.
DKIM, SPF, DMARC
In the admin UI, Configuration → ARC/DKIM keys. Mailcow generates the DKIM key automatically. Copy the public key value and publish it as a TXT record:
TXT dkim._domainkey.yourdomain.tld v=DKIM1; k=rsa; p=<long-public-key>
Then the SPF record:
TXT yourdomain.tld v=spf1 mx ~all
And DMARC, starting in monitoring mode:
TXT _dmarc.yourdomain.tld v=DMARC1; p=none; rua=mailto:dmarc-reports@yourdomain.tld
I keep DMARC at p=none for the first two weeks, monitor the aggregate reports, then move to p=quarantine once I’ve confirmed nothing legitimate is failing alignment. Going straight to p=reject on day one is how you discover that a client’s marketing tool was spoofing your domain for newsletters.
Reverse DNS
On Hetzner, this is in the Cloud Console under the server’s networking tab. Set the PTR for the IPv4 (and IPv6 if you have it) to mail.yourdomain.tld. Without rDNS, large providers (Microsoft especially) will reject your mail before they even look at the body.

The Mailcow admin surface bundles SOGo webmail, Rspamd’s spam UI, and the configuration panel into one interface. Most of the post-install operational work happens here.
The deliverability test
Before you migrate a single real mailbox in, send a test message to mail-tester.com. Aim for 9.5 or higher. Anything less and you have a configuration problem to fix before you go live.
What I check, in order, when the score is low:
- SPF syntax. A typo or a missing
~allis the most common cause. - DKIM signature. The selector and public key must match exactly. Mailcow shows the expected DNS record on the DKIM page; copy that verbatim.
- rDNS / PTR. mail-tester checks this. If the PTR points to the provider’s default
static.<provider>.tld, you haven’t set it. - IP reputation. Check the IP at Talos Intelligence and Spamhaus. If it’s listed, request a swap before doing anything else.
- TLS. MTA-STS and DANE are nice-to-haves; the ACME certificate is the must-have.
After the score is 9.5+, send a second message to a real Gmail account from a fresh IP. Watch where it lands. Promotions is fine for a new sender. Spam means a reputation problem DNS can’t fix.
Operational discipline
The Mailcow box is not a fire-and-forget deployment. The recurring work that keeps it healthy:
- Updates.
./update.shfrom inside/opt/mailcow-dockerizedonce a month. Read the changelog before you run it. Mailcow has had two updates in three years where I had to revert a tag manually; both were documented in the release notes. - Backups. The
helper-scripts/backup_and_restore.shscript handles the volumes correctly. I run it nightly into restic, with the encrypted repository on Backblaze B2. Test the restore quarterly on a throwaway VPS. - Monitoring. Uptime Kuma checks port 993 (IMAPS) and 465 (SMTPS) every minute plus an HTTP check on the admin UI. The first time the box runs out of disk because logs filled the partition, you want a notification, not a confused client.
- DMARC reports. I send aggregate reports to a dedicated mailbox and skim them weekly. They’re how you discover someone is spoofing your domain or that your own marketing platform isn’t aligned.
- IP reputation. Monthly check at Talos and Spamhaus. If you wake up to a deliverability complaint, the first thing to verify is whether your IP got listed overnight.
For the proxy in front of Mailcow’s web UI, my Nginx security hardening post covers the headers. On the abuse-traffic side, CrowdSec catches SMTP brute-force attempts at the perimeter.
Closing the loop
A Mailcow self-hosted email server is the kind of project where the install is the easy part and the deliverability is the actual work. If you’re considering moving off Workspace to save money, run the per-user cost calculation honestly and include your time. The break-even is higher than most people expect.
Where Mailcow earns its keep is in the cases Workspace can’t reach. Multiple custom domains, contractual data residency, an agency model where mail-as-a-service is part of what you sell. In those cases the stack is mature, the maintainers respond on GitHub, and the operational pattern is the same as any other Compose deployment I run. The difference is that email punishes mistakes harder than any other service, so the operational discipline matters more than the install. Get the DNS right, watch the IP reputation, keep the recovery path tested, and Mailcow will run for years without drama.