How to Self-Host Mailu with Docker

What Is Mailu?

Mailu is a full-featured, self-hosted mail server built from standard open-source components (Postfix, Dovecot, Rspamd, SnappyMail) packaged into a cohesive Docker stack with a unified admin interface. It handles SMTP, IMAP, POP3, webmail, antispam, optional antivirus, and DKIM signing out of the box. Think of it as a self-hosted replacement for Gmail, Outlook, or Proton Mail where you own every message and every byte of metadata. Official site

Prerequisites

  • A Linux server (Ubuntu 22.04+ recommended) with a dedicated public IP address — shared hosting won’t work for mail
  • Docker and Docker Compose installed (guide)
  • 2 GB of RAM minimum (4 GB recommended if enabling ClamAV antivirus)
  • 10 GB of free disk space (grows with mailbox usage)
  • A domain name you control with full DNS access (MX, TXT, PTR records required)
  • Reverse DNS (PTR record) configured on your server’s IP — contact your hosting provider to set this to mail.example.com
  • Port 25 open — many cloud providers (AWS, GCP, Azure, Oracle) block outbound port 25 by default. Verify with your provider before starting. Hetzner, OVH, and most dedicated server providers allow it.

Self-hosting email is significantly harder than self-hosting most other services. DNS misconfiguration, missing PTR records, or a dirty IP address will cause your emails to land in spam or be rejected entirely. Read this entire guide before deploying.

DNS Configuration

DNS is the single most critical part of a mail server setup. Get this wrong and your emails go straight to spam — or never arrive at all. Configure these records before deploying Mailu.

Replace example.com with your domain and 203.0.113.10 with your server’s public IP.

MX Record

Tells the internet where to deliver email for your domain.

TypeNameValuePriorityTTL
MXexample.commail.example.com103600
Amail203.0.113.10-3600

SPF Record

Declares which servers are authorized to send email on behalf of your domain. Receiving servers check this to detect spoofing.

TypeNameValueTTL
TXTexample.comv=spf1 mx a:mail.example.com -all3600

The -all means “reject anything not from my listed servers.” Use ~all (softfail) during testing, then switch to -all once confirmed working.

DKIM Record

DKIM cryptographically signs outgoing emails so receivers can verify they haven’t been tampered with. Mailu generates the DKIM key automatically — you’ll add this record after initial setup (see Initial Setup section).

TypeNameValueTTL
TXTdkim._domainkey.example.com(generated by Mailu — see Initial Setup)3600

DMARC Record

Tells receiving servers what to do with emails that fail SPF and DKIM checks, and where to send reports.

TypeNameValueTTL
TXT_dmarc.example.comv=DMARC1; p=quarantine; rua=mailto:[email protected]; pct=1003600

Start with p=quarantine (send failures to spam). Once you’ve confirmed SPF and DKIM are passing, change to p=reject for maximum protection.

Reverse DNS (PTR Record)

Your server’s IP address must resolve back to mail.example.com. This is configured at your hosting provider’s control panel, not in your domain’s DNS. Without a matching PTR record, Gmail and Outlook will reject or spam your emails.

Autodiscover / Autoconfig Records

These let email clients (Thunderbird, Outlook) automatically configure server settings:

TypeNameValueTTL
CNAMEautoconfigmail.example.com3600
SRV_autodiscover._tcp0 443 mail.example.com3600

Docker Compose Configuration

Mailu is a multi-container stack. Each service handles a specific mail function. Create a directory for Mailu and add the following files.

mkdir -p /opt/mailu && cd /opt/mailu

Create a docker-compose.yml file:

services:
  front:
    image: ghcr.io/mailu/nginx:2024.06
    container_name: mailu_front
    env_file: mailu.env
    ports:
      - "25:25"        # SMTP
      - "80:80"        # HTTP (redirect to HTTPS)
      - "110:110"      # POP3
      - "143:143"      # IMAP
      - "443:443"      # HTTPS (webmail + admin)
      - "465:465"      # SMTP over TLS
      - "587:587"      # SMTP submission
      - "993:993"      # IMAPS
      - "995:995"      # POP3S
      - "4190:4190"    # ManageSieve (mail filtering rules)
    volumes:
      - mailu_certs:/certs
      - mailu_overrides:/overrides:ro
    depends_on:
      - resolver
    dns:
      - 192.168.203.254
    networks:
      default:
        ipv4_address: 192.168.203.254
      # NOTE: front is both on the mailu network AND receives DNS queries
    restart: unless-stopped

  resolver:
    image: ghcr.io/mailu/unbound:2024.06
    container_name: mailu_resolver
    env_file: mailu.env
    networks:
      default:
        ipv4_address: 192.168.203.254
    restart: unless-stopped

  admin:
    image: ghcr.io/mailu/admin:2024.06
    container_name: mailu_admin
    env_file: mailu.env
    volumes:
      - mailu_data:/data
      - mailu_dkim:/dkim
    depends_on:
      - redis
      - resolver
    dns:
      - 192.168.203.254
    restart: unless-stopped

  imap:
    image: ghcr.io/mailu/dovecot:2024.06
    container_name: mailu_imap
    env_file: mailu.env
    volumes:
      - mailu_mail:/mail
      - mailu_overrides:/overrides:ro
    depends_on:
      - front
      - resolver
    dns:
      - 192.168.203.254
    restart: unless-stopped

  smtp:
    image: ghcr.io/mailu/postfix:2024.06
    container_name: mailu_smtp
    env_file: mailu.env
    volumes:
      - mailu_mailqueue:/queue
      - mailu_overrides:/overrides:ro
    depends_on:
      - front
      - resolver
    dns:
      - 192.168.203.254
    restart: unless-stopped

  antispam:
    image: ghcr.io/mailu/rspamd:2024.06
    container_name: mailu_antispam
    env_file: mailu.env
    volumes:
      - mailu_filter:/var/lib/rspamd
      - mailu_overrides:/overrides:ro
    depends_on:
      - front
      - resolver
    dns:
      - 192.168.203.254
    restart: unless-stopped

  webmail:
    image: ghcr.io/mailu/webmail:2024.06
    container_name: mailu_webmail
    env_file: mailu.env
    volumes:
      - mailu_webmail:/data
      - mailu_overrides:/overrides:ro
    depends_on:
      - front
    dns:
      - 192.168.203.254
    restart: unless-stopped

  redis:
    image: redis:7-alpine
    container_name: mailu_redis
    volumes:
      - mailu_redis:/data
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5

  # Optional: ClamAV antivirus (adds ~1-2 GB RAM usage)
  # Uncomment to enable virus scanning on incoming/outgoing mail
  # antivirus:
  #   image: ghcr.io/mailu/clamav:2024.06
  #   container_name: mailu_antivirus
  #   env_file: mailu.env
  #   volumes:
  #     - mailu_filter:/data
  #   depends_on:
  #     - resolver
  #   dns:
  #     - 192.168.203.254
  #   restart: unless-stopped

volumes:
  mailu_certs:
  mailu_data:
  mailu_dkim:
  mailu_mail:
  mailu_mailqueue:
  mailu_filter:
  mailu_overrides:
  mailu_webmail:
  mailu_redis:

networks:
  default:
    driver: bridge
    ipam:
      driver: default
      config:
        - subnet: 192.168.203.0/24

Architecture notes:

  • front (Nginx) is the entry point — it terminates TLS and routes traffic to the correct backend service
  • resolver (Unbound) provides internal DNS resolution so mail services don’t depend on external DNS
  • admin is the web-based admin panel for managing domains, users, and aliases
  • imap (Dovecot) handles mailbox storage and IMAP/POP3 access
  • smtp (Postfix) handles sending and receiving email via SMTP
  • antispam (Rspamd) scores incoming mail and filters spam
  • webmail (SnappyMail) provides browser-based email access
  • redis stores session data and rate-limiting counters

Environment Configuration

Create a mailu.env file in the same directory. This is where all Mailu configuration lives.

# ============================================================
# Mailu Environment Configuration
# ============================================================
# Full reference: https://mailu.io/2024.06/configuration.html

# ---- Core Settings ----

# Primary mail domain (the domain after @ in email addresses)
DOMAIN=example.com

# Hostname of this mail server (must match your MX and A records)
HOSTNAMES=mail.example.com

# Postmaster email address (receives bounces, DMARC reports)
POSTMASTER=admin

# Secret key for session signing and internal encryption
# MUST CHANGE: Generate with: openssl rand -hex 16
SECRET_KEY=changeme_generate_a_random_hex_string

# ---- Web Settings ----

# Subnet of the Docker network (must match docker-compose.yml)
SUBNET=192.168.203.0/24

# How Mailu obtains TLS certificates
# Options: letsencrypt, cert, notls, mail, mail-letsencrypt
TLS_FLAVOR=letsencrypt

# Admin interface access
# Options: true (enable admin), false (disable)
ADMIN=true

# Webmail client
# Options: snappymail, roundcube, none
WEBMAIL=snappymail

# Web administration interface URL path
WEB_ADMIN=/admin

# Webmail URL path
WEB_WEBMAIL=/webmail

# Real IP header (set if behind a reverse proxy)
# REAL_IP_HEADER=X-Forwarded-For
# REAL_IP_FROM=172.16.0.0/12

# ---- Listening Addresses ----

# Which IP to bind to (0.0.0.0 = all interfaces)
BIND_ADDRESS4=0.0.0.0
# BIND_ADDRESS6=::1

# ---- Mail Settings ----

# Message size limit in bytes (50 MB)
MESSAGE_SIZE_LIMIT=50000000

# Rate limiting: max outgoing emails per user per day
MESSAGE_RATELIMIT=200/day

# Authentication rate limiting (protects against brute force)
AUTH_RATELIMIT_IP=60/hour
AUTH_RATELIMIT_USER=100/day

# Recipient delimiter (for plus addressing: [email protected])
RECIPIENT_DELIMITER=+

# Default spam threshold (higher = more permissive, lower = stricter)
# Recommended: 80 for moderate filtering
ANTISPAM_THRESHOLD=80

# ---- Optional Services ----

# Antivirus (uncomment if you enabled clamav in docker-compose.yml)
# ANTIVIRUS=clamav

# Fetchmail (for pulling mail from external accounts)
# FETCHMAIL_ENABLED=true
# FETCHMAIL_DELAY=600

# ---- Logging ----

# Log driver (stdout recommended for Docker)
LOG_DRIVER=stdout

# ---- Database ----
# Mailu uses SQLite by default for admin data
# This is fine for small to medium deployments (< 1000 users)
# For large deployments, see: https://mailu.io/2024.06/database.html

Critical settings you MUST change:

  1. DOMAIN — your actual mail domain
  2. HOSTNAMES — your actual mail server hostname
  3. SECRET_KEY — generate a unique key with openssl rand -hex 16
  4. TLS_FLAVOR — set to letsencrypt for automatic certificates

Start the stack:

docker compose up -d

Wait 2-3 minutes for all services to initialize. The first startup takes longer as Let’s Encrypt issues certificates and Rspamd downloads filter rules.

Initial Setup

Create the Admin Account

Mailu’s admin account is created via the CLI, not the web interface:

docker compose exec admin flask mailu admin admin example.com 'YourStrongPassword'

Replace example.com with your domain and set a strong password. This creates [email protected] as the administrator.

Access the Admin Panel

Open https://mail.example.com/admin in your browser. Log in with the admin credentials you just created.

Add Your Domain

Your primary domain should already appear in the admin panel (set via DOMAIN in the env file). To add additional domains:

  1. Go to Mail domains in the admin sidebar
  2. Click Add domain
  3. Enter the domain name
  4. Configure domain-specific settings (max users, max aliases, quota)

Retrieve Your DKIM Key

After Mailu starts, it generates a DKIM signing key for your domain:

  1. In the admin panel, go to Mail domains
  2. Click the DNS icon next to your domain
  3. Copy the displayed DKIM public key
  4. Add it as a TXT record in your DNS:
TypeNameValueTTL
TXTdkim._domainkey.example.com(the key from the admin panel)3600

The key looks like: v=DKIM1; k=rsa; p=MIIBIjANBg... (a long base64 string).

Verify DNS

Use an external tool to confirm your DNS records are correct:

# Check MX record
dig MX example.com +short

# Check SPF record
dig TXT example.com +short

# Check DKIM (after adding the record)
dig TXT dkim._domainkey.example.com +short

# Check DMARC
dig TXT _dmarc.example.com +short

Send a Test Email

Send a test email from your new mail server to an external address (Gmail, Outlook). Then send one back. Check that both directions work. Use mail-tester.com to score your setup — aim for 9/10 or higher.

Configuration

Adding Users

In the admin panel:

  1. Go to Mail domains → click your domain
  2. Click UsersAdd user
  3. Set the email address, display name, password, and quota
  4. Optionally enable the user as a manager (can manage their own aliases)

Aliases

Create aliases under Mail domains → your domain → Aliases. Useful patterns:

Quotas

Set per-user storage quotas in the user settings. The default is unlimited. For a personal server this is fine, but set quotas if you’re hosting mail for multiple people.

Sieve Filters

Users can create server-side mail filtering rules via ManageSieve (port 4190). Configure filters in SnappyMail under Settings → Filters, or connect a dedicated Sieve client. Common filters: move mailing lists to folders, auto-delete notifications, forward specific senders.

RESTful API

Mailu exposes a RESTful API for programmatic management. Enable it by visiting the admin panel and generating an API token under Settings. The API supports managing domains, users, aliases, and relay settings.

# Example: List all users
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
  https://mail.example.com/api/v1/user

API documentation is available at https://mail.example.com/api/v1/docs when the admin panel is enabled.

Advanced Configuration (Optional)

ClamAV Antivirus

ClamAV scans incoming and outgoing email for viruses. To enable it:

  1. Uncomment the antivirus service in docker-compose.yml
  2. Add ANTIVIRUS=clamav to mailu.env
  3. Restart the stack: docker compose up -d

Warning: ClamAV uses 1-2 GB of RAM for its virus signature database. Only enable this if your server has 4 GB+ of RAM. For most personal mail servers, Rspamd’s built-in malware detection is sufficient without ClamAV.

Fetchmail (Migration from External Accounts)

Fetchmail pulls email from external accounts (Gmail, Outlook) into your Mailu mailbox. Useful for migration.

  1. Add to mailu.env:

    FETCHMAIL_ENABLED=true
    FETCHMAIL_DELAY=600    # Check every 600 seconds (10 minutes)
  2. Add the fetchmail service to docker-compose.yml:

    fetchmail:
      image: ghcr.io/mailu/fetchmail:2024.06
      container_name: mailu_fetchmail
      env_file: mailu.env
      volumes:
        - mailu_data:/data
      depends_on:
        - admin
        - smtp
        - imap
        - resolver
      dns:
        - 192.168.203.254
      restart: unless-stopped
  3. Configure external accounts in the admin panel under each user’s settings.

Custom TLS Certificates

If you’re not using Let’s Encrypt (e.g., you have certificates from another CA):

  1. Set TLS_FLAVOR=cert in mailu.env
  2. Mount your certificates into the mailu_certs volume:
    # Copy certificates into the certs volume
    docker cp /path/to/cert.pem mailu_front:/certs/cert.pem
    docker cp /path/to/key.pem mailu_front:/certs/key.pem

Mailu expects cert.pem and key.pem in the /certs directory.

Relay Through External SMTP

If your provider blocks port 25, you can relay outgoing mail through an external SMTP service (Mailgun, SendGrid, Amazon SES):

In the admin panel, go to Mail domains → your domain → Edit and configure:

  • Relayed domain SMTP host: smtp.mailgun.org
  • Relayed domain SMTP port: 587
  • Relayed domain SMTP username/password: your relay credentials

This sends all outgoing mail through the relay while still receiving mail directly.

Reverse Proxy

Mailu handles its own TLS termination through the front (Nginx) container. In most deployments, you should NOT put Mailu behind another reverse proxy — just point your DNS directly at the server running Mailu and let it manage ports 80, 443, and all mail ports.

If you must run Mailu alongside other web services on the same server, you have two options:

Assign a second IP to your server and bind Mailu to it exclusively by changing BIND_ADDRESS4 in mailu.env. Your other web services use the primary IP.

Option 2: External Reverse Proxy

If you need an external proxy (e.g., Nginx Proxy Manager):

  1. Set TLS_FLAVOR=notls in mailu.env (the external proxy handles TLS)
  2. Change the front port mapping to avoid conflicts:
    ports:
      - "25:25"
      - "8080:80"      # HTTP behind proxy
      - "110:110"
      - "143:143"
      - "465:465"
      - "587:587"
      - "993:993"
      - "995:995"
      - "4190:4190"
  3. Set REAL_IP_HEADER=X-Forwarded-For and REAL_IP_FROM=172.16.0.0/12 in mailu.env
  4. Proxy HTTPS traffic to port 8080

Important: Only HTTP(S) traffic goes through the proxy. SMTP, IMAP, and POP3 ports (25, 465, 587, 143, 993, 110, 995) must still be mapped directly — reverse proxies don’t handle mail protocols.

See Reverse Proxy Setup for general configuration.

Backup

Mailu stores data across several volumes. Back up all of them:

VolumeContentsCritical?
mailu_mailAll user mailboxes (Dovecot Maildir format)Yes — this is your email
mailu_dataAdmin database (users, domains, aliases, settings)Yes — losing this means reconfiguring everything
mailu_dkimDKIM signing keysYes — regenerating means updating DNS
mailu_filterRspamd learned data, Bayes filterNice to have — relearns over time
mailu_certsTLS certificatesOptional — Let’s Encrypt regenerates them
mailu_redisSession data, rate limitsOptional — transient data
mailu_mailqueuePending outgoing emailsBack up if possible — contains unsent mail

Back up the critical volumes:

# Stop services for consistent backup
docker compose stop

# Back up all volumes
tar -czf mailu-backup-$(date +%Y%m%d).tar.gz \
  -C /var/lib/docker/volumes \
  mailu_mail mailu_data mailu_dkim mailu_filter mailu_mailqueue

# Restart services
docker compose start

For zero-downtime backups, use filesystem snapshots or rsync the Maildir directories while Dovecot is running (Maildir format is safe for this).

See Backup Strategy for a comprehensive approach.

Troubleshooting

Emails Going to Spam

Symptom: Outgoing emails land in recipients’ spam folders on Gmail, Outlook, or Yahoo.

Fix: This is almost always a DNS or reputation issue. Check in order:

  1. PTR record: Run dig -x YOUR_SERVER_IP — it must return mail.example.com. If not, contact your hosting provider.
  2. SPF: Run dig TXT example.com — verify your SPF record is present and correct.
  3. DKIM: In the Mailu admin panel, check the DNS page for your domain. Verify the DKIM TXT record matches.
  4. DMARC: Run dig TXT _dmarc.example.com — verify the record exists.
  5. IP reputation: Check your IP at multirbl.valli.org. If blocklisted, you may need to request delisting or use a relay.
  6. Test score: Send a test to mail-tester.com — it shows exactly what’s wrong.

Port 25 Blocked by Provider

Symptom: Outgoing mail times out. Incoming mail may still work (other servers can’t reach you either if inbound 25 is blocked).

Fix: Many cloud providers block port 25 to prevent spam. Options:

  1. Request unblock: Some providers (Hetzner, DigitalOcean, Vultr) will unblock port 25 if you open a support ticket and explain you’re running a legitimate mail server.
  2. Use a relay: Configure outgoing relay through Mailgun, SendGrid, or Amazon SES (see Advanced Configuration). This handles outgoing mail but you still need inbound port 25 for receiving.
  3. Switch providers: If your provider won’t unblock port 25, you need a different host. Hetzner and OVH are reliably mail-friendly.

TLS Certificate Errors

Symptom: Email clients show certificate warnings. Browser shows “Not Secure” for the admin panel.

Fix:

  1. Verify HOSTNAMES in mailu.env exactly matches your DNS A record.
  2. Ensure ports 80 and 443 are reachable from the internet (Let’s Encrypt needs port 80 for HTTP-01 challenge).
  3. Check front container logs: docker compose logs front | grep -i cert
  4. If using TLS_FLAVOR=letsencrypt, restart the front container to trigger a new certificate request: docker compose restart front
  5. Verify there’s no other service (Apache, Nginx) already using ports 80/443 on the host.

Admin Login Fails After Setup

Symptom: Cannot log in to the admin panel despite creating the admin user.

Fix:

  1. Verify the admin user was created: docker compose exec admin flask mailu user-list example.com
  2. Reset the admin password: docker compose exec admin flask mailu admin admin example.com 'NewPassword'
  3. Check that ADMIN=true is set in mailu.env.
  4. Ensure you’re accessing the correct URL: https://mail.example.com/admin (not /admin/ or the webmail URL).

High RAM Usage (ClamAV)

Symptom: Server running out of memory. OOM killer terminating containers.

Fix: ClamAV loads its entire virus signature database into RAM, consuming 1-2 GB. If your server has limited memory:

  1. Disable ClamAV: Comment out the antivirus service in docker-compose.yml and remove ANTIVIRUS=clamav from mailu.env. Rspamd still provides malware detection without ClamAV.
  2. Add swap space as a safety net:
    fallocate -l 2G /swapfile
    chmod 600 /swapfile
    mkswap /swapfile
    swapon /swapfile
    echo '/swapfile none swap sw 0 0' >> /etc/fstab
  3. Monitor usage: docker stats shows per-container memory consumption. The antivirus container is almost always the largest consumer.

Resource Requirements

  • RAM (without ClamAV): 1-1.5 GB idle, 2 GB under load
  • RAM (with ClamAV): 2.5-3.5 GB idle, 4 GB under load
  • CPU: Low to moderate — mail is not CPU-intensive unless processing large attachment volumes
  • Disk: ~500 MB for Mailu itself, plus mailbox storage (budget 1-5 GB per active user depending on retention)
  • Network: Dedicated public IP required. Ports 25, 80, 110, 143, 443, 465, 587, 993, 995 must be accessible

Verdict

Mailu is the best Docker-native mail server for people who want a complete, batteries-included setup without spending days integrating individual components. Its admin UI, automatic DKIM key generation, and built-in webmail make it significantly easier to deploy than assembling Postfix + Dovecot + Rspamd manually.

That said, self-hosting email remains one of the hardest things to self-host. IP reputation is a real problem — if your server IP was previously used for spam, major providers will reject your mail regardless of how perfectly you configure DNS. Port 25 restrictions from cloud providers add another barrier. And unlike other self-hosted services, email has zero tolerance for downtime — if your server is offline, you miss messages.

Compared to Mailcow, Mailu is lighter on resources and simpler to configure. Mailcow has a more polished web UI and includes SOGo for calendar/contacts integration. Choose Mailu if you want a lean, efficient mail stack. Choose Mailcow if you want groupware features alongside email. See our Mailu vs Mailcow comparison for a detailed breakdown.

Recommendation: If you need self-hosted email and understand the challenges, Mailu is the right choice for personal and small-team use. If you just want reliable email and don’t need full server control, a privacy-focused provider like Proton Mail or Fastmail will save you significant operational headaches.

FAQ

Is self-hosting email worth it?

It depends on your motivation. For privacy and data sovereignty — yes, nothing beats running your own server. For cost savings — probably not, since a $4/month Fastmail account is cheaper than the server resources and time investment. For learning — absolutely, running a mail server teaches you more about internet infrastructure than almost any other project.

Can I use Mailu with multiple domains?

Yes. Add additional domains in the admin panel under Mail domains. Each domain can have its own users, aliases, and quotas. You need to configure DNS records (MX, SPF, DKIM, DMARC) for each domain separately.

Will Gmail accept email from my Mailu server?

If your DNS records (SPF, DKIM, DMARC) are correct, your PTR record matches, and your IP isn’t blocklisted — yes. New IP addresses sometimes face initial distrust. Send emails to Gmail addresses gradually (don’t blast 1,000 emails on day one) and Gmail will warm up to your server within a few days to weeks.

How do I migrate email from Gmail to Mailu?

Enable Fetchmail in Mailu (see Advanced Configuration) and configure it to pull from your Gmail account via IMAP. Gmail requires you to enable “Less secure app access” or create an App Password if 2FA is enabled. Fetchmail will download all existing email into your Mailu mailbox.

Can I run Mailu on a Raspberry Pi?

Technically yes, but not recommended. Mailu’s multi-container stack consumes 1-2 GB of RAM without ClamAV. A Raspberry Pi 4 with 4 GB can handle it for a single user, but performance will be marginal. A small VPS or mini PC is a much better choice for a mail server.