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 sudo privileges.
  • 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 be mail.your_domain.
  • A static public IPv4 address, referred to as your_server_ip, with a reverse DNS (PTR) record that resolves to mail.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:

TypeNameValue
Amailyour_server_ip
MX@mail.your_domain (priority 10)
TXT@v=spf1 mx a -all
CNAMEautodiscovermail.your_domain
CNAMEautoconfigmail.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:

  1. Go to E-Mail > Configuration and open the Domains tab.
  2. Click Add domain, enter your_domain, adjust mailbox and quota limits if you need to, and save.

Then create a mailbox:

  1. Open the Mailboxes tab and click Add mailbox.
  2. Enter the local part (for example alice), select your_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:

TypeNameValue
TXTdkim._domainkeyv=DKIM1;k=rsa;t=s;s=email;p=MIIBIjANBgkq... (copy the full value from mailcow)
TXT_dmarcv=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.