How to install Mailcow

How to install Mailcow

Mailcow: dockerized is an open source groupware/email suite based on docker.

mailcow relies on many well known and long used components, which in combination result in an all around carefree email server.

Each container represents a single application, connected in a bridged network:

  • ACME Automatic generation of Let’s Encrypt certificates
  • ClamAV Antivirus scanner (optional)
  • Dovecot IMAP/POP server for retrieving e-mails
  • MariaDB Database for storing user information etc.
  • Memcached Cache for the webmailer SOGo
  • Netfilter Fail2ban-like integration
  • Nginx Web server for components of the stack
  • Olefy Analysis of Office documents for viruses, macros, etc.
  • PHP Programming language of most web-based mailcow applications
  • Postfix MTA (Mail Transfer Agent) for e-mail traffic on the Internet
  • Redis Storage for spam information, DKIM key, etc.
  • Rspamd Spam filter with automatic learning of spam mails
  • SOGo Integrated webmailer and Cal-/Carddav interface
  • Solr Full text search for IMAP connections to quickly search emails (Deprecated) (Optional)
  • Unbound Integrated DNS server for verifying DNSSEC etc.
  • Watchdog For basic monitoring of the container status within mailcow

But the heart of mailcow is the graphical web interface, the mailcow UI.

It offers a place for almost all settings and allows the comfortable creation of new domains and email addresses with just a few clicks.

But also other or more tricky tasks can be done in it with ease:

  • DKIM and ARC support/generation.
  • Black and white lists per domain and per user.
  • Spam score management per user (reject spam, flag spam, greylist).
  • Allow mailbox users to create temporary spam aliases
  • Prepend email tags to subject or move emails to subfolders (per user)
  • Allow mailbox users to toggle TLS enforcement for inbound and outbound messages
  • Users can reset caches on SOGo ActiveSync devices
  • imapsync to periodically migrate or retrieve remote mailboxes
  • TFA: Yubikey OTP and WebAuthn USB (Google Chrome and derivatives only), TOTP
  • Add whitelist hosts to forward mail to mailcow
  • Fail2ban-like integration
  • Quarantine system
  • Anti-virus scanning including macro scanning in Office documents
  • Integrated basic monitoring
  • And much more…

Before you run mailcow: dockerized, there are a few requirements that you should check:

Warning

Do not try to install mailcow on a Synology/QNAP device (any NAS), OpenVZ, LXC or other container platforms. KVM, ESX, Hyper-V and other full virtualization platforms are supported.

Minimum System Resources

Compatibility established

Since Update 2024-01 mailcow is finally available on ARM64 platforms! Completely! Without any restrictions in functionality!

Please make sure that your system has at least the following resources:

Resource Minimal Requirement
CPU 1 GHz
RAM Minimum 6 GiB + 1 GiB swap (default config)
Disk 20 GiB (without emails)
Architecture x86_64, ARM64

Not supported

OpenVZ, Virtuozzo and LXC

ClamAV and Solr can be greedy with RAM. You may disable them in mailcow.conf by settings SKIP_CLAMD=y and SKIP_SOLR=y.

RAM usage examples

A company with 15 phones (EAS enabled) and about 50 concurrent IMAP connections should plan 16 GiB RAM.

6 GiB RAM + 1 GiB swap are fine for most private installations while 8 GiB RAM are recommended for ~5 to 10 users.

We can help to correctly plan your setup as part of our support.

Supported OS

Basically, mailcow can be used on any distribution that is supported by Docker CE (see https://docs.docker.com/install/). However, in some cases there may be incompatibilities between the operating systems and the mailcow components.

The following table contains all operating systems officially supported and tested by us (as of June 2023):

OS Compatibility
Alpine since 3.17 ⚠️
Centos 7
Debian 10, 11, 12
Ubuntu 18.04, 20.04, 22.04
Alma Linux 8
Rocky Linux 9

Legend

✅ = Works out of the box using the instructions.
⚠️ = Requires some manual adjustments otherwise usable.
❌ = In general NOT Compatible.
❔ = Pending.

Warning

Note: All other operating systems (not mentioned) may also work, but have not been officially tested.

Firewall & Ports

Please check if any of mailcow’s standard ports are open and not in use by other applications:

ss -tlpn | grep -E -w '25|80|110|143|443|465|587|993|995|4190'
# or:
netstat -tulpn | grep -E -w '25|80|110|143|443|465|587|993|995|4190'

Danger

There are several problems with running mailcow on a firewalld/ufw enabled system.
You should disable it (if possible) and move your ruleset to the DOCKER-USER chain, which is not cleared by a Docker service restart, instead.
See this (blog.donnex.net) or this (unrouted.io) guide for information about how to use iptables-persistent with the DOCKER-USER chain.
As mailcow runs dockerized, INPUT rules have no effect on restricting access to mailcow.
Use the FORWARD chain instead.

If this command returns any results please remove or stop the application running on that port. You may also adjust mailcows ports via the mailcow.conf configuration file.

Default Ports

If you have a firewall in front of mailcow, please make sure that these ports are open for incoming connections:

Service Protocol Port Container Variable
Postfix SMTP TCP 25 postfix-mailcow ${SMTP_PORT}
Postfix SMTPS TCP 465 postfix-mailcow ${SMTPS_PORT}
Postfix Submission TCP 587 postfix-mailcow ${SUBMISSION_PORT}
Dovecot IMAP TCP 143 dovecot-mailcow ${IMAP_PORT}
Dovecot IMAPS TCP 993 dovecot-mailcow ${IMAPS_PORT}
Dovecot POP3 TCP 110 dovecot-mailcow ${POP_PORT}
Dovecot POP3S TCP 995 dovecot-mailcow ${POPS_PORT}
Dovecot ManageSieve TCP 4190 dovecot-mailcow ${SIEVE_PORT}
HTTP(S) TCP 80/443 nginx-mailcow ${HTTP_PORT} / ${HTTPS_PORT}

To bind a service to an IP address, you can prepend the IP like this: SMTP_PORT=1.2.3.4:25

Important: You cannot use IP:PORT bindings in HTTP_PORT and HTTPS_PORT. Please use HTTP_PORT=1234 and HTTP_BIND=1.2.3.4 instead.

Important for Hetzner firewalls

Quoting https://github.com/chermsen via https://github.com/mailcow/mailcow-dockerized/issues/497#issuecomment-469847380 (THANK YOU!):

For all who are struggling with the Hetzner firewall:

Port 53 unimportant for the firewall configuration in this case. According to the documentation unbound uses the port range 1024-65535 for outgoing requests. Since the Hetzner Robot Firewall is a static firewall (each incoming packet is checked isolated) – the following rules must be applied:

For TCP

SRC-IP:       ---
DST IP:       ---
SRC Port:    ---
DST Port:    1024-65535
Protocol:    tcp
TCP flags:   ack
Action:      Accept

For UDP

SRC-IP:       ---
DST IP:       ---
SRC Port:    ---
DST Port:    1024-65535
Protocol:    udp
Action:      Accept

If you want to apply a more restrictive port range you have to change the config of unbound first (after installation):

{mailcow-dockerized}/data/conf/unbound/unbound.conf:

outgoing-port-avoid: 0-32767

Now the firewall rules can be adjusted as follows:

[...]
DST Port:  32768-65535
[...]

Date and Time

To ensure that you have the correct date and time setup on your system, please check the output of timedatectl status:

$ timedatectl status
      Local time: Sat 2017-05-06 02:12:33 CEST
  Universal time: Sat 2017-05-06 00:12:33 UTC
        RTC time: Sat 2017-05-06 00:12:32
       Time zone: Europe/Berlin (CEST, +0200)
     NTP enabled: yes
NTP synchronized: yes
 RTC in local TZ: no
      DST active: yes
 Last DST change: DST began at
                  Sun 2017-03-26 01:59:59 CET
                  Sun 2017-03-26 03:00:00 CEST
 Next DST change: DST ends (the clock jumps one hour backwards) at
                  Sun 2017-10-29 02:59:59 CEST
                  Sun 2017-10-29 02:00:00 CET

The lines NTP enabled: yes and NTP synchronized: yes indicate whether you have NTP enabled and if it’s synchronized.

To enable NTP you need to run the command timedatectl set-ntp true. You also need to edit your /etc/systemd/timesyncd.conf:

# vim /etc/systemd/timesyncd.conf
[Time]
NTP=0.pool.ntp.org 1.pool.ntp.org 2.pool.ntp.org 3.pool.ntp.org

Hetzner Cloud (and probably others)

Check /etc/network/interfaces.d/50-cloud-init.cfg and change the IPv6 interface from eth0:0 to eth0:

# Wrong:
auto eth0:0
iface eth0:0 inet6 static
# Right:
auto eth0
iface eth0 inet6 static

Reboot or restart the interface. You may want to disable cloud-init network changes.

MTU

Especially relevant for OpenStack users: Check your MTU and set it accordingly in docker-compose.yml. See Troubleshooting in our Installation guide.

DNS setup

Below you can find a list of recommended DNS records. While some are mandatory for a mail server (A, MX), others are recommended to build a good reputation score (TXT/SPF) or used for auto-configuration of mail clients (SRV).

References

Reverse DNS of your IP address

Make sure that the PTR record of your IP address matches the FQDN of your mailcow host: ${MAILCOW_HOSTNAME} 1. This record is usually set at the provider you leased the IP address (server) from.

The minimal DNS configuration

This example shows you a set of records for one domain managed by mailcow. Each domain that is added to mailcow needs at least this set of records to function correctly.

# Name              Type       Value
mail                IN A       1.2.3.4
autodiscover        IN CNAME   mail.example.org. (your ${MAILCOW_HOSTNAME})
autoconfig          IN CNAME   mail.example.org. (your ${MAILCOW_HOSTNAME})
@                   IN MX 10   mail.example.org. (your ${MAILCOW_HOSTNAME})

Note: The mail DNS record which binds the subdomain to the given ip address must only be set for the domain on which mailcow is running and that is used to access the web interface. For every other mailcow managed domain, the MX record will route the traffic.

DKIM, SPF and DMARC

In the example DNS zone file snippet below, a simple SPF TXT record is used to only allow THIS server (the MX) to send mail for your domain. Every other server is disallowed but able to (“~all“). Please refer to SPF Project for further reading.

# Name              Type       Value
@                   IN TXT     "v=spf1 mx a -all"

It is highly recommended to create a DKIM TXT record in your mailcow UI and set the corresponding TXT record in your DNS records. Please refer to OpenDKIM for further reading.

# Name              Type       Value
dkim._domainkey     IN TXT     "v=DKIM1; k=rsa; t=s; s=email; p=..."

The last step in protecting yourself and others is the implementation of a DMARC TXT record, for example by using the DMARC Assistant (check).

# Name              Type       Value
_dmarc              IN TXT     "v=DMARC1; p=reject; rua=mailto:[email protected]"

The advanced DNS configuration

SRV records specify the server(s) for a specific protocol on your domain. If you want to explicitly announce a service as not provided, give “.” as the target address (instead of “mail.example.org.”). Please refer to RFC 2782.

# Name              Type       Priority Weight Port    Value
_autodiscover._tcp  IN SRV     0        1      443      mail.example.org. (your ${MAILCOW_HOSTNAME})
_caldavs._tcp       IN SRV     0        1      443      mail.example.org. (your ${MAILCOW_HOSTNAME})
_caldavs._tcp       IN TXT                              "path=/SOGo/dav/"
_carddavs._tcp      IN SRV     0        1      443      mail.example.org. (your ${MAILCOW_HOSTNAME})
_carddavs._tcp      IN TXT                              "path=/SOGo/dav/"
_imap._tcp          IN SRV     0        1      143      mail.example.org. (your ${MAILCOW_HOSTNAME})
_imaps._tcp         IN SRV     0        1      993      mail.example.org. (your ${MAILCOW_HOSTNAME})
_pop3._tcp          IN SRV     0        1      110      mail.example.org. (your ${MAILCOW_HOSTNAME})
_pop3s._tcp         IN SRV     0        1      995      mail.example.org. (your ${MAILCOW_HOSTNAME})
_sieve._tcp         IN SRV     0        1      4190     mail.example.org. (your ${MAILCOW_HOSTNAME})
_smtps._tcp         IN SRV     0        1      465      mail.example.org. (your ${MAILCOW_HOSTNAME})
_submission._tcp    IN SRV     0        1      587      mail.example.org. (your ${MAILCOW_HOSTNAME})

Testing

Here are some tools you can use to verify your DNS configuration:

Misc

Optional DMARC Statistics

If you are interested in statistics, you can additionally register with some of the many below DMARC statistic services – or self-host your own.

Tip

It is worth considering that if you request DMARC statistic reports to your mailcow server and your mailcow server is not configured correctly to receive these reports, you may not get accurate and complete results. Please consider using an alternative email domain for receiving DMARC reports.

It is worth mentioning, that the following suggestions are not a comprehensive list of all services and tools available, but only a small few of the many choices.

Tip

These services may provide you with a TXT record you need to insert into your DNS records as the provider specifies. Please ensure you read the provider’s documentation from the service you choose as this process may vary.

Email test for SPF, DKIM and DMARC:

To run a rudimentary email authentication check, send a mail to check-auth at verifier.port25.com and wait for a reply. You will find a report similar to the following:

==========================================================
Summary of Results
==========================================================
SPF check:          pass
"iprev" check:      pass
DKIM check:         pass
DKIM check:         pass
SpamAssassin check: ham

==========================================================
Details:
==========================================================
....

The full report will contain more technical details.

Fully Qualified Domain Name (FQDN)


  1. A Fully Qualified Domain Name (FQDN) is the complete (absolute) domain name for a specific computer or host, on the Internet. The FQDN consists of at least three parts divided by a dot: the hostname, the domain name, and the Top Level Domain (TLD for short). In the example of mx.mailcow.email the hostname would be mx, the domain name mailcow and the TLD email.

Install mailcow

It’s the same :

The installation is exactly the same on x86 and ARM64 platforms!

Docker and Docker Compose Installation

You need Docker (a version >= 20.10.2 is required) and Docker Compose (a version >= 2.0 is required).

Learn how to install Docker and Docker Compose.

Quick installation for most operation systems:

Docker

curl -sSL https://get.docker.com/ | CHANNEL=stable sh
# After the installation process is finished, you may need to enable the service and make sure it is started (e.g. CentOS 7)
systemctl enable --now docker

docker compose

Danger

mailcow requires the latest version of docker compose v2.
If Docker was installed using the script above, the Docker Compose plugin is already automatically installed in a version >=2.0.
Is your mailcow installation older or Docker was installed in a different way, the Compose plugin or the standalone version of Docker must be installed manually.

Installation via Paketmanager (plugin)

Info

This approach with the package sources is only possible if the Docker repository has been included. This can happen either through the instructions above (see Docker) or through a manually integration.

On Debian/Ubuntu systems:

apt update
apt install docker-compose-plugin

On Centos 7 systems:

yum update
yum install docker-compose-plugin

Danger

The Docker Compose command syntax is docker compose for the plugin variant of Docker Compose!!!

Installation via Script (standalone)

Info

This installation is the old familiar way. It installs Docker Compose as a standalone program and does not rely on the Docker installation way.

LATEST=$(curl -Ls -w %{url_effective} -o /dev/null https://github.com/docker/compose/releases/latest) && LATEST=${LATEST##*/} && curl -L https://github.com/docker/compose/releases/download/$LATEST/docker-compose-$(uname -s)-$(uname -m) > /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose

Danger

The Docker Compose command syntax is docker-compose for the standalone variant of Docker Compose!!!

Please use the latest Docker engine available and do not use the engine that ships with your distros repository.

Check SELinux specifics

On SELinux enabled systems, e.g. CentOS 7:

  • Check if “container-selinux” package is present on your system:
rpm -qa | grep container-selinux

If the above command returns an empty or no output, you should install it via your package manager.

  • Check if docker has SELinux support enabled:
docker info | grep selinux

If the above command returns an empty or no output, create or edit /etc/docker/daemon.json and add "selinux-enabled": true. Example file content:

{
  "selinux-enabled": true
}

Restart the docker daemon and verify SELinux is now enabled.

This step is required to make sure mailcows volumes are properly labeled as declared in the compose file. If you are interested in how this works, you can check out the readme of https://github.com/containers/container-selinux which links to a lot of useful information on that topic.

Install mailcow

Clone the master branch of the repository, make sure your umask equals 0022. Please clone the repository as root user and also control the stack as root. We will modify attributes – if necessary – while bootstrapping the containers automatically and make sure everything is secured. The update.sh script must therefore also be run as root. It might be necessary to change ownership and other attributes of files you will otherwise not have access to. We drop permissions for every exposed application and will not run an exposed service as root! Controlling the Docker daemon as non-root user does not give you additional security. The unprivileged user will spawn the containers as root likewise. The behaviour of the stack is identical.

$ su
# umask
0022 # <- Verify it is 0022
# cd /opt
# git clone https://github.com/mailcow/mailcow-dockerized
# cd mailcow-dockerized

Initialize mailcow

Generate a configuration file. Use a FQDN (host.domain.tld) as hostname when asked.

./generate_config.sh

Change configuration if you want or need to.

nano mailcow.conf

If you plan to use a reverse proxy, you can, for example, bind HTTPS to 127.0.0.1 on port 8443 and HTTP to 127.0.0.1 on port 8080.

You may need to stop an existing pre-installed MTA which blocks port 25/tcp. See this chapter to learn how to reconfigure Postfix to run besides mailcow after a successful installation.

Some updates modify mailcow.conf and add new parameters. It is hard to keep track of them in the documentation. Please check their description and, if unsure, ask at the known channels for advise.

Troubleshooting

Users with a MTU not equal to 1500 (e.g. OpenStack)

Whenever you run into trouble and strange phenomena, please check your MTU.

Edit docker-compose.yml and change the network settings according to your MTU. Add the new driver_opts parameter like this:

networks:
  mailcow-network:
    ...
    driver_opts:
      com.docker.network.driver.mtu: 1450
    ...

Users without an IPv6 enabled network on their host system

Please don’t turn off IPv6, even if you don’t like it. IPv6 is the future and should not be ignored.

If you do not have an IPv6 enabled network on your host and you don’t care for a better internet (thehe), it is recommended to disable IPv6 for the mailcow network to prevent unforeseen issues.

Start mailcow

Pull the images and run the compose file. The parameter -d will start mailcow: dockerized detached:

docker compose pull
docker compose up -d

Done!

You can now access https://${MAILCOW_HOSTNAME} with the default credentials admin + password moohoo.

Info

If you are not using mailcow behind a reverse proxy, you should redirect all HTTP requests to HTTPS.

The database will be initialized right after a connection to MySQL can be established.

Your data will persist in multiple Docker volumes, that are not deleted when you recreate or delete containers. Run docker volume ls to see a list of all volumes. You can safely run docker compose down without removing persistent data.

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *