How to set up a Mailcow email server on a Linux VPS
Technical guide to deploy a Mailcow email server on a Linux VPS with Postfix, Dovecot, SOGo, DKIM, SPF, DMARC and web admin in under an hour.
Running your own email server is no longer taboo — Mailcow packages Postfix, Dovecot, SOGo, Rspamd, ClickHouse and a web admin into isolated Docker containers, eliminating 90% of the manual configuration work that used to take days. You can deploy the full stack on a Linux VPS in under an hour.
This tutorial targets the developer or sysadmin who needs professional email under their own control — without depending on Workspace, Microsoft 365 or Zoho. Typical use cases: a company that wants unlimited mailboxes without per-user fees, a SaaS that needs a transactional SMTP server with isolated reputation, or a technical professional who values correspondence privacy.
Estimated execution time: 45-60 minutes, of which 15-20 minutes are DNS propagation. The rest is automated installation and domain setup in the panel.
Prerequisites
Before starting, line up the resources. Email is sensitive to IP, reverse DNS and ports — skipping any of these points means messages landing in spam even with valid TLS.
Mailcow recommends 6 GB of RAM, 20 GB of disk and 2 vCPUs for comfortable operation. Operating system: Ubuntu 22.04/24.04 LTS or Debian 12. Root access via SSH. Your own domain with DNS control (Cloudflare, your registrar, etc).
mail.yourdomain.com 25, 465, 587 143, 993, 110, 995 443 (HTTPS) 4190 Configure the PTR record (reverse DNS) of the VPS IP pointing to the chosen hostname before continuing. In the VPS provider panel you usually find this under “Network” or “Reverse IP”. Without a valid PTR, delivery to Gmail and Outlook degrades drastically.
Also confirm that outbound port 25 is open — many providers block outbound
SMTP by default. Test with telnet smtp.gmail.com 25 or
nc -vz smtp.gmail.com 25 from the VPS. If it times out, open a support
ticket with the provider before proceeding.
Prepare the base system
The first phase gets the server ready for Docker and Mailcow. We work with each project’s official packages, avoiding third-party repos.
Update the system and install basic dependencies:
sudo apt update && sudo apt upgrade -y
sudo apt install -y curl git ca-certificates gnupg lsb-releaseA full system upgrade ensures a recent kernel and TLS libraries — critical for Let’s Encrypt certificate negotiation and modern cipher suites in Postfix/Dovecot.
Install Docker Engine using the official method:
curl -fsSL https://get.docker.com | sudo bash
sudo systemctl enable --now dockerThe official script detects the distribution, configures the apt repo and
installs docker-ce + docker-compose-plugin. Confirm with docker --version
and docker compose version (note: compose subcommand without hyphen —
it’s the v2 plugin).
Set the server’s FQDN hostname:
sudo hostnamectl set-hostname mail.yourdomain.comEdit /etc/hosts adding the line:
127.0.1.1 mail.yourdomain.com mailPostfix uses the system hostname to introduce itself in EHLO during SMTP handshake. A wrong hostname breaks the rDNS check on remote servers.
Configure the firewall opening the required ports:
sudo ufw allow 22/tcp
sudo ufw allow 25,80,110,143,443,465,587,993,995,4190/tcp
sudo ufw enableBefore enabling UFW, confirm you can reconnect in another SSH session. Enabling the firewall without allowing port 22 will lock you out of the VPS.
Install Mailcow
With the system ready, we clone the official repository and generate the base configuration. The interactive setup asks for the hostname and generates random passwords for the internal services.
Clone the official repository in /opt:
sudo git clone https://github.com/mailcow/mailcow-dockerized /opt/mailcow-dockerized
cd /opt/mailcow-dockerizedThe /opt directory is the convention for the stack — official backup and
update scripts assume this path. Don’t use /root or /home.
Generate the configuration file:
sudo ./generate_config.shWhen it asks for the FQDN hostname, type mail.yourdomain.com (the same as
step 03). The script generates mailcow.conf with random passwords for
MySQL, Redis and the API. Don’t edit the passwords manually — backups
depend on these values.
Bring up the full stack:
sudo docker compose pull
sudo docker compose up -dThe pull downloads ~3 GB of images (Postfix, Dovecot, SOGo, Rspamd, MySQL, Redis, ClickHouse, Nginx, ACME, Watchdog, Netfilter). The up creates all containers in the background. First boot takes 2-3 minutes for MySQL to initialize schemas.
Verify all containers came up:
sudo docker compose psYou should see ~15 containers with status running or healthy. If any
appear restarting, run sudo docker compose logs <container-name> to
investigate — common on first boot while MySQL is still populating.
Configure domain DNS
Mailcow is running, but the domain doesn’t reach it yet. We need to publish 6 DNS records: MX, A, SPF, DKIM, DMARC and autodiscover. These records are what make the server trustworthy for the large providers.
Access the panel for the first time:
https://mail.yourdomain.comDefault login: user admin, password moohoo. Change it immediately in
System → Administrators. Mailcow already generated a Let’s Encrypt
certificate automatically if ports 80 and 443 were reachable when it came
up.
Add the domain in Configuration → Domains → Add domain. Fill in:
yourdomain.com 3072 MB 10 1000 After creating the domain, go to Configuration → Configuration → ARC/DKIM. Mailcow generates the keypair automatically — copy the TXT record shown.
Publish the DNS records in your provider (Cloudflare example):
yourdomain.com. IN A 203.0.113.10
mail.yourdomain.com. IN A 203.0.113.10
yourdomain.com. IN MX 10 mail.yourdomain.com.
yourdomain.com. IN TXT "v=spf1 mx -all"
dkim._domainkey.yourdomain.com. IN TXT "v=DKIM1; k=rsa; p=MIIBI..."
_dmarc.yourdomain.com. IN TXT "v=DMARC1; p=quarantine; rua=mailto:[email protected]"
autodiscover.yourdomain.com. IN CNAME mail.yourdomain.com.
autoconfig.yourdomain.com. IN CNAME mail.yourdomain.com.Replace 203.0.113.10 with your VPS IP and the DKIM key with the actual
value generated in step 10. Propagation takes 5 to 30 minutes depending on
the TTL.
Start DMARC with p=none for 1-2 weeks and monitor the reports at rua=.
Then escalate to quarantine and finally reject once you’re sure SPF and
DKIM pass for 100% of your legitimate outbound mail.
Create mailboxes and test
Domain added and DNS propagated, we now create the first real mailbox and validate bidirectional sending.
In Email → Mailboxes → Add mailbox, fill in user, domain, full name, password and quota. Repeat for each user.
Access the webmail at https://mail.yourdomain.com/SOGo/. Login with the
full email ([email protected]) + the defined password. SOGo delivers
calendar, contacts and ActiveSync for mobile clients with no extra
configuration.
Configure a desktop client (Thunderbird, Outlook, Apple Mail) with
auto-discovery: the autodiscover and autoconfig records you published
in DNS make the client fill in servers and ports on its own. If you
configure manually:
mail.yourdomain.com:993 (SSL/TLS) mail.yourdomain.com:587 (STARTTLS) mail.yourdomain.com:4190 (STARTTLS) Verification
Confirm that deliverability is healthy before migrating real mailboxes or routing the production domain.
Send a test email to a Gmail address and inspect the received message header (in Gmail: three dots → “Show original”). Look for the lines:
spf=pass
dkim=pass
dmarc=pass
If any shows fail or neutral, there’s a DNS error — review SPF, DKIM
and PTR. A quick alternative is sending email to
[email protected], which returns a detailed report in
seconds.
Test reception by sending from Gmail to the address at yourdomain.com. Confirm it arrives in the inbox (not in spam). If it lands in spam, the issue is usually a missing PTR or the VPS IP being on a blocklist — check at mxtoolbox.com/blacklists.
Troubleshooting
Containers keep restarting
Common cause: MySQL couldn’t initialize due to lack of RAM. Run free -h
and confirm at least 4 GB free. If the VPS has 4 GB total and memory is
already consumed, increase swap:
sudo fallocate -l 4G /swapfile && sudo chmod 600 /swapfile
sudo mkswap /swapfile && sudo swapon /swapfile
Add /swapfile none swap sw 0 0 to /etc/fstab to persist.
Let’s Encrypt doesn’t issue certificate
Check the ACME container log:
sudo docker compose logs acme-mailcow
Frequent causes: port 80 blocked by an external firewall, the A record for
mail.yourdomain.com not propagated yet, or Let’s Encrypt rate limit (50
issuances/week per root domain).
Emails go out but land in spam
Check the PTR with dig -x YOUR_IP +short. The result must return
mail.yourdomain.com. exactly — not the provider’s generic hostname. If
it’s wrong, adjust it in the VPS provider panel. Propagation takes up to
24h.
Next steps
With the server running, consider:
- Setting up automated backup with
helper-scripts/backup_and_restore.shfrom the Mailcow repo itself, scheduled via daily cron to an external destination (S3-compatible or another VPS). - Enabling 2FA in the admin panel at Configuration → Administrators → Edit → Two-factor authentication (supports TOTP and Yubikey).
- Migrating old mailboxes via Sync Jobs (Email → Configuration → Sync jobs) — connects to the source IMAP and replicates preserving flags.
- Adjusting sending limits per domain as reputation grows — start with 500/day and ramp gradually up to 5000/day.
If you’re putting this into production and want port 25 already unblocked, PTR configurable from the panel and technical support in English, a Hostini VPS delivers the Linux environment ready to run Mailcow with no friction from provider blocks.
Frequently asked questions
Does Mailcow run on a 2 GB RAM VPS?
Technically it boots, but Rspamd and ClickHouse choke under real load. The official recommended minimum is 6 GB of RAM and 20 GB of disk. For production with 5+ active mailboxes, plan for 8 GB and an NVMe SSD — ClickHouse does intensive log I/O.
Can I install Mailcow alongside other services on the same VPS?
Not recommended. Mailcow occupies ports 25, 465, 587, 110, 143, 993, 995, 80, 443 and 4190, and it takes over the Docker daemon. Conflicts with local Nginx/Apache are common. Use a dedicated VPS for the email stack.
Why is my port 25 blocked and how do I unblock it?
Cloud providers block outbound SMTP (port 25) by default to mitigate spam. You need to open a support ticket with the provider asking for an unblock, justifying the use case (your own transactional email server). Without that, Mailcow receives but cannot send.
Does Mailcow generate the DKIM records automatically?
Yes. After adding the domain in the panel (Configuration → Domains), Mailcow generates the DKIM keypair and shows the TXT record to publish in DNS. The default selector is dkim. You just copy and paste into your DNS provider.
How do I migrate existing mailboxes to Mailcow?
Use the Sync Jobs feature in the panel (Email → Configuration → Sync jobs). Mailcow connects to the source IMAP and replicates messages preserving flags and folder structure. Works with Gmail, Outlook, any IMAP server — as long as you have an app password / IMAP enabled.
Do I need reverse DNS (PTR) configured?
Yes, mandatory. Without a valid PTR pointing to the server hostname (mail.yourdomain.com), Gmail, Outlook and most large providers reject or mark messages as spam. Configure the PTR in your VPS provider panel before sending the first email.