HTTPs redirection for Mailcow

At Sandstorm we use Mailcow. It comes with a very nice Web-UI both for administration and accessing mails, calendars and contacts. User can enable 2-factor-authentication which is great.

One details caught our attention though: users can access the Web-UI via an unencrypted HTTP-connection. Here I want to explain how we fixed that.

Desired behavior - what we want.

It is fine that the Web-UI is reachable via HTTP at port 80. However users should be redirected to the encrypted HTTPs connection on port 443. Nothing else than redirects should ever happen at port 80:

  • http://mailserver/ -> 301 Moved Permanently https://mailserver/
  • http://mailserver/sogo -> 301 Moved Permanently https://mailserver/sogo

The patch - where to change what configuration.

As I did not find any built-in flag like enableHttpsRedirect I patched a configuration file. Hopefully it will survive updates, time will tell. Mailcow uses a nginx server which runs in an own Docker container. The nginx container handles connections incoming on the host server at ports 80 and 443.

$ docker-compose ps … mailcowdockerized_nginx-mailcow_1 … 0.0.0.0:443->443/tcp, 0.0.0.0:80->80/tcp …

A volume contains the nginx configuration for this container. You can find it on the host machine at data/conf/nginx/. We would have to adjust the sites.active file. However a template auto-generates this file so all changes will be lost as soon as we restart the nginx container. We have to adjust the template instead. You find it at data/conf/nginx/templates/sites.template.sh.

We have to adjust the server configuration a bit. Here you see the template file. All changes will go to the highlighted area.

echo ' server { listen 127.0.0.1:65510; include /etc/nginx/conf.d/listen_plain.active; include /etc/nginx/conf.d/listen_ssl.active; ssl_certificate /etc/ssl/mail/cert.pem; ssl_certificate_key /etc/ssl/mail/key.pem; include /etc/nginx/conf.d/server_name.active; include /etc/nginx/conf.d/includes/site-defaults.conf; } '; for cert_dir in /etc/ssl/mail/*/ ; do if [[ ! -f ${cert_dir}domains ]] || [[ ! -f ${cert_dir}cert.pem ]] || [[ ! -f ${cert_dir}key.pem ]]; then continue fi # do not create vhost for default-certificate. the cert is already in the default server listen domains="$(cat ${cert_dir}domains | sed -e 's/^[[:space:]]*//')" case "${domains}" in "") continue;; "${MAILCOW_HOSTNAME}"*) continue;; esac echo -n ' server { include /etc/nginx/conf.d/listen_ssl.active; ssl_certificate '${cert_dir}'cert.pem; ssl_certificate_key '${cert_dir}'key.pem; '; echo -n ' server_name '${domains}'; include /etc/nginx/conf.d/includes/site-defaults.conf; } '; done

Now move the listen_plain.active include into an own server block, add the permanent redirect and we are good to go.

echo ' server { include /etc/nginx/conf.d/listen_plain.active; return 301 https://$host$request_uri; } server { listen 127.0.0.1:65510; include /etc/nginx/conf.d/listen_ssl.active; ssl_certificate /etc/ssl/mail/cert.pem; ssl_certificate_key /etc/ssl/mail/key.pem; include /etc/nginx/conf.d/server_name.active; include /etc/nginx/conf.d/includes/site-defaults.conf; } '; for cert_dir in /etc/ssl/mail/*/ ; do if [[ ! -f ${cert_dir}domains ]] || [[ ! -f ${cert_dir}cert.pem ]] || [[ ! -f ${cert_dir}key.pem ]]; then continue fi # do not create vhost for default-certificate. the cert is already in the default server listen domains="$(cat ${cert_dir}domains | sed -e 's/^[[:space:]]*//')" case "${domains}" in "") continue;; "${MAILCOW_HOSTNAME}"*) continue;; esac echo -n ' server { include /etc/nginx/conf.d/listen_ssl.active; ssl_certificate '${cert_dir}'cert.pem; ssl_certificate_key '${cert_dir}'key.pem; '; echo -n ' server_name '${domains}'; include /etc/nginx/conf.d/includes/site-defaults.conf; } '; done

As you see we added four lines and removed one line. After a restart of the nginx container all unencrypted HTTP requests are redirected to HTTPs.

$ docker-compose restart nginx-mailcow

A quick test in the end with curl confirms that the change works and is deployed.

$ curl -I http://mailserver HTTP/1.1 301 Moved Permanently Server: nginx Date: Wed, 12 May 2021 06:38:51 GMT Content-Type: text/html Content-Length: 162 Connection: keep-alive Location: https://mailserver/

Thanks for reading. I hope you found what you where looking for. If you have any comments or questions feel free to contact us.