How to Self-Host Production Email with Mailcow on a Dedicated Server

Running your own mail server used to mean stitching together Postfix, Dovecot, Rspamd, an antivirus engine, a webmail client, and a web UI by hand. Mailcow packages all of that into a Docker Compose stack that you can deploy in under an hour.

This guide walks through a full production deployment on a dedicated server: installation, DNS authentication, security hardening, and the deliverability testing that actually determines whether your mail lands in the inbox or the spam folder.

Prerequisites

Before you start, make sure you have:

  • A dedicated server with a static public IP address, running on KVM or bare metal. Mailcow needs proper kernel-level Docker support (cgroups, overlay filesystems), which container-based virtualization like LXC and OpenVZ generally can't provide — avoid those for the host.
  • Minimum resources: 2 CPU cores (4+ for production), 6 GB RAM plus 1 GB swap (8 GB+ recommended), and 40 to 50 GB of storage (SSD preferred).
  • A supported OS: Debian and Ubuntu LTS releases are the most heavily tested.
  • A clean IP address: Check your server IP against blocklists (Spamhaus, Barracuda, SORBS) before committing to it. If it has a bad reputation, deliverability will suffer regardless of how well you configure the server.
  • Port 25 open (Outbound & Inbound): Many cloud/VPS providers block outbound port 25 to curb spam. Confirm with your host that port 25 is unblocked, otherwise SMTP simply won't work. If your provider refuses to unblock it, your fallback is to route outbound mail through a smarthost/relay.
  • Reverse DNS (PTR) control: You'll need your hosting provider to set the PTR record for your IP to match your mail server's hostname (e.g., mail.yourdomain.com). If you have both an IPv4 and IPv6 address, both must have a PTR record pointing to this hostname.

Step 1: Prepare the Server

Update the system and confirm the required ports aren't already in use by another service:

Bash
sudo apt update && sudo apt upgrade -y
sudo apt install -y curl git apt-transport-https ca-certificates gnupg lsb-release

ss -tlpn | grep -E -w '25|80|110|143|443|465|587|993|995|4190'

If anything else is bound to those ports (an existing web server on 80/443, another mail service on 25/587/993), stop or reconfigure it first.

Confirm your system clock is correct and NTP-synced, which is critical for Let's Encrypt TLS certificate validation and TOTP two-factor codes:

Bash
timedatectl

Step 2: Install Docker

Mailcow requires recent versions of Docker Engine and Docker Compose. Rather than piping a downloaded script straight into sh, download it first so you can inspect it, then run it:

Bash
curl -fsSL https://get.docker.com/ -o install-docker.sh
sh install-docker.sh
sudo systemctl enable --now docker

If you'd rather avoid the convenience script entirely, install Docker Engine and the Compose plugin from Docker's official apt repository per their current documentation for your distribution.

Step 3: Download and Configure Mailcow

Clone the repository and run the configuration script:

Bash
sudo mkdir -p /opt && cd /opt
sudo git clone https://github.com/mailcow/mailcow-dockerized
cd /opt/mailcow-dockerized
sudo ./generate_config.sh

You'll be prompted for two important values:

  • MAILCOW_HOSTNAME: The Fully Qualified Domain Name (FQDN) of the mail server itself (e.g., mail.yourdomain.com). This is what your PTR records and Let's Encrypt certificates will be built around.
  • MAILCOW_TIMEZONE: Your local timezone (e.g., America/New_York).

This script generates your mailcow.conf file. If your server uses IPv6, ensure enable_ipv6 in Docker is configured correctly, or optionally disable IPv6 in mailcow.conf entirely if your hosting provider does not grant you IPv6 PTR records. (Note: Sending IPv6 mail without a PTR record will result in immediate rejection by Gmail).

Step 4: Start the Stack

Pull the latest container images and bring the stack up:

Bash
sudo docker compose pull
sudo docker compose up -d

This starts Postfix, Dovecot, Rspamd, ClamAV, SOGo, MariaDB, Redis, and Nginx. The first boot takes a few minutes while the databases initialize.

Once the logs settle, the admin UI is reachable at https://<your-server-IP-or-hostname>. Mailcow automatically provisions a Let's Encrypt certificate assuming ports 80/443 are reachable.

Step 5: Initial Login and Admin Hardening

Log in to the admin UI using the default credentials:

  • Username: admin
  • Password: moohoo

Change this password immediately. Navigate to the admin's profile settings and set a complex password. While you're there:

  • Enable Two-Factor Authentication (TOTP or WebAuthn/U2F) on the admin account.
  • Under System → Configuration, review the Fail2Ban settings and adjust ban thresholds if needed.

Step 6: Configure DNS Records (Authentication)

This step determines whether your mail actually reaches the inbox. You need specific DNS records for every domain you send from. Assuming your server is mail.yourdomain.com, its IPv4 is 1.2.3.4, and its IPv6 is 2001:db8::1:

Name Type Value
mail A 1.2.3.4
mail AAAA 2001:db8::1
autodiscover CNAME mail.yourdomain.com
autoconfig CNAME mail.yourdomain.com
@ MX 10 mail.yourdomain.com
@ TXT (SPF) v=spf1 mx -all
_dmarc TXT v=DMARC1; p=none; rua=mailto:[email protected]

Why v=spf1 mx -all? Relying solely on an IP address (e.g., ip4:1.2.3.4) is brittle. By using mx, you authorize whatever IPv4 or IPv6 address your MX record resolves to. The -all dictates a "Hard Fail" for any unauthorized servers.

DKIM: Generate your DKIM key inside the Mailcow UI: Configuration → Mail Setup → Domains → [Select Domain] → Key Icon. Mailcow will generate the exact TXT record you need to publish. Raw DNS zone files automatically split values over 255 characters into quoted chunks, which is standard — but some DNS provider web panels don't do this for you. If your panel rejects the long string or truncates it, you'll need to manually break it into quoted 255-character segments yourself.

Once published, check the DNS button in Mailcow. Mailcow parses your live DNS zone; when properly configured and propagated, your A, MX, SPF, DKIM, and DMARC records will all light up with green checkmarks.

Step 7: Add Your Domain and Mailboxes

  • Add Domain: Go to Configuration → Mail Setup → Domains → Add Domain. Enter the bare domain (yourdomain.com).
  • Add Mailbox: Go to Configuration → Mail Setup → Mailboxes → Add Mailbox. Set the username, password, and allocate a quota.

Step 8: TLS Certificates

Mailcow handles TLS certificate generation and renewal automatically via its acme-mailcow container. It secures the primary hostname and any ADDITIONAL_SAN domains defined in mailcow.conf.

If you prefer to use a custom commercial certificate, set SKIP_LETS_ENCRYPT=y in mailcow.conf, copy your cert.pem and key.pem files to /opt/mailcow-dockerized/data/assets/ssl/, set the correct permissions (600 on the key, 644 on the cert), and restart the stack.

Step 9: Security Hardening

Before sending production email, lock down the environment:

  • Firewall: Only allow ports that Mailcow requires:
    • 25 (SMTP routing)
    • 80 / 443 (HTTP/HTTPS for Web UI, ACME, ActiveSync)
    • 143 / 993 (IMAP / IMAP over TLS)
    • 110 / 995 (POP3 / POP3 over TLS — optional, only if you support legacy clients)
    • 465 / 587 (SMTPS / Submission)
    • 4190 (Sieve filtering)
    Restrict port 22 (SSH) to your specific admin IP addresses.
  • Rate Limiting: Go to the admin panel and configure per-mailbox rate limits. This prevents a compromised user account from blasting out thousands of spam emails and destroying your IP reputation.
  • MTA-STS (Optional but Recommended): Modern providers like Google encourage MTA-STS. Mailcow can host the required policy file, but it takes two pieces to work: an _mta-sts TXT record (v=STSv1; id=<unique-id>) and an mta-sts subdomain (e.g., mta-sts.yourdomain.com) pointed at your server and served over valid HTTPS, since that's where senders fetch the actual policy file from. Publishing only the TXT record without the working HTTPS endpoint leaves MTA-STS non-functional.
  • Updates: Run ./update.sh regularly.

Step 10: Test Deliverability

Before putting your server into active use, verify your authentication:

  • Port25 Verifier: Send a blank test email from your newly created mailbox to [email protected]. You will receive an automated reply analyzing your SPF, DKIM, iprev (Reverse DNS), and a SpamAssassin score. You want "pass" across the board.
  • Mail-Tester: Go to mail-tester.com, send an email to the unique address provided, and aim for a 10/10 score.
  • Real-World Test: Send emails to a Gmail account and an Outlook/Microsoft 365 account you control to ensure they land in the inbox.
Troubleshooting Tip If tests fail, troubleshoot in this exact order: PTR Records → SPF Syntax → DKIM Match → DMARC Alignment.

Step 11: Warm Up the IP

If your IP address has no sending history, major providers will treat it suspiciously. Ramp up your email volume gradually:

  • Week 1: Keep it under 50 messages/day to establish clean authentication.
  • Weeks 2–3: Scale up to 200–500 messages/day. Monitor your test inboxes for any spam folder placements.
  • Week 4+: Gradually scale toward your normal target volume.

Ongoing Maintenance

  • Monitor your server's IP periodically via Spamhaus, Talos Intelligence, and SenderScore.
  • Monitor your DMARC reports (sent to the email address in your rua= tag) for unauthorized sending attempts or authentication breaks.
  • Regularly back up your Mailcow Docker volumes, as this is where all the mail database data lives. Mailcow provides a /helper-scripts/backup_and_restore.sh script for this purpose.

Summary

Mailcow gets you a fully self-hosted, production-capable mail stack in a single Docker Compose deployment: Postfix and Dovecot for mail transport and storage, Rspamd and ClamAV for filtering, SOGo for webmail, and a web admin UI tying it together. The install itself takes under an hour.

What actually determines whether your mail reaches the inbox is everything downstream of that: a clean IP, correct PTR, properly published SPF/DKIM/DMARC, TLS, sane rate limiting, and a gradual sending ramp. Do those consistently and you'll get deliverability on par with, or better than, most commercial providers — just not a guaranteed 100%, because that number isn't fully within any sender's control.

Need a Reliable Server for Your Mail Transport?

Self-hosting email requires a provider that offers clean IPs, full reverse DNS control, and doesn't arbitrarily block port 25. Stop fighting with restrictive virtual servers and move your communication stack to reliable bare metal.

Explore Fit Servers for high-performance, cost-effective dedicated servers perfectly tailored for Docker-based mail deployments. Take control of your data privacy and deliverability today.

View Dedicated Servers