aboutsummaryrefslogtreecommitdiff
path: root/articles/2025/Mailcow-with-Nginx-reverse-proxy.html
diff options
context:
space:
mode:
authorPhilip Wittamore <philip@wittamore.com>2026-07-12 11:08:50 +0200
committerPhilip Wittamore <philip@wittamore.com>2026-07-12 11:08:50 +0200
commit420ceb49b53a8fa9d8ba8443e42e50cd7bd7cea9 (patch)
tree3009f0564d75e3f7d1ca38507e7ebb92deaded07 /articles/2025/Mailcow-with-Nginx-reverse-proxy.html
update
Diffstat (limited to 'articles/2025/Mailcow-with-Nginx-reverse-proxy.html')
-rwxr-xr-xarticles/2025/Mailcow-with-Nginx-reverse-proxy.html207
1 files changed, 207 insertions, 0 deletions
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 @@
+<!doctype html>
+<html lang="EN">
+ <head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <link rel="stylesheet" href="/style.css">
+ <title>Mailcow with Nginx reverse proxy</title>
+ </head>
+ <body>
+ <header>
+ <h1>Bloggings</h1>
+ <a href="/index.php">Back</a>
+
+ </header>
+
+<main>
+<article>
+<h2>Mailcow with Nginx reverse proxy (updated)</h2>
+<h3>2025-04-19</h3>
+<h4>Using other web services with Mailcow</h4>
+<p>This is roughly what I did to have an Nginx web server on the same machine as dockerized
+<a href="https://mailcow.email/">Mailcow</a></p>
+<p><b>Note:</b> mail.xxxxx.com is your mail server (MX), yourdomain.com is whatever domain you wish to use.</p>
+<ol>
+<li>Change the default http and https ports (for example 8480 and 8443)<br>
+and set <b>SKIP_LETS_ENCRYPT=y</b> in /opt/mailcow-dockerized/mailcow.conf</li>
+<li>Restart Mailcow by executing <br>
+<b>cd /opt/mailcow-dockerized;docker compose restart</b></li>
+<li>Make sure the DNS entry mail.xxxxx.com points to the server</li>
+<li><b>mkdir -p /var/www/html/letsencrypt/.well-known/acme-challenge</b></li>
+<li>Create /etc/nginx/letsencrypt_path. This can be re-used in your other domains.
+<pre># 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;
+}
+</pre>
+</li>
+<li>Stop the nginx server <b>systemctl stop nginx</b></li>
+<li>Run <b>certbot certonly -d mail.xxxxx.com</b> (select 2 standalone)</li>
+<li>Create /etc/nginx/sites-available/mail.xxxxx.com</li>
+<pre>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;
+}
+</pre>
+<li><b>ln -s /etc/nginx/sites-available/mail.xxxxx.com <br>/etc/nginx/sites-enabled/mail.xxxxx.com</b></li>
+<li>Test with <b>nginx -t</b>, if all is well run <b>sudo systemctl start nginx</b></li>
+<li>Pointing your browser to https://mail.xxxxx.com should show you the Mailcow login page.</li>
+</ol>
+<h3>Problem</h3>
+<p>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.</p>
+<pre>#!/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
+</pre>
+<p>Inspiration: <a href="https://felixmoessbauer.com/blog-reader/mailcow-reverse-proxy-letsencrypt.html">Felix Moesbauer</a></p>
+<p>Don't forget to check and correct if necessary your DANE and MTA-STS records if you use them</p>
+<h4>DANE</h4>
+<p>On your mail server:</p>
+<pre>apt install hash-slinger -y</pre>
+<p>Create a TLSA record:</p>
+<pre>tlsa --create --selector 1 -p 25 --certificate /etc/letsencrypt/live/mail.xxxxx.com/fullchain.pem mail.xxxxx.com
+</pre>
+<p>The last line or the result will be something like:</p>
+<pre>_25._tcp.mail.xxxxx.com. IN TLSA 3 1 1 443ac7c5c70fbfbc...</pre>
+<p>Enter this TLSA line in your mail server's DNS record.</p>
+<p>Do the same for the following ports:</p>
+<pre>_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
+</pre>
+<p>You can check your DANE records at <a href="https://www.huque.com/bin/danecheck-smtp">huque.com</a></p>
+<p><img src="/images/blog/dane-success.png" alt="dane success"></p>
+<h4>MTA-STS</h4>
+<p>This is a fallback to DANE, you can run both.</p>
+<p><b>Note:</b> https access to mta-sts.yourdomain.com is obligatory</p>
+<ol>
+<li>Create /var/www/html/mta-sts/.well-known/mta-sts.txt with the following content:<br>
+<pre>version: STSv1
+mode: enforce
+max_age: 172800
+mx: mail.xxxxx.com
+</pre>
+</li>
+<li>Create the DNS record _mta-sts.mail.xxxxx.com as TXT with "v=STSv1; id=<b>INSERT AN ID, EX. 202511101644</b>"</li>
+<li>Create the DNS record _mta-sts.yourdomain.com as CNAME pointing to _mta-sts.mail.xxxxx.com (your mail server)</li>
+<li>Create the DNS record mta-sts.yourdomain.com pointing to the ip of the web server</li>
+<li>You may need to wait for the DNS records to propagate</li>
+<li>Stop Nginx with <b>systemctl stop nginx</b></li>
+<li>Create a letsencrypt certificate with <b>certbot certonly -d mta-sts.yourdomain.com</b> (select 2 standalone)</li>
+<li>Create mta-sts.yourdomain.com in Nginx:</li>
+<li>/etc/sites-available/mta-sts.yourdomain.com<br>
+<pre>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;
+}
+</pre>
+</li>
+<li><b>ln -s /etc/nginx/sites-available/mta-sts.yourdomain.com <br>/etc/nginx/sites-enabled/mta-sts.yourdomain.com</b></li>
+<li>Test with nginx -t and if all goes well start nginx <b>systemctl start nginx</b></li>
+</ol>
+<p>You can check your domain's MTA-STS at <a href="https://mxtoolbox.com/mta-sts.aspx">mxtoolbox.com</a><p>
+<p>&nbsp;</p>
+<p>All this worked for me :-) I hope it does for you!</p>
+
+</article>
+</main>
+<footer>
+
+<p>&nbsp;</p>
+
+<p>ยง</p>
+ </footer>
+</body>
+</html>
+
+
+