Tag: mailu

  • Running Mailu Behind Caddy with Let’s Encrypt (A Practical Guide)


    Self‑hosting email is one of those projects that sounds simple until you actually try it. Between TLS, reverse proxies, Autodiscover, and modern mail client expectations, it’s very easy to end up with redirect loops, broken SMTP, or certificates that load everywhere except where you need them.

    This post walks through a working, real‑world setup: running Mailu behind Caddy with Let’s Encrypt certificates (DNS‑01). It’s written from the perspective of what actually works, not just what looks correct on paper.

    All domains, paths, and identifiers in this post are examples.




    Why This Architecture

    Mail servers are special:

    They need TLS for web, IMAP, and SMTP

    They need predictable hostnames

    They must support Autodiscover for Outlook, Gmail, and mobile clients


    The architecture below solves all of that cleanly:

    Internet
       │
       ▼
    [Caddy :443]  ← public HTTPS
       │
       ▼
    [Mailu Front :443]  ← internal TLS
       │
       ├─ IMAP (993)
       ├─ SMTP (465)
       └─ Webmail / Admin

    Key design choice:

    > Caddy proxies to Mailu’s HTTPS port (443), not HTTP.



    That single decision avoids most redirect and TLS issues.




    Directory Layout (Example)

    Here’s a clean, anonymized layout that works well:

    /opt/mail/
    ├── mailu/
    │   ├── data/
    │   ├── mail/
    │   ├── dkim/
    │   ├── certs/
    │   │   ├── cert.pem
    │   │   └── key.pem
    │   ├── docker-compose.yml
    │   └── mailu.env

    ├── caddy/
    │   ├── Caddyfile
    │   └── docker-compose.yml

    └── letsencrypt/

    You can place this anywhere — /opt, /srv, or a mounted volume.




    Certificates: Let’s Encrypt with DNS‑01

    Mail servers benefit greatly from DNS‑01 validation:

    No open ports required

    Works with multiple subdomains

    Ideal behind proxies


    Requesting a Certificate (Example)

    certbot certonly \
      –dns-provider \
      -d mail.example.com \
      -d autodiscover.example.com \
      -d autoconfig.example.com

    After success:

    /etc/letsencrypt/live/mail.example.com/
    ├── fullchain.pem
    └── privkey.pem




    Making Certificates Available to Mailu

    Mailu expects certificates inside its own volume:

    cp /etc/letsencrypt/live/mail.example.com/fullchain.pem \
       /opt/mail/mailu/certs/cert.pem

    cp /etc/letsencrypt/live/mail.example.com/privkey.pem \
       /opt/mail/mailu/certs/key.pem

    chmod 644 /opt/mail/mailu/certs/cert.pem
    chmod 600 /opt/mail/mailu/certs/key.pem

    If these files are empty or truncated, IMAP and SMTP will fail immediately.




    Mailu Configuration (Minimal, Correct)

    In mailu.env:

    DOMAIN=example.com
    HOSTNAMES=mail.example.com,autodiscover.example.com,autoconfig.example.com
    TLS_FLAVOR=cert
    ENABLE_AUTODISCOVER=true

    WEB_ADMIN=/admin
    WEB_WEBMAIL=/webmail

    This ensures Mailu advertises the correct endpoints to clients.




    Caddy: The Reverse Proxy That Makes This Work

    Here’s the critical part.

    Caddyfile

    mail.example.com,
    autodiscover.example.com,
    autoconfig.example.com {

        @root path /
        redir @root /webmail 302

        reverse_proxy https://mailu-front:443 {
            transport http {
                tls_insecure_skip_verify
            }

            header_up Host {host}
            header_up X-Forwarded-Host {host}
            header_up X-Forwarded-Proto https
            header_up X-Forwarded-Port 443
        }
    }

    Why HTTPS → HTTPS Matters

    Mailu internally enforces HTTPS. If you proxy to port 80, nginx will continuously attempt to “upgrade” requests — even when the client is already using HTTPS.

    Result:

    Infinite 301 redirects

    Broken admin UI

    Webmail never loads


    Proxying to 443 avoids this entirely.




    Starting the Stack

    cd /opt/mail/mailu
    docker compose up -d

    cd /opt/mail/caddy
    docker compose up -d

    Confirm everything is running:

    docker ps




    Verifying the Web Interfaces

    curl -Ik https://mail.example.com/admin/
    curl -Ik https://mail.example.com/webmail/

    Expected:

    No redirect loops

    Login page loads





    Verifying Mail Protocols

    IMAP (993)

    openssl s_client -connect mail.example.com:993 \
      -servername mail.example.com -brief </dev/null

    SMTP (465)

    openssl s_client -connect mail.example.com:465 \
      -servername mail.example.com -brief </dev/null

    Modern Mailu deployments often do not support STARTTLS on 587. Port 465 is the correct choice.




    Autodiscover Test

    curl -X POST https://autodiscover.example.com/autodiscover/autodiscover.xml \
      -H “Content-Type: text/xml” \
      –data ‘<Autodiscover xmlns=”http://schemas.microsoft.com/exchange/autodiscover/outlook/requestschema/2006″>
      <Request>
        <EMailAddress>user@example.com</EMailAddress>
        <AcceptableResponseSchema>
          http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a
        </AcceptableResponseSchema>
      </Request>
    </Autodiscover>’

    Clients should receive IMAP and SMTP settings automatically.




    Automatic Certificate Renewal

    certbot renew –deploy-hook “
      docker restart mailu-front \
      docker restart mailu-imap \
      docker restart mailu-smtp


    This ensures mail protocols reload certificates correctly.




    Lessons Learned

    Proxying Mailu via HTTP causes redirect loops

    Empty certificate files break IMAP/SMTP instantly

    Port 465 is now the safest SMTP option

    DNS‑01 validation is ideal for mail servers





    Final Thoughts

    Mail servers don’t fail loudly — they fail subtly. A single redirect, missing TLS flag, or incorrect port can break everything downstream.

    This setup has proven reliable, debuggable, and future‑proof. If you’re self‑hosting mail today, this architecture will save you hours of frustration.




    All paths and domains shown are examples. Replace them with values appropriate to your environment.

Secret Link