mailcow: dockerized is a self-hosted mail server suite that packages Postfix, Dovecot, Rspamd, SOGo webmail, ClamAV and a web administration interface as a set of Docker containers. In this tutorial you will install mailcow on an Ubuntu 24.04 server, publish the DNS records a mail server needs to deliver reliably, create your first domain and mailbox, and set up automatic backups.
Prerequisites
To follow this tutorial, you will need:
- A dedicated server running Ubuntu 24.04 LTS, for example a CubePath VPS, with at least 6 GB of RAM, 1 GB of swap and 20 GB of free disk space. mailcow's own minimum is 6 GB of RAM because ClamAV and Rspamd are memory hungry.
- A non-root user with
sudoprivileges. - Docker Engine and the Docker Compose plugin installed from Docker's official repository. mailcow does not support the Snap version of Docker.
- A domain name, referred to as
your_domain, and the ability to edit its DNS zone. The mail host will bemail.your_domain. - A static public IPv4 address, referred to as
your_server_ip, with a reverse DNS (PTR) record that resolves tomail.your_domain. Set the PTR record from your provider's panel or ask support to do it. - Inbound and outbound TCP port 25 allowed by your provider. Many providers restrict outbound port 25 on new accounts; confirm with support before you start.
No other service may listen on the mail and web ports. Check that nothing is already bound to them:
sudo ss -tlpn | grep -E ':(25|80|110|143|443|465|587|993|995|4190)\s'
The command should print nothing on a fresh server.
Step 1 - Creating the DNS records
Let's Encrypt certificates and mail delivery both depend on DNS, so create the records first and let them propagate while you install. In your DNS provider, add:
| Type | Name | Value |
|---|---|---|
| A | mail | your_server_ip |
| MX | @ | mail.your_domain (priority 10) |
| TXT | @ | v=spf1 mx a -all |
| CNAME | autodiscover | mail.your_domain |
| CNAME | autoconfig | mail.your_domain |
The SPF record says only this domain's MX host may send mail for it. The autodiscover and autoconfig records let Outlook, Thunderbird and mobile clients find server settings automatically. You will add DKIM and DMARC records in Step 5, once mailcow has generated the DKIM key.
Verify the records from the server:
dig +short A mail.your_domain
dig +short MX your_domain
dig +short -x your_server_ip
your_server_ip
10 mail.your_domain.
mail.your_domain.
Step 2 - Downloading mailcow and generating the configuration
mailcow's installer and helper scripts expect to be run as root with a standard umask. Switch to a root shell and confirm the umask is 0022:
sudo -i
umask
0022
Install the tools the scripts depend on, then clone the repository into /opt:
apt update
apt install -y git openssl curl gawk coreutils grep jq
cd /opt
git clone https://github.com/mailcow/mailcow-dockerized
cd mailcow-dockerized
Generate the configuration file:
./generate_config.sh
The script asks for the mail server hostname. Enter the fully qualified name, mail.your_domain, not the bare domain. Then accept or change the detected time zone and choose the master (stable) branch when asked. It writes the result to mailcow.conf.
Review the key values:
grep -E '^(MAILCOW_HOSTNAME|HTTP_PORT|HTTPS_PORT|TZ|SKIP_LETS_ENCRYPT|SKIP_CLAMD)=' mailcow.conf
MAILCOW_HOSTNAME=mail.your_domain
HTTP_PORT=80
HTTPS_PORT=443
TZ=Europe/Madrid
SKIP_LETS_ENCRYPT=n
SKIP_CLAMD=n
Leave SKIP_LETS_ENCRYPT=n so mailcow requests a certificate for mail.your_domain automatically. If the server has less memory than recommended, you can set SKIP_CLAMD=y to disable antivirus scanning, at the cost of weaker attachment filtering.
Step 3 - Starting mailcow
Pull the images and start the stack:
docker compose pull
docker compose up -d
The first start takes a few minutes while databases initialize and certificates are requested. Check that every container is running:
docker compose ps --format "table {{.Service}}\t{{.Status}}"
SERVICE STATUS
acme-mailcow Up 2 minutes
clamd-mailcow Up 2 minutes (healthy)
dovecot-mailcow Up 2 minutes
mysql-mailcow Up 2 minutes
nginx-mailcow Up 2 minutes
postfix-mailcow Up 2 minutes
rspamd-mailcow Up 2 minutes
sogo-mailcow Up 2 minutes
...
Confirm the Let's Encrypt certificate was issued:
docker compose logs acme-mailcow | tail -n 5
A successful run ends with a line reporting that the certificate was obtained or is still valid. If it keeps retrying, your A record is wrong or port 80 is not reachable from the internet.
Opening the firewall
mailcow publishes its ports through Docker, which writes its own iptables rules, so traffic to published container ports is not filtered by UFW. Still, keep UFW enabled for the host itself and allow SSH and the mail ports so the rules document what the server exposes:
exit
sudo ufw allow OpenSSH
sudo ufw allow 25,80,443,465,587,993,995,4190/tcp
sudo ufw enable
The exit leaves the root shell. Ports 110 and 143 (plain POP3 and IMAP) are also published by mailcow; clients should use 993 and 995 instead. If you need to block a published port, set it to 127.0.0.1 binding in mailcow.conf (for example IMAP_PORT=127.0.0.1:143) and run docker compose up -d again from /opt/mailcow-dockerized.
Step 4 - Securing the admin account and adding a domain
Open https://mail.your_domain/admin in a browser and log in with the default credentials, user admin and password moohoo. Change the password immediately from the account menu and enable two-factor authentication on the same page.
Next, add your mail domain:
- Go to E-Mail > Configuration and open the Domains tab.
- Click Add domain, enter
your_domain, adjust mailbox and quota limits if you need to, and save.
Then create a mailbox:
- Open the Mailboxes tab and click Add mailbox.
- Enter the local part (for example
alice), selectyour_domain, set the full name and a strong password, and save.
Users log in to SOGo webmail at https://mail.your_domain/SOGo with their full email address and password. SOGo also provides CalDAV and CardDAV for calendars and contacts.
Step 5 - Publishing DKIM and DMARC records
DKIM signs outgoing mail so receivers can verify it was not altered, and DMARC tells them what to do when SPF or DKIM fail. mailcow generates a 2048-bit DKIM key for each domain when you add it.
In the Domains tab, click the DNS button next to your_domain. This page lists every record mailcow expects, with its current state. Copy the DKIM record it shows, which uses the selector dkim:
| Type | Name | Value |
|---|---|---|
| TXT | dkim._domainkey | v=DKIM1;k=rsa;t=s;s=email;p=MIIBIjANBgkq... (copy the full value from mailcow) |
| TXT | _dmarc | v=DMARC1; p=quarantine; rua=mailto:postmaster@your_domain |
Starting with p=quarantine sends failing mail to spam instead of rejecting it. Once aggregate reports show that all legitimate mail passes, you can move to p=reject.
Check that the records are published:
dig +short TXT dkim._domainkey.your_domain
dig +short TXT _dmarc.your_domain
Reload the DNS page in mailcow: every row should now show a green check mark.
Step 6 - Testing delivery
Log in to SOGo as alice@your_domain and send a message to an external mailbox, such as a Gmail account. Open the received message's original headers and confirm spf=pass, dkim=pass and dmarc=pass in the Authentication-Results header. Reply to it to test inbound delivery.
For a scored report, send a message to the address shown at mail-tester.com. A correctly configured server with fresh IP reputation should score 9 or 10 out of 10.
You can follow mail flow in the Postfix logs while testing:
cd /opt/mailcow-dockerized
sudo docker compose logs -f --tail=20 postfix-mailcow
Step 7 - Scheduling backups
mailcow ships a backup script that saves mail, the database, Rspamd data and the configuration. It reads the destination from the MAILCOW_BACKUP_LOCATION variable. Create a backup directory and run a first backup:
sudo mkdir -p /opt/mailcow-backups
cd /opt/mailcow-dockerized
sudo MAILCOW_BACKUP_LOCATION=/opt/mailcow-backups ./helper-scripts/backup_and_restore.sh backup all
When it finishes, a timestamped directory appears:
ls /opt/mailcow-backups
mailcow-2026-09-25-03-00-01
Schedule a nightly backup that keeps three days of history. Open root's crontab:
sudo crontab -e
Add this line:
0 3 * * * MAILCOW_BACKUP_LOCATION=/opt/mailcow-backups /opt/mailcow-dockerized/helper-scripts/backup_and_restore.sh backup all --delete-days 3
Backups stored on the same server do not protect you from losing the server, so copy /opt/mailcow-backups to another location, such as object storage or a second machine, with a tool like rsync or restic. To restore, run the same script with restore and pick the backup when prompted.
Step 8 - Updating mailcow
mailcow updates through its own script, which pulls the new code and images and recreates the containers. Run it from the installation directory:
cd /opt/mailcow-dockerized
sudo ./update.sh
To only check whether an update is available without applying it:
sudo ./update.sh --check
Take a backup before updating, and read the release notes on the mailcow GitHub releases page for any manual steps.
Troubleshooting
Other servers cannot deliver to you: test port 25 from a machine outside your network with nc -vz mail.your_domain 25. If it times out, check the provider firewall and confirm inbound port 25 is not blocked.
Your mail lands in spam: open the domain's DNS page in mailcow and fix any red rows, then verify the PTR record with dig +short -x your_server_ip. A missing or mismatched PTR is the most common cause.
The certificate is not issued: check docker compose logs acme-mailcow. The A records for mail, autodiscover and autoconfig must point to this server and port 80 must be reachable.
A container keeps restarting: view its recent logs, for example docker compose logs --tail=50 clamd-mailcow. On servers with too little memory, ClamAV is usually the one that fails; add RAM or set SKIP_CLAMD=y in mailcow.conf and run docker compose up -d.
Conclusion
You now have a mailcow server on Ubuntu 24.04 with valid TLS, SPF, DKIM and DMARC, a working mailbox and nightly backups. From here, add more domains and mailboxes from the admin interface, create per-app passwords for mail clients under each user's settings, and review spam filter results in the Rspamd interface linked from the admin panel.
