Kontakt

HTTPs redirection for Mailcow

by Christoph Dähne on 12.05.2021

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 psmailcowdockerized_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 PermanentlyServer: 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.