First, a big thanks to all devs and contributors to this magnificent project. It sure is fun to work with mailcow. I just stumbled across this project a few days ago and decided to give it a try. I’m not a pro, let alone a Linux guru, so I had various issues with certs and Nginx reverse proxy. I managed to solve all of the problems and ended up with a working instance of a mailcow dockerized behind an Nginx reverse proxy, eventually.

Because of all the config involved in setting up Nginx and Let’sencrypt (yes, I’m lazy…), I decided to try Caddy Web Server Icon Caddy server

as a reverse proxy, which I discovered recently. I remembered from my other projects, it was simple to install and it worked out-of-the-box. Caddy has reverse proxy included, HTTPS by default, automatic certs, and renewals. No fuss, almost no config 🙂

I got a 2 core, 4GB RAM Ubuntu 20.04 cloud server at hetzner.cloud Icon Hetzner

(aff. link), for a test. Apart from setting up the Caddy server, I followed the manuals in everything else in detail: prerequisites, setting up docker and docker-compose, open ports and DNS settings.
mailcow.github.io
Redirecting...

Instead of using acme-mailcow to handle the certs, I turned that option off in mailcow.conf. I added an additional server name (my.mail-domain.tld), with which I wanted to access my mailcow host (my.mailcow-host.tld), also. Then I changed HTTP and HTTPS ports and bindings. Here are all the changes I made to the mailcow.conf:

MAILCOW_HOSTNAME=my.mailcow-host.tld
.
.
HTTP_PORT=8080
HTTP_BIND=127.0.0.1
HTTPS_PORT=8443
HTTPS_BIND=127.0.0.1
.
.
ADDITIONAL_SERVER_NAMES=my.mail-domain.tld
.
.
SKIP_LETS_ENCRYPT=y

After docker-compose pull and docker-compose up -d I installed Caddy server like this:

echo "deb [trusted=yes] https://apt.fury.io/caddy/ /" | sudo tee -a /etc/apt/sources.list.d/caddy-fury.list
sudo apt update
sudo apt install caddy libnss3-tools

With Caddy installed I needed to config it as a reverse proxy. To do this I commented everything out in its config file located at /etc/caddy/Caddyfile and added the following:

my.mailcow-host.tld {
reverse_proxy 127.0.0.1:8080
}
my.mail-domain.tld {
reverse_proxy 127.0.0.1:8080
}

I had to reload Caddy afterward with caddy reload in /etc/caddy .

That was it! I just waited a minute or so for Caddy to generate certs. After a short wait, I headed to my.mailcow-host.tld and was presented with a secure, valid Let’s encrypt protected mailcow login! The same was true for my.mail-domain.tld.

After initial config and adding a mail-box for a user@mail-domain.tld I can confirm everything just works! I got myself a working Mailcow-dockerized instance behind Caddy server reverse proxy with automatic Let’s encrypt. All this with almost no config and no hassle. Of course, none of this would be possible without a wonderful team behind Mailcow - Thank You again!

Please note:

  1. This was a test setup. I’m planning to go to production in a few weeks.

  2. For this setup to work and for a Caddy server to be able to issue certs, all DNS records must be correct and must already propagate before the setup.

  3. The link to Hetzner is an affiliate link. It will get you 20€ in cloud credits if you decided to go with them. And I’d receive a small commission 🙂. To admins: remove if inappropriate.

–Robert

    9 months later

    Thanks for the how-to. I am wondering, how mailcow gets the certificates that caddy generated for the IMAP and SMTP ports, though. Does this really work?

    Have something to say?

    Join the community by quickly registering to participate in this discussion. We'd like to see you joining our great moo-community!

    Hello! Yes it works great all that time since the first post. I set up a production server with some email domains.

    As for certs: I created a simple bash script that copies .crt and .key files from caddy’s letsencrypt location to corresponding .pem files in /opt/mailcow-dockerized/data/assets/ssl/

    I also changed SKIP_LETS_ENCRYPT to yes in mailcow.conf so only caddy takes care of certs.

    I’m upgrading the server and mailcow regulary. Apart from a few minor issues averything works beautiful.

    Great to hear. I will try to set it up here the same. How many domains do you use in Mailcow? I would have three. So, you let caddy generate all the certificates and just copy all of them over to Mailcow directory, thereby renaming them to pems? Do you keep the rest of the filenames the same, e.g. in you example:

    copy my.mailcow-host.tld.crt => cert.pem
    copy my.mailcow-host.tld.key => key.pem
    copy my.mailcow-domain.tld.crt => my.mailcow-domain.tld.cert.pem
    copy my.mailcow-domain.tld.key => my.mailcow-domain.tld.key.pem

    Like this?

    Exactly.

    I use three domains, too. I let the first domain be the mail exchanger for all three domains. I only issue cert for the first domain but I’m sure your setup with more https domains should work just the same.

    Please report on your progress.

    7 months later

    This setup also seems to be working fine for me, except autoconfig….if I add

    autoconfig.mail-domain.tld {
    reverse_proxy 127.0.0.1:8080
    }

    to the caddy config, the xml autoconfig information points to my.mailcow-host.tld, whereas I was wanting it to point to my.mail-domain.tld. (I had copied the my.mail-domain.tld certs for use with dovecot.) I’ll probably just turn autoconfig off for now since my personal domain only has a a few users so manual configuration is feasible. Alternatively I’ll just use my principal my.mail-domain.tld domain for both dovecot and the mailcow web interface, and not use separate domains as in the OP.
    The caddy setup is still great since it is easy to add additional applications (e.g. a wordpress blog) to the cloud server. (Maybe it is also easy to run other applications using the NGINX proxy but I am only familiar with caddy. I would consider ditching caddy and reverting back to NGINX only if there was a tutorial on how to extend the existing mailcow-dockerized to add additional applications.)

    2 months later

    Sorry, for reactivation a “old” thread.
    And hopefully, I did not tell anything wrong.

    But let your mailserver have the hostname/domain mail.mydomain.com, there you are running an email configuration for the mail-domain “mymail.com” (like info@mymail.com) then autodiscover.mymail.com should a CNAME to mail.mydomain.com
    If this is done, caddy should work with
    mail.mydomin.com audodiscover.mymail.com autoconfig.mymail.com {
    reverse_proxy 10.20.30.40:8080
    }

    Obviously, there is no mymail.com domain (without auto*). And thats correct, as the mail-server (not mail-domain) is mydomain.com, not mymail.com. –> its correct if autoconfig configure mail.mydomain.com instead mymail.com.
    The domain-switch is done thru the login-name, which contains the mx-domain.

    18 days later

    robertrud Hey there!

    Thank you very much for that tutorial.
    We from the mailcow team would like to see that reverse proxy config in the official mailcow documentation.

    So if you don’t mind we would like to implement it. Or you can implement it either if you want to 🙂

    But yeah, if you don’t have time to thats ok, but i’ll ask for your permission anyways 🙂

    Kind regards
    Niklas (mailcow Dev)

    Hey, nice to hear that! Please, go ahead, implement it!

    It would mean a lot to me. You are right about the time 🙂, so it’d be the best if you could do it.

    Best regards
    Robert

    Of interest is the new eventing system in caddy. I haven’t tried it, but it should allow us to set up a certificate provisioning hook which could copy the new certs to the correct locations so that they can be used for the mailcow servers.

    caddyserver/caddy4984

    Uh! That is awesome.

    I´ll take a look at that and prepare a script for that then which will be part of the Docs.

    More good news…. Can’t wait for this to be implemented.

    Start with that tomorrow. So i guess it´s only a day i think.

    Btw. Caddy is so awesome why did i discovered it just yesterday?

    It’s never too late 🙂

    2 years later

    This is exactly what I was looking for but I ran into some issues that I believe is due to interesting setup and I’m not really sure where to locate the issue but my assumption is it has to do with port 25 since I’m unable to send or receive emails at this time.

    Here’s my setup:

    VPS(Server A) setup running Netmaker as a means of tunneling to my home server (Server B) behind CGNAT and for ACL purposes.

    Mailcow is installed on my Home server (Server B) which is an Ubuntu implementation running in Virtual Box. I have other services that are running without issue at the moment.

    My Caddyfile is on my VPS and I have a declaration in it that points to Mailcow:
    # Mail Server
    https://mail.mydomain.com {
    reverse_proxy serverB:8008
    }

    I know this is working properly for I’m able to access it publicly through my domain.
    I successfully created the necessary DNS records for my domain except for the DKIM which for some reason is not working when searching my domain on dmarcian.com. - However, that’s not my main concern and I have a feeling that will fix itself when I locate the root problem.

    When I attempt to send an email through the Webmail SOGo interface, it fails with “Not allowed in state 1”.
    I have all the necessary ports open on my VPS but when I run:netstat -tulpn | grep -E -w '25|80|110|143|443|465|587|993|995|4190' on my home server (ServerB)

    I get the following and don’t see port 25:
    tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 77424/docker-proxy
    tcp 0 0 0.0.0.0:4190 0.0.0.0:* LISTEN 80220/docker-proxy
    tcp 0 0 0.0.0.0:110 0.0.0.0:* LISTEN 80300/docker-proxy
    tcp 0 0 0.0.0.0:143 0.0.0.0:* LISTEN 80280/docker-proxy
    tcp 0 0 0.0.0.0:443 0.0.0.0:* LISTEN 77352/docker-proxy
    tcp 0 0 0.0.0.0:993 0.0.0.0:* LISTEN 80260/docker-proxy
    tcp 0 0 0.0.0.0:995 0.0.0.0:* LISTEN 80240/docker-proxy
    tcp6 0 0 :::80 :::* LISTEN 77436/docker-proxy
    tcp6 0 0 :::4190 :::* LISTEN 80227/docker-proxy
    tcp6 0 0 :::110 :::* LISTEN 80307/docker-proxy
    tcp6 0 0 :::143 :::* LISTEN 80285/docker-proxy
    tcp6 0 0 :::443 :::* LISTEN 77357/docker-proxy
    tcp6 0 0 :::993 :::* LISTEN 80265/docker-proxy
    tcp6 0 0 :::995 :::* LISTEN 80245/docker-proxy

    I tried to look through the SOGo logs and I found this:
    [67]: [ERROR] <0x0x55f93bb37640[SOGoMailer]> Could not connect to the SMTP server smtp://172.16.1.253:588/?tls=NO&tlsVerifyMode=none

    I had to change the Mailcow network for it was clashing with another service and maybe that’s where my issue lies… I’m not really sure at this point. My first initial guess is that the other ports aren’t binding from my VPS to my Home Server. Not sure how to set that up in my Caddyfile if it should just be the same as I have it above but for each port that is used by Caddy?

    The other issue also might be that NGINX is running in the Mailcow container and I also have an instance of NGINX Proxy Manager running on my home server to direct traffic to the proper services… Very complicated I know but fun?

    Lastly, I cant seem to locate where my SSL certs are being stored so that I can move them to mailcow as you mentioned. Caddy is running in a docker compose that Netmaker automatically setups and looks like this:
    caddy:
    image: caddy:2.6.2
    container_name: caddy
    env_file: ./netmaker.env
    restart: unless-stopped
    extra_hosts:
    - "host.docker.internal:host-gateway"
    volumes:
    - ./Caddyfile:/etc/caddy/Caddyfile
    - caddy_data:/data
    - caddy_conf:/config
    - /root/www:/var/www/mydomain.com
    ports:
    - "80:80"
    - "443:443"

    volumes:
    caddy_data: { } # runtime data for caddy
    caddy_conf: { } # configuration file for Caddy
    sqldata: { }
    dnsconfig: { } # storage for coredns
    mosquitto_logs: { } # storage for mqtt logs
    mosquitto_data: { } # storage for mqtt data

    I’m pretty sure the caddy_data { } contains the lets encrypt certs but I can’t figure out where that volume is. Or maybe it’s not persisted and just runs in the container? Which would be fun to figure out how to copy the certs in there to mailcow but I digress.

    Not asking for complete support but maybe something sticks out to someone and I can slap my head and buy a beer 😃.

    Thanks in advanced to any help or direction provided!

    Hi, this is a bit of an overhead to me… with all the configs involved. I hope you’ll find the error. In situations like this you just have to check and check again…

    3 months later

    @DerLinkman
    Somehow I don’t quite understand your script… This only works for mail.example.com and not for autoconfig.example.com etc. (what about additional domains?). Especially as autoconfig is also entered as autoconfig.mail.example.com in the caddy file, isn’t it?

    Are the capitalized things like HTTP_BIND and MAILCOW_HOSTNAME just placeholders (or vars which dont work for me?)? The hostnames have to be repeated for each domain, can’t this be generalized?

    Maybe I’m understanding something completely wrong, but if I switch off the internal certificate tool, I have to handle everything in your file and caddy file.

    Can’t you just leave the ACME of Mailcow switched on and then use caddy to provide the certificates of the domain (mail.example.com) with the web interface?

    Sorry for all the questions, but in my head this is getting ultra complicated with multiple domains and covering all subdomains and additional subdomains.

    Thanks for the help