Tutorial

Business Email

Hosting Your Own Email Server on a VPS with Mailcow

In July 2026, Microsoft raised Microsoft 365 prices by up to 43% depending on the plan (12 to 16% on Business Basic and Standard) — a shock that pushed thousands of SMBs to reconsider their email strategy. On top of that, Microsoft announced the end of Exchange Web Services (EWS) in October 2026, rendering dozens of third-party integrations obsolete and forcing urgent migrations. In this context, self-hosting your email is no longer a hobbyist project: it is a strategic, financial, and data sovereignty decision. Mailcow, with over 13,200 GitHub stars, has established itself as the reference solution for organizations that want to take back control of their messaging without sacrificing the comfort of a modern interface. This guide walks you through deploying Mailcow on a Linux VPS, step by step.

20 min read19 steps
Contents· Why Self-Host Your Email in 20261/14
  1. 01Why Self-Host Your Email in 2026
  2. 02What Mailcow Includes Out of the Box
  3. 03Technical and Network Prerequisites
  4. 04Installing Mailcow in 8 Steps
  5. 05Deliverability: Pitfalls to Avoid
  6. 06Migrating from Google Workspace or Microsoft 365 to Mailcow
  7. 07Comparison: Mailcow vs Mailu vs iRedMail
  8. 08Maintenance and Version Upgrades
  9. 09Updating Mailcow urgently: the PHP2Shell case
  10. 10Emergency procedure in six steps
  11. 11Troubleshooting: the netfilter container restarts in a loop
  12. 12Diagnose, then work around, from safest to riskiest
  13. 13When Self-Hosting Email Is Not Advisable
  14. 14Conclusion: An Email Server That Belongs to You

Why Self-Host Your Email in 2026

Microsoft's price increase for Microsoft 365, effective 1 July 2026 — up to 43% depending on the plan was a wake-up call for many SMBs. An organization with 20 employees that was paying around €200 per month now faces bills of €285 or more, with no meaningful improvement in features. And that is not all: Microsoft has officially scheduled the shutdown of Exchange Web Services (EWS) for October 2026, making obsolete dozens of third-party integrations that relied on this protocol — shared calendars, CRM tools, in-house automations.

Faced with these pricing and technical disruptions, self-hosted email has returned to the conversation with renewed legitimacy. Hosting your own mail server on a VPS offers three major advantages: full cost control (a properly sized VPS costs between €15 and €40 per month for 20 to 50 mailboxes), data sovereignty (your messages do not transit through a hyperscaler's datacenters), and simplified GDPR compliance (you know exactly where your data is stored and processed). For law firms, healthcare professionals, communication agencies, or any organization handling sensitive data, this last argument is often decisive.

What Mailcow Includes Out of the Box

  • Postfix + Dovecot: the time-tested open-source email duo — Postfix handles inbound and outbound SMTP, while Dovecot provides IMAP and POP3 access for your email clients.
  • Rspamd: a next-generation spam filtering engine far more accurate than SpamAssassin, with adaptive Bayes, greylisting, DKIM, ARC, and multi-factor scoring built in.
  • SOGo: a full-featured webmail client (calendars, contacts, tasks) with ActiveSync synchronization for Outlook and mobile devices, without needing Exchange.
  • Web administration panel: a modern control panel to manage domains, mailboxes, aliases, quotas, mailing lists, and DKIM policies in just a few clicks.
  • WebAuthn / two-factor authentication: enhanced security via hardware keys (YubiKey, etc.) or TOTP to protect admin access and individual user mailboxes.
  • Full REST API: all administration actions are exposed through a documented API, enabling automation of mailbox and alias creation, or integration into your own customer portal.
  • Built-in IMAP synchronization: the interface's sync jobs (*E-Mail Configuration → Synchronizations*) copy messages from a source server (Gmail, Microsoft 365, cPanel…) into a Mailcow mailbox, without any command-line work.
  • Automatic TLS certificates: Let's Encrypt is natively integrated — Mailcow renews certificates for your mail domain with no manual intervention required.

Technical and Network Prerequisites

Before starting the installation, make sure your VPS and network configuration meet the following requirements. On the hardware side, the Mailcow documentation asks for at least 6 GB of RAM plus 1 GB of swap in the default configuration: below that, you are outside what the authors plan for, and the hungriest containers (antispam, antivirus, database) are the first to suffer. Plan for 2 vCPUs and at least 20 GB of disk space for containers and emails — 50 GB is a reasonable starting point for a small organization.

On the network side, several points are non-negotiable. First, port 25 must be open outbound: many hosting providers block it by default to fight spam. Check with your provider before ordering. Second, you must be able to configure the rDNS (reverse DNS / PTR record) for your IP: this allows recipient servers to verify that your IP actually maps to your mail hostname — without a correct PTR, your emails will land in spam at most major providers. Third, you need a domain name whose DNS you control (adding MX, A, and TXT records). Finally, a VPS running Debian 12 or Ubuntu 24.04 LTS is recommended for optimal compatibility with Docker and the Mailcow scripts. The Mailcow documentation lists Debian 11 to 13, Ubuntu 22.04 and newer, AlmaLinux 8 and 9, and Rocky Linux 9 as compatible; Alpine appears only with limited compatibility and manual adjustments.

Installing Mailcow in 8 Steps

  1. Provision the VPS and Configure rDNS

    Order a VPS with at least 6 GB of RAM, 2 vCPUs, and a dedicated IP. In your hosting control panel, navigate to the rDNS (or PTR record) section and map your IP to mail.yourdomain.com. This setting is critical: without a correct PTR, your emails will be systematically rejected or marked as spam by Gmail, Outlook, and other major providers. Allow up to 24 hours for PTR propagation.

  2. Point Your Domain's DNS Records

    In your DNS manager, create an A (or AAAA) record for mail.yourdomain.com pointing to your VPS IP. Add an MX record on yourdomain.com with the value mail.yourdomain.com and a priority of 10. Also prepare your SPF record (v=spf1 mx ~all) and DMARC record (_dmarc.yourdomain.com) — DKIM will be automatically generated by Mailcow when it first starts.

  3. Install Docker and Docker Compose

    Connect to your VPS via SSH as root. Install Docker using the official script: curl -fsSL https://get.docker.com | sh. Docker Compose v2 is included in modern Docker packages. Verify the versions with docker --version and docker compose version. Ensure the Docker service starts automatically with systemctl enable --now docker.

  4. Clone the Repository and Generate the Configuration

    Clone the official repository into /opt/mailcow-dockerized: git clone https://github.com/mailcow/mailcow-dockerized /opt/mailcow-dockerized. Enter the directory and run ./generate_config.sh. The script asks for your mail hostname (e.g., mail.yourdomain.com) and timezone. It generates the mailcow.conf file with secure random keys — do not manually modify these keys afterward.

  5. Start Mailcow

    From /opt/mailcow-dockerized, run docker compose pull to download all images (approximately 2 to 4 GB depending on your connection), then docker compose up -d to start all containers in the background. The first startup takes 2 to 5 minutes. Check container status with docker compose ps — all should show a status of Up or Up (healthy).

  6. Access the UI and Create Your First Domain

    Open your browser at https://mail.yourdomain.com. Default credentials are admin / moohoo. Change the admin password immediately. Under *Configuration → Domains*, add your domain. Then create your first mailbox under *Configuration → Mailboxes*. The interface is intuitive and well-organized.

  7. Enable DKIM and Test Deliverability

    Under *Configuration → ARC/DKIM Keys*, select your domain and click *Generate new key* (2048-bit length recommended). Copy the generated TXT record and add it to your DNS manager (TXT record on dkim._domainkey.yourdomain.com). Then send a test email from your new mailbox to [email protected], or use mail-tester.com for a comprehensive deliverability score.

  8. Set Up Automated Backups

    Mailcow provides an official backup script at helper-scripts/backup_and_restore.sh. Schedule a daily run via cron: 0 3 * * * MAILCOW_BACKUP_LOCATION=/opt/backup /opt/mailcow-dockerized/helper-scripts/backup_and_restore.sh backup all. The MAILCOW_BACKUP_LOCATION variable skips the interactive question about the location; then ship that folder to external storage (NFS mount, S3 bucket, remote directory via rsync). Add --delete-days 3 to the command to purge older copies. The documentation asks you not to copy the script elsewhere: call it from its original folder. Test a restore before considering the backup operational.

Deliverability: Pitfalls to Avoid

A technically correct mail server can still have its messages land in spam if certain network and DNS conditions are not met. Here are the most common mistakes.

Blacklists: your VPS IP may be listed in blacklists like Spamhaus or Barracuda if it was previously used for spam — this is common with datacenter IP ranges. Check your IP on mxtoolbox.com/blacklists.aspx before installation. If listed, request delisting from each organization or switch IPs.

Port 25 blocked: some hosting providers block outbound port 25 on all new IPs for a probation period. Contact support before starting.

Missing or misconfigured DKIM: an email without a valid DKIM signature is treated with suspicion by Gmail and Outlook. Verify with dig TXT dkim._domainkey.yourdomain.com that the record is visible.

DMARC too permissive: start with p=none to collect reports without blocking, then move to p=quarantine and p=reject once you are confident all legitimate mail streams are covered by SPF and DKIM.

Missing PTR record: the most commonly forgotten point. Without a PTR record resolving to your mail hostname, Microsoft Outlook will reject your emails with a 550 error.

Mailcow behind Nginx Proxy Manager: if your VPS hosts other web services (website, application), you can run Mailcow alongside Nginx Proxy Manager on the same IP. The key is to let NPM handle only ports 80 and 443 (HTTP/HTTPS) as a reverse proxy, while Mailcow retains direct control over SMTP ports (25, 465, 587) and IMAP/POP3 (993, 995). In mailcow.conf, set HTTP_PORT=8080, HTTP_BIND=127.0.0.1, HTTPS_PORT=8443 and HTTPS_BIND=127.0.0.1, apply with docker compose up -d, then configure NPM to proxy to http://127.0.0.1:8080 (the port used by the Nginx example in the Mailcow documentation). Never try to route port 25 through an HTTP reverse proxy — SMTP does not work that way.

Migrating from Google Workspace or Microsoft 365 to Mailcow

Migrating existing emails is often what holds organizations back. Mailcow includes IMAP sync jobs directly in its interface, making the operation accessible without advanced technical skills.

The basic procedure is: (1) create the destination mailboxes in Mailcow, (2) open *E-Mail Configuration → Synchronizations* in the admin interface (a user has the same tool under *User Settings → Sync Jobs*), (3) enter the host, port, username and password of the source server, choose the encryption — TLS usually fits port 143, SSL fits port 993, and PLAIN is strongly discouraged — then enable the job. It copies messages folder by folder. The migration can be run multiple times before the final cutover (already-copied messages are skipped on subsequent passes), enabling a progressive cutover: you continue receiving mail on the old address while data migrates, then switch the MX record in a single operation. Once the switch is done, disable or delete the job.

For Microsoft 365, do not count on EWS: according to Microsoft's documentation, it starts being disabled for all organizations in October 2026 and will be fully disabled in April 2027, so migration tools that rely on it will stop working. Mailcow uses IMAP, not EWS, and is therefore unaffected — yet another reason to migrate early.

For email clients, Thunderbird, Apple Mail, and mobile apps (iOS, Android) configure via standard IMAP/SMTP. Outlook works too, including calendar and contact sync via ActiveSync (SOGo).

Comparison: Mailcow vs Mailu vs iRedMail

Mailcow is not the only Docker-based self-hosted email solution. Here is how it compares to its main competitors.

Mailu is lighter on resources (runs with 1 GB of RAM) and suits personal use or very small organizations. Its interface is minimalist, it does not include SOGo, and its feature set is more limited. It is an excellent choice when simplicity is a priority over features.

iRedMail is a mature solution, available as a community edition and a paid Pro version (iRedMail Pro). It supports multiple database backends (MySQL, PostgreSQL, LDAP) and installs directly on the host system without Docker. Its configuration curve is steeper, and updates are less automated. iRedMail Pro adds a paid web management interface that competes with Mailcow's.

Mailcow stands out for the richness of its features, the quality of its administration interface, the frequency of its updates (monthly releases), and the size of its community (13,200+ GitHub stars). It is the natural choice for an SMB that wants a complete, actively maintained solution with abundant documentation. The trade-off is RAM consumption: 6 GB minimum is a non-negotiable requirement.

Maintenance and Version Upgrades

Mailcow publishes one release per month, and its tag carries the year and month (2026-10 for the one of 21 October 2026). They fix security vulnerabilities, improve spam filters, and add features. The official update procedure is straightforward and well-documented.

From the /opt/mailcow-dockerized directory, run ./update.sh. This script pulls the new Docker images, applies any configuration migrations, and restarts the affected containers. The operation typically takes 5 to 15 minutes with less than a minute of service interruption for the restarted containers.

Do not use Watchtower (the automatic Docker container update tool) with Mailcow. Mailcow updates sometimes involve database migrations or configuration changes that require running update.sh in the correct order. Letting Watchtower pull and restart images autonomously can corrupt your installation.

Schedule updates during a known maintenance window (for example, Saturday morning), read the release note on the Mailcow repository's releases page before applying, and verify container status with docker compose ps after each update. Take a full backup right before each update. When the release is a security update, do not wait: the next section gives the emergency procedure.

Updating Mailcow urgently: the PHP2Shell case

Mailcow is not a PHP application sitting on your system: it ships its own PHP, in the php-fpm-mailcow container, which runs the admin interface. Your distribution's PHP fixes therefore do not apply to it, and an apt upgrade on the VPS changes nothing about the version Mailcow uses. That is what prompted thread #7533, opened on 21 October 2026 under the title "[UPDATE NOW] PHP RCE Vulnerability (CVE-2026-109618 and CVE-2026-109619)": administrators there ask for a fix from Mailcow itself. Both flaws are detailed in our article [PHP2Shell: fixing CVE-2026-109618 and CVE-2026-109619](/blog/php2shell-cve-2026-109618-109619-mettre-a-jour-php).

On the version side, the 2026-10 release, published on 21 October 2026, is titled "Security Update: PHP 8.2.35, SOGo 5.12.12 & Nginx 1.30.6". Its notes do not cite the CVE numbers: the link is made through the PHP version, since 8.2.35 is the fixed version of the 8.2 branch in the PHP project's advisory of the same day, and the phpfpm image shipped with that release carries the tag 8.2.35-1. Read the release note before acting: a newer release may have changed these numbers.

Emergency procedure in six steps

  1. Back up first

    From any folder, run MAILCOW_BACKUP_LOCATION=/opt/backup /opt/mailcow-dockerized/helper-scripts/backup_and_restore.sh backup all. Check that the destination folder actually contains files before moving on, and keep a copy off the VPS: a backup that lives on the machine you are about to modify protects nothing.

  2. Read what the update will change

    In /opt/mailcow-dockerized, ./update.sh --check tells you whether an update exists and what it changes, without applying anything. Open the release note alongside and look for the "Security" heading: if the PHP numbers are not there, it is up to you to compare them with the PHP project's advisory.

  3. Apply the update

    Run ./update.sh from the same folder and answer the script's questions. Do not use --force, which the documentation presents as unsupported, nor --ours, which it advises against: they hide file conflicts instead of resolving them. Plan for a few minutes of downtime.

  4. Check the PHP version inside the container

    docker compose exec php-fpm-mailcow php -v must show 8.2.35 or a newer version. docker compose images php-fpm-mailcow gives the image and its tag (8.2.35-1 for the 2026-10 release). The php -v of the VPS itself, outside the container, says nothing about Mailcow: the check inside the container is the one that counts.

  5. Check the state of the stack

    docker compose ps must show every container as Up or Up (healthy). Then run docker compose logs --tail 50 php-fpm-mailcow to look for a startup error, open the web interface and send a test message from a mailbox to an external address.

  6. If you cannot update right away

    Thread #7533 listed stopgaps before the fix was released: session.upload_progress.enabled = Off (judged incomplete by the thread's author), an nginx rule that answers 403 to multipart requests, and binding the interface to 127.0.0.1 via HTTP_BIND and HTTPS_BIND, which also cuts direct access to the interface and, one participant points out, makes MTA-STS unavailable. These buy a few hours, they are not a fix. If the update goes wrong, the documentation provides a rollback: docker compose down, git checkout <commit ID>, docker compose pull, then docker compose up -d; update.sh leaves a "Before update" commit to find with git log.

Troubleshooting: the netfilter container restarts in a loop

On a VPS that already hosts other Docker containers, the netfilter-mailcow container can start restarting endlessly. The visible symptom: the web interface stops responding, the unbound-mailcow container is reported as failing ("dependency failed to start: container mailcowdockerized-unbound-mailcow-1 is unhealthy") and netfilter's logs repeat "MAILCOW target not found in ip forward table, restarting container to fix it...".

Reported cause: at startup, netfilter checks the position of its MAILCOW chain in the FORWARD table. According to thread #6795, the code in NFTables.py assumes the chain sits in first position and places it by insertion; if Docker has already created its own chains, the check fails, the container exits, and each restart adds one more MAILCOW jump. In #6995, one participant counts more than 9,000 of them, and another describes the same effect after a Docker update on Debian.

What we know about the status: #6795, opened in September 2025, was closed as "not planned" in December 2025 after a "stale" label; #6995, opened in January 2026, was closed the same way in October 2026. No maintainer confirms a fix there, and one participant believes the root cause needs a refactor. What follows is therefore a community workaround, not an official procedure.

Diagnose, then work around, from safest to riskiest

  1. Confirm the duplicates

    docker compose logs --tail 50 netfilter-mailcow shows the messages above. iptables -S FORWARD | grep -c MAILCOW counts the jumps to the MAILCOW chain: one is expected, and more confirms the diagnosis. Repeat the test with ip6tables if IPv6 is enabled.

  2. Avoid the problem at the source

    The safest course is not to run Mailcow alongside other Docker stacks: a machine dedicated to mail avoids the collision. The Mailcow documentation also flags several problems with an active ufw or firewalld, recommends disabling it and moving your rules to the DOCKER-USER chain, and states that INPUT rules have no effect on Mailcow.

  3. Clean up the duplicates

    List the rules with iptables -t filter -L FORWARD --line-numbers, spot the lines that jump to MAILCOW, then delete them with iptables -t filter -D FORWARD <number>, recounting after each deletion because the numbers shift. Restart the stack with docker compose down && docker compose up -d. Do not touch the DOCKER rules of your other containers. This cleanup is temporary: if the cause is still there, the duplicates come back.

  4. Hook MAILCOW into DOCKER-USER

    In #6995, one participant reports keeping bans working by hooking the chain into DOCKER-USER: iptables -I DOCKER-USER 1 -m comment --comment "mailcow bans" -j MAILCOW, with the equivalent ip6tables command. Another proposes an unofficial netfilter test image, proven only with nftables. Upstream validates neither route: try them on a disposable VM, with a snapshot, before touching production.

  5. Remove the isolation rule, as a last resort

    If netfilter's isolation rule blocks legitimate flows, DISABLE_NETFILTER_ISOLATION_RULE=y in mailcow.conf, followed by docker compose up -d, removes it. The file's comment warns that this may expose container ports to neighbors on the same subnet, even when they are bound to localhost: keep this setting for a machine dedicated to Mailcow.

A VPS for mail alone: the sizing of 6 GB plus swap assumes Mailcow has the machine to itself. On a VPS shared with a showcase site or a containerized application, you add up the RAM of each stack, the firewall-rule collision risk described above, and one more PHP to watch at every security advisory. Separating mail from the rest costs one more VPS, but removes all three issues at once.

When Self-Hosting Email Is Not Advisable

Self-hosting email is not a universal answer. There are situations where Google Workspace or Microsoft 365 remains the more sensible choice, even after the price increase.

If you lack in-house technical expertise: managing a mail server requires regular security monitoring, proactive updates, and the ability to diagnose deliverability issues. Without these skills, you risk being left without email during a critical incident.

If deliverability is absolutely critical: large-scale transactional email operations (banks, insurers, high-volume e-commerce platforms) benefit from the reputation of shared IPs professionally managed by ESPs (Mailgun, Postmark, Brevo). A solo VPS starts with a fresh IP whose reputation must be built from scratch.

If your hosting provider blocks port 25: some cloud operators (particularly on entry-level plans) permanently block this port to combat spam. Verify before committing.

If you operate in a strictly regulated industry: some sectors (finance, healthcare) impose certification or audit requirements for email infrastructure that can be difficult to satisfy with self-hosting without specialized support.

Conclusion: An Email Server That Belongs to You

Self-hosting your email server with Mailcow on a VPS is now within reach of any organization with a minimum of technical maturity. The solution is mature, actively maintained, and delivers a functional level that honestly competes with commercial offerings — at a fraction of the cost.

Microsoft 365's price increase and the end of EWS in 2026 have transformed self-hosting from a fringe option into a strategic alternative for thousands of SMBs and agencies. With a properly sized VPS (6 GB RAM, dedicated IP with configured rDNS), a Mailcow installation takes less than an hour, and the migration of existing mailboxes can be done progressively without service interruption.

Three things to remember before getting started: verify that port 25 is open with your hosting provider, configure the PTR record for your IP before sending your very first email, and set up external backups from day one. With these foundations in place, your email server will be just as reliable — and far more cost-effective — than a cloud subscription.

Add a fourth reflex to these three: Mailcow carries its own PHP, so every PHP security advisory is handled with ./update.sh followed by a check inside the container, as with PHP2Shell in October 2026.

A VPS for Your Email Server

Mailcow requires at least 6 GB of RAM and 2 vCPUs. Our Linux VPS plans are delivered with Debian 12 or Ubuntu 24.04, root access, a dedicated IP, and configurable rDNS — essential for email deliverability.