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.
Tag: mailu
-
Running Mailu Behind Caddy with Let’s Encrypt (A Practical Guide)