In this post I will outline, how I integrated Mailcow into an existing setup of docker containers and a reverse proxy, including the distribution of required SSL certificates.

Reverse Proxy

On my webserver, I run several services along with a Mailcow installation as my mailserver. All services are run as docker containers. Since only one service may listen on HTTP(S) ports 80 and 443, I use a reverse proxy. It handles all HTTP and HTTPS connections, dispatches them to the respective containers, and manages all SSL certificates issued by Let’s Encrypt. I chose to use Nginx Proxy for that. To expose a service on a subdomain and generate valid SSL certificates, I just need to add the following environment variables to the container specification:

- VIRTUAL_HOST=subdomain.example.com
- VIRTUAL_PORT=8080 #HTTP port of the container
- LETSENCRYPT_HOST=subdomain.example.com
- LETSENCRYPT_EMAIL=letsencryptemail@example.com

Add Mailcow to the Reverse Proxy

All external HTTP(S) connections are done via the reverse proxy. Therefore, I made the web interface of Mailcow only directly accessible internally, by changing mailcow.conf:

HTTP_PORT=1234
HTTP_BIND=127.0.0.1

HTTPS_PORT=1235
HTTPS_BIND=127.0.0.1

Serving the web interface of Mailcow via the reverse proxy is fairly easy. The configuration for that is done in the docker-compose.override.yml file in Mailcow’s root folder:

version: '2.1'
services:
  nginx-mailcow:
    environment:
      - VIRTUAL_HOST=mail.example.com
      - VIRTUAL_PORT=1234
      - LETSENCRYPT_HOST=mail.example.com
      - LETSENCRYPT_EMAIL=letsencryptemail@example.com

Postfix and Dovecot

Mailcow uses Postfix as an MTA (for SMTP) and Dovecot as an MDA (for POP3 and IMAP). Both services support SSL encryption for their connections, so the certificates generated by the reverse proxy need to be supplied to them as well.

All certificates generated by nginx-proxy are located in a single folder. This folder is provided to postfix and dovecot by a simple symlink:

ln -s $MAILCOW_ROOT/data/assets/ssl $LE_CERT_FOLDER

Postfix and Dovecot expect the correct certificates to be available with specific file names, which are again symlinked:

    cd $LE_CERT_FOLDER
    ln -s key.pem mail.example.com/key.pem
    ln -s dhparams.pem dhparam.pem
    ln -s cert.pem mail.example.com/fullchain.pem

The only remaining problem is that the reverse proxy may renew the certificates and I need to notify Postfix and Dovecot, so that they can load the new certificates. I do that using a small script:

#!/bin/bash
/usr/bin/docker exec $(/usr/bin/docker ps -qaf name=postfix-mailcow) postfix reload
/usr/bin/docker exec $(/usr/bin/docker ps -qaf name=dovecot-mailcow) dovecot reload

This script is called by a cronjob, whenever the certificates did change within the last day.

0 3 * * * find $MAILCOW_ROOT/data/assets/ssl/mail.example.com/key.pem -mmin -1440 -exec <some-folder>/reload_ssl_certs.sh