From 420ceb49b53a8fa9d8ba8443e42e50cd7bd7cea9 Mon Sep 17 00:00:00 2001 From: Philip Wittamore Date: Sun, 12 Jul 2026 11:08:50 +0200 Subject: update --- .../2025/Mailcow-with-Nginx-reverse-proxy.html | 207 +++++++++++++++++++++ 1 file changed, 207 insertions(+) create mode 100755 articles/2025/Mailcow-with-Nginx-reverse-proxy.html (limited to 'articles/2025/Mailcow-with-Nginx-reverse-proxy.html') diff --git a/articles/2025/Mailcow-with-Nginx-reverse-proxy.html b/articles/2025/Mailcow-with-Nginx-reverse-proxy.html new file mode 100755 index 0000000..657b214 --- /dev/null +++ b/articles/2025/Mailcow-with-Nginx-reverse-proxy.html @@ -0,0 +1,207 @@ + + + + + + + Mailcow with Nginx reverse proxy + + +
+

Bloggings

+ Back + +
+ +
+
+

Mailcow with Nginx reverse proxy (updated)

+

2025-04-19

+

Using other web services with Mailcow

+

This is roughly what I did to have an Nginx web server on the same machine as dockerized +Mailcow

+

Note: mail.xxxxx.com is your mail server (MX), yourdomain.com is whatever domain you wish to use.

+
    +
  1. Change the default http and https ports (for example 8480 and 8443)
    +and set SKIP_LETS_ENCRYPT=y in /opt/mailcow-dockerized/mailcow.conf
  2. +
  3. Restart Mailcow by executing
    +cd /opt/mailcow-dockerized;docker compose restart
  4. +
  5. Make sure the DNS entry mail.xxxxx.com points to the server
  6. +
  7. mkdir -p /var/www/html/letsencrypt/.well-known/acme-challenge
  8. +
  9. Create /etc/nginx/letsencrypt_path. This can be re-used in your other domains. +
    # url for letsencrypt
    +location ^~ /.well-known/acme-challenge/ {
    +  allow all;
    +  default_type "text/plain";
    +  # Path can be used for cert-validation on all domains
    +  root /var/www/html/letsencrypt;
    +  break;
    +}
    +
    +
  10. +
  11. Stop the nginx server systemctl stop nginx
  12. +
  13. Run certbot certonly -d mail.xxxxx.com (select 2 standalone)
  14. +
  15. Create /etc/nginx/sites-available/mail.xxxxx.com
  16. +
    server {
    +  listen 443 ssl;
    +  listen [::]:443 ssl;
    +  http2 on;
    +
    +  server_name mail.xxxxx.com;
    +
    +  charset UTF-8;  
    +  access_log /var/log/nginx/access.mail.xxxxx.com;  
    +  error_log /var/log/nginx/error.mail.xxxxx.com;  
    +  include snippets/error_pages.conf;
    +  ssl_certificate /etc/letsencrypt/live/mail.xxxxx.com/fullchain.pem; 
    +  ssl_certificate_key /etc/letsencrypt/live/mail.xxxxx.com/privkey.pem; 
    +  include /etc/letsencrypt/options-ssl-nginx.conf;
    +  ssl_dhparam /etc/letsencrypt/ssl-dhparams-2048.pem;  
    +
    +  include /etc/nginx/letsencrypt_path;
    +
    +  location / {
    +    proxy_pass      http://127.0.0.1:8480;
    +    proxy_buffering off;
    +    include /etc/nginx/proxy_params;
    +    
    +  }
    +
    +}
    +
    +server {
    +
    +    listen 80;
    +    listen [::]:80;
    +    server_name mail.xxxxx.com;
    +    return 301 https://mail.xxxxx.com;
    +}
    +
    +
  17. ln -s /etc/nginx/sites-available/mail.xxxxx.com
    /etc/nginx/sites-enabled/mail.xxxxx.com
  18. +
  19. Test with nginx -t, if all is well run sudo systemctl start nginx
  20. +
  21. Pointing your browser to https://mail.xxxxx.com should show you the Mailcow login page.
  22. +
+

Problem

+

The problem is that Mailcow can no longer use port 80 to update it's ssl certificates that +are used by postfix and dovecot. Instead certbot puts them in /etc/letsencrypt/live. +To fix this the following script runs from crontab daily.

+
#!/usr/bin/env bash
+
+# we are running behind an nginx proxy, where certbot is run  by systemd,
+# so the mail.xxxxx.com certificates are updated, but not copied to the mailcow folder
+# in case the certificate isn't renewed automatically, run this:
+# sudo certbot -n certonly --webroot -w /var/www/html/letsencrypt -d mail.xxxxx.com
+
+t1="/etc/letsencrypt/live/mail.xxxxx.com/fullchain.pem"
+t2="/opt/mailcow-dockerized/data/assets/ssl/mail.xxxxx.com/cert.pem" 
+
+# test if certificate has been updated
+differ=$(cmp -b $t1 $t2 | grep -c "differ")
+
+[[ "$differ" = "0" ]] && exit 0
+
+# these files are required in /data/assets/ssh/mail.xxxxx.com
+cp /etc/letsencrypt/live/mail.xxxxx.com/fullchain.pem /opt/mailcow-dockerized/data/assets/ssl/mail.xxxxx.com/cert.pem
+cp /etc/letsencrypt/live/mail.xxxxx.com/privkey.pem /opt/mailcow-dockerized/data/assets/ssl/mail.xxxxx.com/key.pem
+
+# these files are required in /data/assets/ssl/ (turns out sending failed otherwise)
+cp /etc/letsencrypt/live/mail.xxxxx.com/fullchain.pem /opt/mailcow-dockerized/data/assets/ssl/cert.pem
+cp /etc/letsencrypt/live/mail.xxxxx.com/privkey.pem /opt/mailcow-dockerized/data/assets/ssl/key.pem
+
+# update postfix & docker
+docker exec $(/usr/bin/docker ps -qaf name=postfix-mailcow) postfix reload
+docker exec $(/usr/bin/docker ps -qaf name=dovecot-mailcow) dovecot reload
+
+

Inspiration: Felix Moesbauer

+

Don't forget to check and correct if necessary your DANE and MTA-STS records if you use them

+

DANE

+

On your mail server:

+
apt install hash-slinger -y
+

Create a TLSA record:

+
tlsa --create --selector 1 -p 25 --certificate /etc/letsencrypt/live/mail.xxxxx.com/fullchain.pem mail.xxxxx.com
+
+

The last line or the result will be something like:

+
_25._tcp.mail.xxxxx.com. IN TLSA 3 1 1 443ac7c5c70fbfbc...
+

Enter this TLSA line in your mail server's DNS record.

+

Do the same for the following ports:

+
_110._tcp.mail.xxxxx.com
+_143._tcp.mail.xxxxx.com
+_465._tcp.mail.xxxxx.com
+_587._tcp.mail.xxxxx.com
+_993._tcp.mail.xxxxx.com
+_995._tcp.mail.xxxxx.com
+
+

You can check your DANE records at huque.com

+

dane success

+

MTA-STS

+

This is a fallback to DANE, you can run both.

+

Note: https access to mta-sts.yourdomain.com is obligatory

+
    +
  1. Create /var/www/html/mta-sts/.well-known/mta-sts.txt with the following content:
    +
    version: STSv1
    +mode: enforce
    +max_age: 172800
    +mx: mail.xxxxx.com
    +
    +
  2. +
  3. Create the DNS record _mta-sts.mail.xxxxx.com as TXT with "v=STSv1; id=INSERT AN ID, EX. 202511101644"
  4. +
  5. Create the DNS record _mta-sts.yourdomain.com as CNAME pointing to _mta-sts.mail.xxxxx.com (your mail server)
  6. +
  7. Create the DNS record mta-sts.yourdomain.com pointing to the ip of the web server
  8. +
  9. You may need to wait for the DNS records to propagate
  10. +
  11. Stop Nginx with systemctl stop nginx
  12. +
  13. Create a letsencrypt certificate with certbot certonly -d mta-sts.yourdomain.com (select 2 standalone)
  14. +
  15. Create mta-sts.yourdomain.com in Nginx:
  16. +
  17. /etc/sites-available/mta-sts.yourdomain.com
    +
    server {
    +   listen 443 ssl;
    +   listen [::]:443 ssl;
    +   http2 on;
    +
    +   charset UTF-8;  
    +   access_log /var/log/nginx/access.mts-sta.yourdomain.com;  
    +   error_log /var/log/nginx/error.mts-sta.yourdomain.com;  
    +   include snippets/error_pages.conf;     
    +   ssl_certificate /etc/letsencrypt/live/mta-sts.yourdomain.com/fullchain.pem;
    +   ssl_certificate_key /etc/letsencrypt/live/mta-sts.yourdomain.com/privkey.pem;
    +   include /etc/letsencrypt/options-ssl-nginx.conf;
    +   ssl_dhparam /etc/letsencrypt/ssl-dhparams-2048.pem;
    +
    +   include /etc/nginx/letsencrypt_path;
    +  
    +   root /var/www/html/mta-sts;
    +   index index.html index.htm;
    +
    +   server_name mta-sts.yourdomain.com;
    +
    +   location / {
    +      try_files $uri $uri/ =404;
    +   }
    +}
    +server {
    +    listen 80;
    +    listen [::]:80;
    +    server_name mta-sts.yourdomain.com;
    +    return 301 https://mta-sts.yourdomain.com;
    +}
    +
    +
  18. +
  19. ln -s /etc/nginx/sites-available/mta-sts.yourdomain.com
    /etc/nginx/sites-enabled/mta-sts.yourdomain.com
  20. +
  21. Test with nginx -t and if all goes well start nginx systemctl start nginx
  22. +
+

You can check your domain's MTA-STS at mxtoolbox.com

+

 

+

All this worked for me :-) I hope it does for you!

+ +
+
+ + + + + + -- cgit v1.3.1