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):
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
- A good article covering all relevant topics: “3 DNS Records Every Email Marketer Must Know”
- Another great one, but Zimbra as an example platform: “Best Practices on Email Protection: SPF, DKIM and DMARC”
- An in-depth discussion of SPF, DKIM and DMARC: “How to eliminate spam and protect your name with DMARC”
- A thorough guide on understanding DMARC: “Demystifying DMARC: A guide to preventing email spoofing”
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:
- MX Toolbox (DNS, SMTP, RBL)
- port25.com (DKIM, SPF)
- Mail-tester (DKIM, DMARC, SPF)
- DMARC Analyzer (DMARC, SPF)
- MultiRBL.valli.org (DNSBL, RBL, FCrDNS)
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.
- Postmaster Tool
- parsedmarc (self-hosted)
- Fraudmarc
- Postmark
- Dmarcian
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)
- 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 bemx
, the domain namemailcow
and the TLDemail
.
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.