Deploying Mailcow with Docker and Traefik for a Full E-Mail Stack
Table of Contents 📑
- Changelog
- 1. Prerequisites
- 2. Directory Structure
- 3. Clone the Mailcow Repository
- 4. Generate the Configuration File
- 5. Configure for Traefik Integration
- 6. Launch Mailcow
- 7. DNS Configuration
- 8. Firewall Configuration
- 9. Initial Mailcow Setup
- 10. Testing and Verification
- 11. Create Mailboxes and Log In
- 12. Conclusion
- 13. Further Reading
Mailcow is a powerful and flexible open-source mail server suite that allows you to manage your email communications securely and efficiently. With Mailcow, you can create multiple email domains and accounts, manage users, and leverage features like spam filtering and encryption, giving you full control over your email infrastructure. This guide will show you how to set up Mailcow using Docker Compose behind an existing Traefik reverse proxy.
Changelog
| Date | Change |
|---|---|
| 2025-10-03 | Major Revision: Corrected certdumper command, simplified Traefik labels, and fixed critical mailcow.conf networking settings to resolve 404 errors. Enhanced troubleshooting for network conflicts. |
| 2025-09-18 | Initial Version: First version of this guide was created. |
1. Prerequisites
This guide assumes you have a fully functional server environment with the following components already set up:
- Traefik v3 and CrowdSec with Docker Compose: This is the foundation for our reverse proxy, security, and certificate management.
- Docker and Docker Compose: Must be installed on your server.
- Required Tools: You will need
gitandjq. Install them with:sudo apt update && sudo apt install -y git jq sudoor root access.
2. Directory Structure
First, create a dedicated directory for your Mailcow installation.
| ℹ️ PATH CONSISTENCY |
This guide uses |
sudo mkdir -p /opt/containers/mailcow
3. Clone the Mailcow Repository
Clone the latest version of the Mailcow Dockerized project from GitHub into the directory you just created.
sudo git clone https://github.com/mailcow/mailcow-dockerized /opt/containers/mailcow
4. Generate the Configuration File
Navigate into the new directory and run the configuration generation script.
cd /opt/containers/mailcow
./generate_config.sh
You will be prompted for the following information:
- Mail server hostname (FQDN): Enter the fully qualified domain name for your mail server, for example,
mail.your-domain.com. - Timezone: Press Enter to accept the default or provide your own.
- Available Branches: Press
1to select the master branch.
5. Configure for Traefik Integration
To make Mailcow work with our external Traefik instance, we need to create an override file and modify the main configuration.
5.1. Create docker-compose.override.yml
This file contains all our customizations, telling Mailcow to use the external proxy network and defining labels for Traefik to route traffic correctly. It also sets up a certdumper service, which is crucial for sharing Traefik’s Let’s Encrypt certificates with Mailcow’s services (Postfix and Dovecot).
| ℹ️ HOW THE CERTDUMPER WORKS |
The |
Create the file:
sudo tee /opt/containers/mailcow/docker-compose.override.yml > /dev/null << 'EOF'
networks:
proxy:
name: proxy
external: true
services:
certdumper:
image: ghcr.io/kereis/traefik-certs-dumper:latest
restart: unless-stopped
network_mode: none
# Correct command to restart containers by their full name. Mailcow's project name is "mailcowdockerized".
command: --restart-containers mailcowdockerized-postfix-mailcow-1,mailcowdockerized-dovecot-mailcow-1
volumes:
# Adjust this path to match YOUR Traefik stack's certificate location
- /opt/containers/traefik-stack/traefik/certs:/traefik:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./data/assets/ssl:/output:rw
environment:
DOMAIN: ${MAILCOW_HOSTNAME}
# Adjust this to match YOUR Traefik acme file name
ACME_FILE_PATH: "/traefik/acme.json"
healthcheck:
test: ["CMD", "/usr/bin/healthcheck"]
interval: 30s
timeout: 10s
retries: 5
nginx-mailcow:
# We use !reset to completely remove the default port bindings set by Mailcow.
ports: !reset
networks:
- proxy
labels:
- "traefik.enable=true"
- "traefik.docker.network=proxy"
# HTTPS Router (Secure)
# The HTTP to HTTPS redirect is handled globally by our Traefik setup, so we only need the secure router.
- "traefik.http.routers.nginx-mailcow-secure.entrypoints=websecure"
- "traefik.http.routers.nginx-mailcow-secure.rule=Host(`mail.your-domain.com`) || Host(`autodiscover.your-domain.com`) || Host(`autoconfig.your-domain.com`) || Host(`mta-sts.your-domain.com`)"
- "traefik.http.routers.nginx-mailcow-secure.tls=true"
- "traefik.http.routers.nginx-mailcow-secure.tls.certresolver=tls_resolver"
- "traefik.http.routers.nginx-mailcow-secure.service=nginx-mailcow"
- "traefik.http.routers.nginx-mailcow-secure.middlewares=security-headers@file,crowdsec-bouncer@docker"
# Service Definition: Point to Mailcow's internal Nginx port.
# This must be 80, as we are NOT changing the internal ports in mailcow.conf
- "traefik.http.services.nginx-mailcow.loadbalancer.server.port=80"
EOF
| ℹ️ CROWDSEC BOUNCER SCOPE |
The |
| ⚠️ IMPORTANT |
You must replace |
5.2. Adjust mailcow.conf
Next, edit the main configuration file. There are only two changes needed here.
sudo nano /opt/containers/mailcow/mailcow.conf```
<!-- Infobox Shortcode - Retro-kompatibel mit Browsern der 90er Jahre -->
<!-- Verwendung: infobox(type="info", title="Titel") mit Inhalt der Box -->
<!-- Types: info, warning, tip, note -->
<table class="retro-infobox" width="100%" cellspacing="0" cellpadding="10" border="1">
<tr>
<td bgcolor="#eeeeee" style="color: #000000; font-weight: bold;">
ℹ️
CRITICAL: DO NOT CHANGE PORTS OR BINDINGS!
</td>
</tr>
<tr>
<td bgcolor="#000000" style="color: #ffffff;">
<p>Do <strong>not</strong> change <code>HTTP_PORT</code>, <code>HTTPS_PORT</code>, <code>HTTP_BIND</code>, or <code>HTTPS_BIND</code>. Leaving them at their default values is crucial. Changing <code>HTTP_BIND</code> to <code>127.0.0.1</code> will prevent Traefik from reaching the Mailcow web UI, resulting in a <strong>404 Not Found error</strong>. The internal ports do not conflict with Traefik because they are inside the Docker network, not on the host.</p>
</td>
</tr>
</table>
1. **Disable Mailcow's Let's Encrypt:** Traefik is responsible for all certificate management. This is the most important change.
```ini
# Find this line:
SKIP_LETS_ENCRYPT=n
# Change it to:
SKIP_LETS_ENCRYPT=y
```
2. **Add SAN for Internal TLS:** This helps internal services correctly recognize the hostname on the certificate provided by the `certdumper`.
```ini
# Find this line (it may be commented out):
# ADDITIONAL_SAN=
# Change it to:
ADDITIONAL_SAN=${MAILCOW_HOSTNAME}
```
Save and close the file. No other changes are needed in this file.
### 5.3. Activate MTA-STS
MTA-STS (Mail Transfer Agent-Strict Transport Security) is a security standard that helps prevent man-in-the-middle attacks by ensuring emails are transmitted over secure TLS connections.
<!-- Infobox Shortcode - Retro-kompatibel mit Browsern der 90er Jahre -->
<!-- Verwendung: infobox(type="info", title="Titel") mit Inhalt der Box -->
<!-- Types: info, warning, tip, note -->
<table class="retro-infobox" width="100%" cellspacing="0" cellpadding="10" border="1">
<tr>
<td bgcolor="#ccffcc" style="color: #000000; font-weight: bold;">
💡
EASIER ALTERNATIVE: USE THE MAILCOW UI
</td>
</tr>
<tr>
<td bgcolor="#000000" style="color: #ffffff;">
<p>Instead of creating the policy file by hand, you can navigate to <strong>System -> Configuration -> MTA-STS</strong> in the Mailcow UI after setup. The UI provides a generator that creates the correct policy and DNS records for you, which is less error-prone. The manual method below is still valid if you prefer it.</p>
</td>
</tr>
</table>
If you choose the manual route, create the required directory:
```bash
sudo mkdir -p /opt/containers/mailcow/data/web/.well-known/
Now, create the policy file with a simple enforce policy. This file will be served by Mailcow’s web server.
sudo tee /opt/containers/mailcow/data/web/.well-known/mta-sts.txt > /dev/null << 'EOF'
version: STSv1
mode: enforce
max_age: 15552000
mx: mail.your-domain.com
EOF
| ⚠️ IMPORTANT |
You must replace |
6. Launch Mailcow
You can now start the Mailcow stack.
cd /opt/containers/mailcow
docker compose up -d
| 💡 TROUBLESHOOTING TIPS |
|
7. DNS Configuration
Correct DNS setup is critical for a mail server to function.
7.1. Reverse DNS (PTR Record)
Your server’s Reverse DNS (PTR) record for both IPv4 and IPv6 must match the hostname you configured in mailcow.conf (mail.your-domain.com). This is usually set in your server provider’s control panel and is essential for not being marked as spam, especially by major providers like Gmail.
7.2. DNS Records
| ⚠️ USING CLOUDFLARE? |
If you are using Cloudflare for your DNS, all mail-related records (including |
In your domain’s DNS management panel, add the following records. We start with a non-restrictive DMARC policy (p=none) to prevent legitimate emails from being rejected during the initial setup.
| ⚠️ GMAIL & YAHOO SENDER REQUIREMENTS (2024) |
As of 2024, major providers like Gmail and Yahoo enforce stricter sender policies:
Failing to meet these requirements will result in your emails being rejected. For more details, see the official announcements from Google and Yahoo. |
| ℹ️ DMARC STAGED ROLLOUT |
Starting with
|
| Name | Type | Value |
|---|---|---|
| A | your-server-ip | |
| AAAA | your-server-ipv6 | |
| autodiscover | CNAME | mail.your-domain.com. |
| autoconfig | CNAME | mail.your-domain.com. |
| imap | CNAME | mail.your-domain.com. |
| pop3 | CNAME | mail.your-domain.com. |
| smtp | CNAME | mail.your-domain.com. |
| @ | MX 10 | mail.your-domain.com. |
| SPF | ||
| @ | TXT | v=spf1 mx a -all |
| 💡 SPF FINE-TUNING |
The |
v=DMARC1; p=none; rua=mailto:admin@your-domain.com | | MTA-STS | | | | mta-sts | A | your-server-ip | | mta-sts | AAAA | your-server-ipv6 | | _mta-sts | TXT | v=STSv1; id=2025091801 | | _smtp._tls | TXT | v=TLSRPTv1; rua=mailto:admin@your-domain.com | | Service Records | | | | _autodiscover._tcp | SRV | 0 1 443 mail.your-domain.com. | | _caldavs._tcp | SRV | 0 1 443 mail.your-domain.com. | | _caldavs._tcp | TXT | "path=/SOGo/dav/" | | _carddavs._tcp | SRV | 0 1 443 mail.your-domain.com. | | _carddavs._tcp | TXT | "path=/SOGo/dav/" | | _imaps._tcp | SRV | 0 1 993 mail.your-domain.com. | | _pop3s._tcp | SRV | 0 1 995 mail.your-domain.com. | | _submission._tcp | SRV | 0 1 587 mail.your-domain.com. | | _smtps._tcp | SRV | 0 1 465 mail.your-domain.com. | A DKIM record will be added later after it’s generated by Mailcow.
| ℹ️ UNUSED SRV RECORDS |
This guide only lists SRV records for encrypted services (IMAPS, POP3S, etc.). If you decide not to offer certain services (e.g., POP3), you should also omit their corresponding SRV records to prevent clients from attempting to connect to them. |
| ℹ️ DNS RECORD NOTES |
|
8. Firewall Configuration
Your firewall must allow traffic on several ports for email services to be reachable.
| Port | Service | Protocol |
|---|---|---|
| 25 | SMTP | TCP |
| 587 | Submission | TCP |
| 465 | SMTPS | TCP |
| 143 | IMAP | TCP |
| 993 | IMAPS | TCP |
| 110 | POP3 | TCP |
| 995 | POP3S | TCP |
| 4190 | ManageSieve | TCP |
| 💡 PROTOCOL HYGIENE |
For better security, consider disabling protocols you don’t need. For example, if all your clients use IMAP, you can keep the POP3 ports (110, 995) closed. It is also best practice to enforce encrypted connections, favoring Submission (587) and IMAPS (993) over their unencrypted counterparts. |
| ℹ️ RESERVED INTERNAL PORTS |
Mailcow uses several ports internally (e.g., 8081, 9081, 65510). Avoid using these for your own services on the Docker host to prevent conflicts. |
Use ufw to open these ports:
| ⚠️ DOCKER BYPASSES UFW RULES |
By default, Docker manipulates
|
sudo ufw allow 25/tcp
sudo ufw allow 587/tcp
sudo ufw allow 465/tcp
sudo ufw allow 143/tcp
sudo ufw allow 993/tcp
sudo ufw allow 110/tcp
sudo ufw allow 995/tcp
sudo ufw allow 4190/tcp
sudo ufw status
9. Initial Mailcow Setup
Navigate to your Mailcow UI at https://mail.your-domain.com.
- Default Username:
admin - Default Password:
moohoo
First, change the administrator password under System -> Configuration -> Edit.
9.1. Add Your Domain
Go to Email -> Configuration and click “Add domain”. Enter your main domain (e.g., your-domain.com, not mail.your-domain.com).
9.2. Generate DKIM Key
A DKIM key is essential for email authentication.
Go to System -> Configuration.
Navigate to the Options -> ARC/DKIM keys tab.
A 2048-bit key should already be generated for your domain. Copy the public key text from the text box.
Go back to your DNS provider and add a new TXT record:
- Name:
dkim._domainkey - Value: Paste the entire copied key, including the
v=DKIM1;k=rsa;p=...part.
💡 LONG DKIM RECORDS Some DNS providers have a 255-character limit for a single TXT record string. If your 2048-bit DKIM key is longer, you may need to split it into multiple quoted strings. Many providers handle this automatically, but if you encounter issues, check your provider’s documentation on how to format long TXT records.
- Name:
10. Testing and Verification
After waiting for DNS propagation, thoroughly test your setup.
10.1. Internal DNS and Certificate Check
- Mailcow DNS Check: In the Mailcow UI, go to Email -> Configuration -> DNS next to your domain for an internal check of your records.
- Verify Certificates: Ensure that the certificates from Traefik have been correctly passed to Mailcow’s services. Run these commands, replacing
mail.your-domain.comwith your mail server’s FQDN:
In the output, look for a certificate chain issued by a trusted authority. For Let’s Encrypt, the chain typically starts with# Check SMTP certificate echo "Q" | openssl s_client -starttls smtp -crlf -connect mail.your-domain.com:587 # Check IMAP certificate echo "Q" | openssl s_client -starttls imap -crlf -connect mail.your-domain.com:143ISRG Root X1. The key is to confirm it is not a self-signed certificate. If you see a self-signed certificate (where the issuer matches the subject), it indicates a problem with thecertdumperservice or its path mappings. In that case, check the certdumper logs:cd /opt/containers/mailcow docker compose logs certdumper
10.2. External Testing Tools
- mail-tester.com: Send an email to the address provided on their site to get a score out of 10.
- mxtoolbox.com: Provides various checks for your mail server.
- checktls.com/TestReceiver: Use this to specifically verify your MTA-STS configuration.
11. Create Mailboxes and Log In
Under Email -> Mailboxes, you can add new users. Once a user is created, the primary way to access the webmail interface (SOGo) is by clicking Apps -> Webmail from within the Mailcow UI. While you can also try going directly to https://mail.your-domain.com/SOGo/, be aware that recent Mailcow versions may redirect unauthenticated access back to the main login page.
12. Conclusion
You have now successfully deployed a full-featured Mailcow e-mail server. By leveraging Docker for containerization and Traefik for reverse proxying and certificate management, you have a secure, robust, and maintainable mail solution. This setup provides you with complete control over your email, enhanced security through features like MTA-STS, and the flexibility to manage multiple domains and users with ease.
13. Further Reading
For more detailed information, refer to the official Mailcow documentation:
- Reverse Proxy Overview: docs.mailcow.email/post-installation/r_p-overview/
- IP Bindings: docs.mailcow.email/post-installation/firststeps-ip_bindings/
- MTA-STS Setup: docs.mailcow.email/post-installation/firststeps-mta_sts/


