Introduction

This guide provides detailed steps to set up Mailcow on a VPS, configure it to bypass port 25 by using SendGrid for outgoing emails, and manage SSL/TLS using OpenResty as a reverse proxy.

Prerequisites

  1. A VPS with Docker and Docker Compose installed.

  2. A domain name (e.g., lunapapa.eu).

  3. A SendGrid account with an API key.

  4. OpenResty installed and configured via Docker.

  5. Cloudflare account for managing SSL/TLS certificates.

Step-by-Step Guide

1. Clone Mailcow Repository

git clone https://github.com/mailcow/mailcow-dockerized
cd mailcow-dockerized
./generate_config.sh

2. Configure mailcow.conf

Edit the mailcow.conf file to match your domain setup:

MAILCOW_HOSTNAME=<your-mail-server-domain>


# Internal HTTP port for Mailcow containers
HTTP_PORT=8001
HTTP_BIND=0.0.0.0

# HTTPS is handled by reverse proxy
HTTPS_PORT=
HTTPS_BIND=

3. Adjust nginx-mailcow service with following setup

nginx-mailcow:
  depends_on:
    - sogo-mailcow
    - php-fpm-mailcow
    - redis-mailcow
  image: nginx:mainline-alpine
  dns:
    - ${IPV4_NETWORK:-172.22.1}.254
  command: /bin/sh -c "envsubst < /etc/nginx/conf.d/templates/listen_plain.template > /etc/nginx/conf.d/listen_plain.active &&
    envsubst < /etc/nginx/conf.d/templates/sogo.template > /etc/nginx/conf.d/sogo.active &&
    . /etc/nginx/conf.d/templates/server_name.template.sh > /etc/nginx/conf.d/server_name.active &&
    . /etc/nginx/conf.d/templates/sites.template.sh > /etc/nginx/conf.d/sites.active &&
    . /etc/nginx/conf.d/templates/sogo_eas.template.sh > /etc/nginx/conf.d/sogo_eas.active &&
    nginx -qt &&
    until ping phpfpm -c1 > /dev/null; do sleep 1; done &&
    until ping sogo -c1 > /dev/null; do sleep 1; done &&
    until ping redis -c1 > /dev/null; do sleep 1; done &&
    until ping rspamd -c1 > /dev/null; do sleep 1; done &&
    exec nginx -g 'daemon off;'"
  environment:
    - HTTP_PORT=${HTTP_PORT:-8001}
    - MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME}
    - IPV4_NETWORK=${IPV4_NETWORK:-172.22.1}
    - TZ=${TZ}
    - SKIP_SOGO=${SKIP_SOGO:-n}
    - ALLOW_ADMIN_EMAIL_LOGIN=${ALLOW_ADMIN_EMAIL_LOGIN:-n}
    - ADDITIONAL_SERVER_NAMES=${ADDITIONAL_SERVER_NAMES:-}
  volumes:
    - ./data/web:/web:ro,z
    - ./data/conf/rspamd/dynmaps:/dynmaps:ro,z
    - ./data/assets/ssl/:/etc/ssl/mail/:ro,z
    - ./data/conf/nginx/:/etc/nginx/conf.d/:z
    - ./data/conf/rspamd/meta_exporter:/meta_exporter:ro,z
    - sogo-web-vol-1:/usr/lib/GNUstep/SOGo/
  ports:
    - "${HTTP_BIND:-}:${HTTP_PORT:-8001}:${HTTP_PORT:-8001}"
  restart: always
  networks:
    mailcow-network:
      aliases:
        - nginx

4 Remove the listen_ssl.active file:

rm ./data/conf/nginx/listen_ssl.active

5 Adjust the sites.template.sh

echo '
server {
  listen 127.0.0.1:65510;
  include /etc/nginx/conf.d/listen_plain.active;

  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_plain.active;

  server_name '${domains}';

  include /etc/nginx/conf.d/includes/site-defaults.conf;
}
';
done

6. Start all the services

docker-compose up -d

7. Set Up SendGrid as SMTP Relay

Enter the Postfix container and edit the main.cf configuration file:

docker exec -it mailcowdockerized-postfix-mailcow-1 /bin/bash
nano /opt/postfix/conf/main.cf

Add the following lines to use SendGrid for outgoing emails

relayhost = [smtp.sendgrid.net]:587
smtp_sasl_auth_enable = yes
smtp_sasl_password_maps = hash:/opt/postfix/conf/sasl_passwd
smtp_sasl_security_options = noanonymous
smtp_tls_security_level = may
header_size_limit = 4096000

Create and populate the sasl_passwd file:

nano /opt/postfix/conf/sasl_passwd

Add your SendGrid credentials:

[smtp.sendgrid.net]:587 apikey:YOUR_SENDGRID_API_KEY

Hash the password file and reload Postfix:

postmap /opt/postfix/conf/sasl_passwd
postfix reload
exit

8. Configure SSL/TLS with OpenResty

Create a website with reverse proxy setup

server {
    listen 80;
    server_name <your-mail-server-domain>;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name <your-mail-server-domain>;

    ssl_certificate /etc/ssl/certs/cert.pem;
    ssl_certificate_key /etc/ssl/private/key.pem;

    location / {
        proxy_pass http://mailcow-nginx-mailcow:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    location /webmail {
        proxy_pass http://mailcow-nginx-mailcow:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Restart OpenResty to apply the changes:

docker restart openresty

9. Set Up SPF, DKIM, and DMARC Records

Check details in Cloudflare as well as SendGrid to have your specific configuration.

10 Check the SSL configuration using OpenSSL:

openssl s_client -connect <YOUR_MAILCOW_HOSTNAME>:993 -crlf
openssl s_client -connect <YOUR_MAILCOW_HOSTNAME>:995 -crlf
openssl s_client -connect <YOUR_MAILCOW_HOSTNAME>:465 -crlf
openssl s_client -connect <YOUR_MAILCOW_HOSTNAME>:587 -crlf

Conclusion

By following these steps, you've successfully set up Mailcow on a VPS with SendGrid for outgoing emails and managed SSL/TLS using OpenResty as a reverse proxy. This setup ensures secure and reliable email communication for your domain.