Working Setup: Caddy (public HTTPS) → Mailu Front (internal HTTPS) + Mailu SMTP/IMAP uses same Sectigo cert

Goal

  • https://my.richardapplegate.io/webmail and /admin work
  • Caddy serves the site on public port 443
  • Mailu also serves HTTPS internally on front:443
  • SMTP/IMAP ports (465/993/995/587) use the same Sectigo wildcard cert
  • No port conflicts and no redirect loops

1) Certificates on the host

You have these two files (already confirmed they match):

  • fullchain.pem
  • privkey.pem

Store them in a stable host path, example:

  • /mnt/volumes/certs/fullchain.pem
  • /mnt/volumes/certs/privkey.pem

✅ Verified match test:

openssl x509 -noout -modulus -in fullchain.pem | openssl md5
openssl pkey -noout -modulus -in privkey.pem  | openssl md5

2) Caddy docker-compose

Caddy publishes public ports and has the certs mounted:

services:
  caddy:
    image: caddy:2
    restart: unless-stopped
    networks:
      - caddy
    ports:
      - "80:80"
      - "443:443/tcp"
      - "443:443/udp"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - ./data:/data
      - ./config:/config
      - /mnt/volumes/certs:/certs:ro
networks:
  caddy:
    external: true

3) Mailu env (mailu.env) — corrected values

These are the critical “must be right” ones:

DOMAIN=richardapplegate.io
HOSTNAMES=my,mail

PORTS=25,465,587,993,995,4190

TLS_FLAVOR=cert
TLS_CERT_FILENAME=fullchain.pem
TLS_KEYPAIR_FILENAME=privkey.pem

WEB_ADMIN=/admin
WEB_WEBMAIL=/webmail
WEBSITE=https://my.richardapplegate.io

✅ Important notes:

  • DOMAIN is the apex domain: richardapplegate.io
  • HOSTNAMES are labels only: my,mail (no dots)
  • TLS_FLAVOR is cert (not certs)
  • Do not include 80 or 443 in PORTS (Caddy owns those publicly)

4) Mailu docker-compose networking + cert mounts

4.1 Attach front to BOTH networks

Mailu internal network (for smtp/imap/etc) and the caddy network (so Caddy can reach it):

services:
  front:
    networks:
      - mailu
      - caddy

4.2 Mount certs into Mailu containers that use them

At minimum, mount into front, smtp, and imap (names may vary, but these are typical):

services:
  front:
    volumes:
      - /mnt/volumes/certs/fullchain.pem:/certs/fullchain.pem:ro
      - /mnt/volumes/certs/privkey.pem:/certs/privkey.pem:ro

  smtp:
    volumes:
      - /mnt/volumes/certs/fullchain.pem:/certs/fullchain.pem:ro
      - /mnt/volumes/certs/privkey.pem:/certs/privkey.pem:ro

  imap:
    volumes:
      - /mnt/volumes/certs/fullchain.pem:/certs/fullchain.pem:ro
      - /mnt/volumes/certs/privkey.pem:/certs/privkey.pem:ro

(These filenames match your TLS_CERT_FILENAME / TLS_KEYPAIR_FILENAME.)


5) Caddyfile (the working site)

You terminate TLS at Caddy and proxy to Mailu front over internal HTTPS to avoid redirect loops:

my.richardapplegate.io {
  tls /certs/fullchain.pem /certs/privkey.pem

  # optional: make / go to webmail
  @root path /
  redir @root /webmail 302

  reverse_proxy https://front:443 {
    transport http {
      tls_server_name my.richardapplegate.io
      # Only if chain issues ever happen:
      # tls_insecure_skip_verify
    }

    header_up Host {host}
    header_up X-Forwarded-Proto {scheme}
    header_up X-Forwarded-Host  {host}
    header_up X-Forwarded-For   {remote_host}
  }
}

✅ Why HTTPS upstream (front:443)?
Because with TLS_FLAVOR=cert, Mailu front enforces HTTPS; proxying to http://front:80 triggers redirect loops. HTTPS upstream avoids that entirely.


6) Restart order (clean rebuild)

6.1 Restart Mailu (force recreate so config regenerates)

cd /mnt/volumes/SamsungSSD970EVOPlus2TB/mailu
docker compose down
docker compose up -d --force-recreate

6.2 Reload Caddy

docker exec caddy caddy reload --config /etc/caddy/Caddyfile

7) Verification tests

7.1 Web cert (public 443 via Caddy)

openssl s_client -connect my.richardapplegate.io:443 -servername my.richardapplegate.io </dev/null 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates

7.2 Mailu front cert (internal, from inside caddy container)

docker exec caddy sh -lc \
'openssl s_client -connect front:443 -servername my.richardapplegate.io </dev/null 2>/dev/null | openssl x509 -noout -subject -issuer -dates'

7.3 SMTP TLS cert (465)

openssl s_client -connect my.richardapplegate.io:465 -servername my.richardapplegate.io </dev/null 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates

7.4 IMAPS cert (993)

openssl s_client -connect my.richardapplegate.io:993 -servername my.richardapplegate.io </dev/null 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates

Expected: all show CN=*.richardapplegate.io and Sectigo issuer.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

Secret Link