Closed michacassola closed 6 years ago
Feel free to submit a pull request to the docs repository if you have a working configuration. It canโt hurt to have documentation on another possible reverse proxy.
Never heard of it, thanks for pointing it out. Sounds interesting!
I stumble upon a lot of tutorials with using treafik for their docker reverse proxy needs. Thats why I wanted to ask to include it. Unfortunately I am not code fit yet to help out with making a documentation. I am still learning myself and was hoping to host some other docker stuff on the mailcow server. Thanks for your quick responses!
Integrating mailcow in traefik is rather simple.
ports
in the nginx-mailcow section in docker-compose.yml
.docker-compose.override.yml
or add the statements to the original docker-compose.yml
(change traefik_web
to your trafik-network):version: '2.3'
services:
nginx-mailcow:
expose:
- "80"
labels:
- "traefik.backend=mailcow"
- "traefik.docker.network=traefik_web"
- "traefik.enable=true"
- "traefik.port=80"
- "traefik.frontend.rule=Host:${MAILCOW_HOSTNAME}"
networks:
web:
networks:
web:
external:
name: traefik_web
Thanks!
This should be included in the docs ๐ธ
Maybe add some SSL support with Lets Encrypt please?
Not coming from an expert, but... This is wrong. If it was to do some real support, it would have to be direct. And not this whole turns around. A reverse proxy from another proxy ... Makes no sense.
By doing this you can not take full advantage of Traefik. Like that Traefik can't balance the instances with autonomy for example. Doing it as shown above. this becomes unviable.
Traefik is a reverse proxy, in addition to balance and have integration with Prometheus. Generating good control and stats. Through it you can see the situation of the server in real time and advance the resolutions of failures. Has Encryption out of the box and Supports gRPC.
By the way, to give support with Traefik. You just need to pass the Labels on instances that need to have ports exposed externally. And configure Traefik to manage these ports. That would be port 80 and the other e-mail ports like IMAP, POP3 and so on.
Anyway, Traefik is much better option.
Isnt Traefik http/s only? How do you proxy IMAPS, POP3S, SMTPS and Submission with it? AFAIK Traefik cant handle TCP.
@ledodev Your comment would only make any sense if you would want to run multiple mail servers on the same IP address...
EDIT: I guess it was directed at @MichelDiz . I don't think Traefik can be used as a load-balancer for mail servers just like that.
MichelDiz mentioned in his last comment he would proxy these ports. I was just curious how with Traefik.
Yeah, I mentioned it in my edit. There's no point in trying to "load-balance" mail servers via domain-routing on traefik-level on the same IP address. It just makes no sense to me at all. Instead you would employ several MX records on the DNS of different priority and then keep the servers synchronized I think. @andryyy correct me if I'm wrong.
@Braintelligence sorry i missed your edit before writing :) i think the same
To whom it might help:
certdumper:
container_name: traefik_certdumper
image: alpine:latest
depends_on:
- traefik
restart: unless-stopped
volumes:
- $PWD:/traefik
- /opt/mailcow-dockerized/data/assets/ssl:/ssl
command: >
ash -c " \
apk --no-cache add inotify-tools jq openssl util-linux bash && \
wget https://raw.githubusercontent.com/containous/traefik/91ff94ea56587a3afa398b572bb7ae4185849091/contrib/scripts/dumpcerts.sh -O dumpcerts.sh && \
mkdir -p /traefik/ssl/ && \
while true; do \
inotifywait -e modify /traefik/acme.json && \
bash dumpcerts.sh /traefik/acme.json /traefik/ssl/ && \
ln -f /traefik/ssl/certs/* /traefik/ssl/ && \
ln -f /traefik/ssl/private/* /traefik/ssl/ && \
mv /traefik/ssl/private/${MAILCOW_HOSTNAME}.key /ssl/key.pem && \
mv /traefik/ssl/certs/${MAILCOW_HOSTNAME}.crt /ssl/cert.pem; \
done"
The container watches acme.json and explodes it on change to certs&keys for each domain. After certs get splited push cert for ${MAILCOW_HOSTNAME} to /data/assets/ssl for reuse in SMTPS, IMAPS, Submission. Maybe Dovecot and Postfix need to be reloaded after pushing new cert, I dont know.
In fact, at the time I said I had no idea that there was no support for email protocols. I went to question and I discovered that Traefik does not deal with these protocols other than gRPC. Sorry about that.
here: https://github.com/containous/traefik/issues/1047
I commented because I had seen you using nginx and using Traefik to point to it. For me this adds load to the server. I believe that nginx should also not deal with email protocols. I am wrong? (But as it is not being used for this, it would not come to the case.)
How can I get this working when traefik is deployed with network_mode: bridge?
docker-compose override is a good choice for adding reverse proxy capabilities to mailcow, but there is a twist. Unfortunatly, it is not possible to override already declared options for a service or a service as a whole (like ports or volumes). Is it possible or feasable to export all custom options that we have into an docker-compose.override.yml and remove them from the main docker-compose?
For example, i use traefik for reverse proxying the web interface as well as mailcow's acme client. i can override every aspect of mailcow suited for my setup except mailcow-nginx's port definition.
nginx service in modified docker-compose.yml
nginx-mailcow:
depends_on:
- sogo-mailcow
- php-fpm-mailcow
- redis-mailcow
image: nginx:mainline-alpine
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/listen_ssl.template > /etc/nginx/conf.d/listen_ssl.active &&
envsubst < /etc/nginx/conf.d/templates/server_name.template > /etc/nginx/conf.d/server_name.active &&
envsubst < /etc/nginx/conf.d/templates/sogo.template > /etc/nginx/conf.d/sogo.active &&
envsubst < /etc/nginx/conf.d/templates/sogo_eas.template > /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:
- HTTPS_PORT=${HTTPS_PORT:-443}
- HTTP_PORT=${HTTP_PORT:-80}
- MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME}
- IPV4_NETWORK=${IPV4_NETWORK:-172.22.1}
- TZ=${TZ}
volumes:
- ./data/web:/web:ro
- ./data/conf/rspamd/dynmaps:/dynmaps:ro
- ./data/assets/ssl/:/etc/ssl/mail/:ro
- ./data/conf/nginx/:/etc/nginx/conf.d/:rw
- ./data/conf/rspamd/meta_exporter:/meta_exporter:ro
volumes_from:
- sogo-mailcow
restart: always
dns:
- ${IPV4_NETWORK:-172.22.1}.254
networks:
mailcow-network:
aliases:
- nginx
configurable options should go in docker-compose.override.yml by default
nginx-mailcow:
expose:
- "80"
# ports:
# - "${HTTPS_BIND:-0.0.0.0}:${HTTPS_PORT:-443}:${HTTPS_PORT:-443}"
# - "${HTTP_BIND:-0.0.0.0}:${HTTP_PORT:-80}:${HTTP_PORT:-80}"
my setup with traefik as reverseproxy. acme challenges are redirected to nginx-mailcow, so postfix, dovecot and so on are getting independed, valid certificates.
traefik.toml
defaultEntryPoints = ["http", "https"]
[entryPoints]
[entryPoints.http]
address = ":80"
[entryPoints.http.redirect]
entryPoint = "https"
[entryPoints.https]
address = ":443"
[entryPoints.https.tls]
[docker]
endpoint = "unix:///var/run/docker.sock"
domain = "example.org"
watch = true
exposedbydefault = false
[acme]
email = "user@example.org"
storage = "/etc/traefik/acme.json"
#caServer = "https://acme-staging-v02.api.letsencrypt.org/directory"
entryPoint = "https"
acmeLogging = true
onHostRule = true
[acme.dnsChallenge]
# i have to use dns for wildcard certificates
provider = "ovh"
delayBeforeCheck = 5
[[acme.domains]]
main = "*.example.org"
sans = ["example.org"]
in docker-compose.yml, removed port definition for nginx-mailcow service but add an exposed port
[..]
nginx-mailcow:
[..]
# ports:
# - "${HTTPS_BIND:-0.0.0.0}:${HTTPS_PORT:-443}:${HTTPS_PORT:-443}"
# - "${HTTP_BIND:-0.0.0.0}:${HTTP_PORT:-80}:${HTTP_PORT:-80}"
expose:
- "80"
[..]
created docker-compose.override.yml
services:
nginx-mailcow:
# autodiscover is used by acme container
labels:
- "traefik.enable=true"
- "traefik.https.frontend.rule=Host:example.org"
- "traefik.docker.network=reverseproxy"
- "traefik.autodiscover.frontend.rule=Host:autodiscover.example.org"
networks:
reverseproxy:
# make rspam accesable, too
rspamd-mailcow:
labels:
- "traefik.rspamd.frontend.rule=Host:example.org;Path:/rspamd"
networks:
reverseproxy:
external: yes
@svengo could you please help me how would this work if I am running traefik on host network directly? Below is my setup:
version: '3.7'
services:
traefik:
image: traefik
container_name: traefik
restart: always
ports:
- 80:80
- 443:443
- 8081:8081
network_mode: host
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- $PWD/traefik.toml:/traefik.toml
- $PWD/acme.json:/acme.json
labels:
- "traefik.enable=true"
- "traefik.frontend.rule=Host:traefik.example.com"
- "traefik.port=8081"
command:
- "--logLevel=INFO"
debug = false
logLevel = "INFO"
defaultEntryPoints = ["https","http"]
[entryPoints]
[entryPoints.http]
address = ":80"
[entryPoints.http.redirect]
entryPoint = "https"
[entryPoints.https]
address = ":443"
[entryPoints.https.tls]
[entryPoints.traefik]
address = ":8081"
[entryPoints.traefik.auth.basic]
users = ["admin:XXXXXXXXXXXXXXXXXX",]
[api]
dashboard = true
entryPoint = "traefik"
[retry]
[web]
address = ":8081"
[docker]
endpoint = "unix:///var/run/docker.sock"
domain = "traefik.example.com"
watch = true
exposedByDefault = false
[acme]
email = "admin@example.com"
storage = "acme.json"
entryPoint = "https"
onHostRule = true
acmeLogging = true
KeyType = "RSA4096"
[acme.httpChallenge]
entryPoint = "http"
[[acme.domains]]
main = "example.com"
sans = [
"example.com",
"www.example.com",
"mail.example.com",
"gitlab.example.com",
"cloud.example.com",
]
[file]
############
# Backends #
############
[backends]
#mailcow
# [backends.mailcow]
# [backends.mailcow.servers.1]
# url = "http://127.0.0.1:52180"
# weight = 1
#netdata
[backends.netdata]
[backends.netdata.servers.1]
url = "http://127.0.0.1:19999"
weight = 1
#discourse
[backends.discourse]
[backends.discourse.servers.1]
url = "http://127.0.0.1:59180"
weight = 1
#############
# Frontends #
#############
[frontends]
#mailcow
# [frontends.mailcow]
# backend = "mailcow"
# entryPoints = ["http", "https"]
# passHostHeader = true
# [frontends.mailcow.headers]
# forceSTSHeader = true
# STSSeconds = 315360000
# STSIncludeSubdomains=true
# STSPreload = true
# [frontends.mailcow.routes.1]
# rule = "Host:mail.example.com"
#netdata
[frontends.netdata]
backend = "netdata"
entryPoints = ["http", "https"]
passHostHeader = true
basicAuth = ["admin:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",]
[frontends.netdata.headers]
forceSTSHeader = true
STSSeconds = 315360000
STSIncludeSubdomains=true
STSPreload = true
[frontends.netdata.routes.1]
rule = "Host:stats.example.com"
#discourse
[frontends.discourse]
backend = "discourse"
entryPoints = ["http", "https"]
passHostHeader = true
[frontends.discourse.headers]
forceSTSHeader = true
STSSeconds = 315360000
STSIncludeSubdomains=true
STSPreload = true
[frontends.discourse.routes.1]
rule = "Host:forum.example.com"
@MacGyver27, I don't know why you configure traefik manually (and use host base networking), maybe you should follow the quick start guide and the reference first.
But how does mailcow get the certificate that Traefik generated with acme?
I don't know if there is an easier way, but I am doing the following:
I am using acme-cert-dump-all.py called by /etc/cron.weekly/traefik-acme-certificate-update
to extract the certificates from traefik:
#!/bin/sh
cert_dump="/usr/local/bin/acme-cert-dump-all.py"
acme_json="/opt/docker/volumes/traefik/acme.json"
cert_dir="/opt/docker/volumes/traefik/certs"
# clean up
rm --force ${cert_dir}/*
# dump certs
"${cert_dump}" "${acme_json}" "${cert_dir}" > /dev/null
# fix permissions
chmod 0700 "${cert_dir}"
find "${cert_dir}" -type f -exec chmod 0600 {} \;
# restart containers
postfix_c=$(docker ps -qaf name=postfix-mailcow)
dovecot_c=$(docker ps -qaf name=dovecot-mailcow)
nginx_c=$(docker ps -qaf name=nginx-mailcow)
docker restart "${postfix_c}" "${dovecot_c}" "${nginx_c}"
The certificate must be mounted in the containers so I added some lines to docker-compose.override.yml
:
version: '2.1'
services:
dovecot-mailcow:
userns_mode: host
volumes:
- /opt/docker/volumes/traefik/certs/${MAILCOW_HOSTNAME}.pem:/etc/ssl/mail/mail.pem:ro
postfix-mailcow:
userns_mode: host
volumes:
- /opt/docker/volumes/traefik/certs/${MAILCOW_HOSTNAME}.pem:/etc/ssl/mail/mail.pem:ro
nginx-mailcow:
userns_mode: host
networks:
- web
labels:
- "traefik.backend=nginx-mailcow"
- "traefik.docker.network=traefik_web"
- "traefik.enable=true"
- "traefik.port=8080"
- "traefik.frontend.rule=Host:${MAILCOW_HOSTNAME}"
volumes:
- /opt/docker/volumes/traefik/certs/${MAILCOW_HOSTNAME}.pem:/etc/ssl/mail/mail.pem:ro
Dovecot must be configured to use the certificate, in data/conf/dovecot/extra.conf
add
# SSL/TLS support: yes, no, required. <doc/wiki/SSL.txt>
ssl = required
ssl_cert = </etc/ssl/mail/mail.pem
ssl_key = </etc/ssl/mail/mail.pem
and for postfix, in data/conf/postfix/main.cf
add
smtpd_tls_cert_file = /etc/ssl/mail/mail.pem
smtpd_tls_key_file = ${smtpd_tls_cert_file}
@svengo because some dockerized apps does not allow you to specify network they should connect to - simple as that.. one example is discourse.. then I just asked myself, why not to run in natively on host. Saw no conflict went ahead..
I managed to solve this think today and I think the best way so far possible.
All I did was put this adapter into traefik compose file, set it up it matches my setup of mailcow and traefik. I strongly recommend using SANs for your subdomains. Happened to me I run out of tries (50 generations per week, this way 1 cert is used also for the specified subdomains in SANs array - limit 100).
My final settings then:
version: '3.7'
services:
traefik:
image: traefik:latest
container_name: traefik
domainname: example.com
restart: always
ports:
- 80:80
- 443:443
- 8081:8081
network_mode: host
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /docker/.shared:/shared
- $PWD/traefik/traefik.toml:/traefik.toml
- $PWD/traefik/acme.json:/acme.json
#- $PWD/traefik:/etc/traefik
environment:
- CLOUDFLARE_EMAIL=${CLOUDFLARE_EMAIL}
- CLOUDFLARE_API_KEY=${CLOUDFLARE_API_KEY}
labels:
- "traefik.enable=true"
- "traefik.backend=traefik"
- "traefik.frontend.rule=Host:traefik.example.com"
- "traefik.port=8081"
- "traefik.frontend.headers.SSLRedirect=true"
- "traefik.frontend.headers.STSSeconds=315360000"
- "traefik.frontend.headers.browserXSSFilter=true"
- "traefik.frontend.headers.contentTypeNosniff=true"
- "traefik.frontend.headers.forceSTSHeader=true"
- "traefik.frontend.headers.SSLHost=example.com"
- "traefik.frontend.headers.STSIncludeSubdomains=true"
- "traefik.frontend.headers.STSPreload=true"
- "traefik.frontend.headers.frameDeny=true"
certdumper:
container_name: traefik_mailcow_cert_adapter
image: jovobe/mailcow-traefik-acme-adapter:latest
depends_on:
- traefik
restart: always
volumes:
- ./traefik:/traefik:ro
- /docker/mailcow/data/assets/ssl:/ssl-share:rw
- /var/run/docker.sock:/var/run/docker.sock:rw
environment:
- DOMAIN=example.com
debug = false
logLevel = "INFO"
defaultEntryPoints = ["https","http"]
[entryPoints]
[entryPoints.http]
address = ":80"
[entryPoints.http.redirect]
entryPoint = "https"
[entryPoints.https]
address = ":443"
[entryPoints.https.tls]
[entryPoints.traefik]
address = ":8081"
[entryPoints.traefik.auth.basic]
users = ["admin:XXXXXXXXXXXXXXXXXXXXXXXXX",]
[api]
dashboard = true
entryPoint = "traefik"
[retry]
[web]
address = ":8081"
#[metrics]
# [metrics.prometheus]
# entryPoint = "traefik"
# buckets = [0.1,0.3,1.2,5.0]
[docker]
endpoint = "unix:///var/run/docker.sock"
domain = "example.com"
watch = true
exposedByDefault = false
[acme]
#caServer = "https://acme-staging-v02.api.letsencrypt.org/directory" <- use for testing purposes!!!
email = "admin@example.com"
storage="acme.json"
entryPoint = "https"
onHostRule = true
acmeLogging = true
onDemand = false #create certificate when container is created
[acme.httpChallenge]
entryPoint = "http"
[acme.dnsChallenge]
provider = "cloudflare"
delayBeforeCheck = 0
[[acme.domains]]
main = "example.com"
sans = [
"www.example.com",
"chat.example.com",
"cloud.example.com",
"collabora.example.com",
"forum.example.com",
"gitlab.example.com",
"glances.example.com",
"mail.example.com",
"whoami.example.com"
]
[file]
############
# Backends #
############
[backends]
#mailcow
[backends.mailcow]
[backends.mailcow.servers.1]
url = "http://127.0.0.1:52180"
weight = 1eight = 1
#discourse
[backends.discourse]
[backends.discourse.servers.1]
url = "http://127.0.0.1:59180"
weight = 1
#############
# Frontends #
#############
[frontends]
#mailcow
[frontends.mailcow]
backend = "mailcow"
entryPoints = ["http", "https"]
passHostHeader = true
[frontends.mailcow.headers]
forceSTSHeader = true
STSSeconds = 315360000
STSIncludeSubdomains=true
STSPreload = true
[frontends.mailcow.routes.1]
rule = "Host:mail.example.com"
#discourse
[frontends.discourse]
backend = "discourse"
entryPoints = ["http", "https"]
passHostHeader = true
[frontends.discourse.headers]
forceSTSHeader = true
STSSeconds = 315360000
STSIncludeSubdomains=true
STSPreload = true
[frontends.discourse.routes.1]
rule = "Host:forum.example.com"
@DanseAtEntropy: could you please explain which of your traefik.toml settings cause the acme redirect to mailcow? I use the acme.httpChallenge. With your settings the mailcow UI, rspamd and SoGO works fine, but postfix, dovecot and so on are NOT getting independent certificates but use self-signed certificates.
@DanseAtEntropy: could you please explain which of your traefik.toml settings cause the acme redirect to mailcow? I use the acme.httpChallenge. With your settings the mailcow UI, rspamd and SoGO works fine, but postfix, dovecot and so on are NOT getting independent certificates but use self-signed certificates.
Did you figure this out? I'm at this same point.
Edit: FYI I've gone a different route. Traefik v2 is getting my wildcard cert which applies against 443 and mailcow/acme gets the autodiscover,autocconfig,mail cert which only gets loaded by the imaps service currently (a different problem)
@romprod Did you get everything working? I have a single server docker swarm with Traefik v2 automatic letsencrypt working from wildcard DNS and I'm looking to have mailcow-dockerized running along side it. I'm thinking it might be easier just to run certbot on the host and have a renew post-hook script copy the certs to a bind mount for whichever containers need them.
I just let the mailcow acme client grab the certs for the subdomains that it needed and left Traefik to grab a wildcard certificate which covered what was being passed through Traefik.
I had issues witht he mailcow cert and the HTTP challenge passing but I think that's down to HTTP redirect to HTTPS which looked like it was causing it to fail. In the end I found that I have somehow received certs for the mailcow stuff (but was still getting the HTTP challenge failed error) and everything was working.
I'll revisit the mailcow acme client in a few months when the cert needs renewing, maybe the acme will have been updated or fixed by then, or maybe I'll be able to spot the problem!
You can pass through HTTP and HTTPS with the following labels on nginx-mailcow in mailcows docker-compose.yml:
- traefik.enable=true
# delete if you have a general https redirection in traefik before
# otherwise you have to define the redirect for mailcow in mailcow-nginx
- traefik.http.routers.mail.rule=Host(`mail.example.com`)
- traefik.http.routers.mail.entrypoints=web
# end of delete
- traefik.tcp.routers.mailsecure.rule=HostSNI(`mail.example.com`)
- traefik.tcp.routers.mailsecure.entrypoints=websecure
- traefik.tcp.routers.mailsecure.tls.passthrough=true
- traefik.tcp.services.mailsecure.loadbalancer.server.port=443
Additionally, remove the port bindings (to host ports) for nginx-mailcow.
After these changes, the traefik debug log indicated that traefik was catching LetsEncrypt's challenge call ("Error getting challenge for token retrying in...") instead of passing it to nginx from mailcow. We managed to let mailcow handle the certificates itself by switching to TLS challenges (instead of default HTTP) by using the following config:
[certificatesResolvers.le.acme]
email = "mail@example.com"
storage = "acme.json"
[certificatesResolvers.le.acme.tlsChallenge]
When traefik uses this challenge type, it apparently does not try to respond to all urls containing ".well-known/acme-challenge" as it did before, so the challenge call from LE reaches the mailcow nginx. Alternatively, if you need to use validation via HTTP challenge in traefik, you could try to "convert" this to traefik 2.0 config format: https://gist.github.com/micw/67faf5cd3d4a6f64568ca2bb9a051230
For testing you should be using LE staging in order to not exceed the rate limits to fast
@andryyy Can you confirm whenever you find the time if this configuration doesn't leave anything to be desired concerning the mailcow stack? From what you always wrote I could imagine that some kind of inter-container communication could make some trouble.
I can't verify it. Someone needs to test it, confirm and add it to the docs. :) You can cross check it against the existing configuration presets. That would help me a lot!
This is my current setup now, and right now everything is working. We will create some accounts and sign up on some newsletters to see if it's working nice over a period of 1-2weeks.
I think this solutions is far better than copying certs via any type of job since it lets mailcow handle certs itself.
By the way, if you still want to copy over the certs there is an official docker image called jobber. With jobber everything happens in the container, not on the host machine.
For Traefik 2 and using SSL Passthrough:
In docker-compose.yml, comment the ports in nginx-mailcow.
In docker-compose.override.yml, put the following:
version: '2.1'
services:
nginx-mailcow:
labels:
# Docker
- "traefik.enable=true"
- "traefik.docker.network=mailcow-network"
# Routing HTTP (for acme challenge)
- "traefik.http.routers.mailcow.entrypoints=http"
- "traefik.http.routers.mailcow.rule=Host(`example.com`) || Host(`autoconfigure.example.com`)"
- "traefik.http.services.mailcow.loadBalancer.server.port=80"
# Routing HTTPs
- "traefik.tcp.routers.mailcow.entrypoints=https"
- "traefik.tcp.routers.mailcow.tls.passthrough=true"
- "traefik.tcp.routers.mailcow.rule=HostSNI(`mail.example`) || HostSNI(`autodiscover.example.com`)"
- "traefik.tcp.services.mailcow.loadBalancer.server.port=443"
Finally, configure the http
and https
endpoints in traefik.
EDIT: Fix according to comment below
there's a typo in the above answer from @jdorel
- "traefik.http.routers.mailcow.rule=Host(`mail.dorel.me`) || Host(`autoconfigure.dorel.me`)"
- "traefik.http.services.mailcow.loadBalancer.server.port=80"
For Traefik 2 and using SSL Passthrough:
In docker-compose.yml, comment the ports in nginx-mailcow.
In docker-compose.override.yml, put the following:
version: '2.1' services: nginx-mailcow: labels: # Docker - "traefik.enable=true" - "traefik.docker.network=mailcow-network" # Routing HTTP (for acme challenge) - "traefik.http.routers.mailcow.entrypoints=http" - "traefik.http.routers.mailcow.rule=Host(`example.com`) || Host(`autoconfigure.example.com`)" - "traefik.http.services.mailcow.loadBalancer.server.port=80" # Routing HTTPs - "traefik.tcp.routers.mailcow.entrypoints=https" - "traefik.tcp.routers.mailcow.tls.passthrough=true" - "traefik.tcp.routers.mailcow.rule=HostSNI(`mail.example`) || HostSNI(`autodiscover.example.com`)" - "traefik.tcp.services.mailcow.loadBalancer.server.port=443"
Finally, configure the
http
andhttps
endpoints in traefik.EDIT: Fix according to comment below
You can pass through HTTP and HTTPS with the following labels on nginx-mailcow in mailcows docker-compose.yml:
- traefik.enable=true # delete if you have a general https redirection in traefik before # otherwise you have to define the redirect for mailcow in mailcow-nginx - traefik.http.routers.mail.rule=Host(`mail.example.com`) - traefik.http.routers.mail.entrypoints=web # end of delete - traefik.tcp.routers.mailsecure.rule=HostSNI(`mail.example.com`) - traefik.tcp.routers.mailsecure.entrypoints=websecure - traefik.tcp.routers.mailsecure.tls.passthrough=true - traefik.tcp.services.mailsecure.loadbalancer.server.port=443
Additionally, remove the port bindings (to host ports) for nginx-mailcow.
After these changes, the traefik debug log indicated that traefik was catching LetsEncrypt's challenge call ("Error getting challenge for token retrying in...") instead of passing it to nginx from mailcow. We managed to let mailcow handle the certificates itself by switching to TLS challenges (instead of default HTTP) by using the following config:
[certificatesResolvers.le.acme] email = "mail@example.com" storage = "acme.json" [certificatesResolvers.le.acme.tlsChallenge]
When traefik uses this challenge type, it apparently does not try to respond to all urls containing ".well-known/acme-challenge" as it did before, so the challenge call from LE reaches the mailcow nginx. Alternatively, if you need to use validation via HTTP challenge in traefik, you could try to "convert" this to traefik 2.0 config format: https://gist.github.com/micw/67faf5cd3d4a6f64568ca2bb9a051230
For testing you should be using LE staging in order to not exceed the rate limits to fast
@jdorel @xFirestorm
Are the above your full changes on top of the latest mailcow compose file?
If not, then would you mind posting them (and citing any additional magic that has them playing nicely)?
Thanks for your work! :)
Hey @dm17 Actually we didn't write further comments in this issue, because our setup is working nicely for about a year now. We have the current compose in use, we directly pull from git regularly and we are working with an overwrite compose file.
Our plan was to update the mailcow documentation for traefik 2 but sadly time is scarce and we didn't yet manage to do it and probably won't be able to in the near future. So if someone here is motivated to do so, feel free to use our setup as an example for the docs.
The only change to the original compose is commenting out the port bindings at line 339-341 (at the time of this comment). The output of git diff docker-compose.yml shows:
- ./data/conf/nginx/:/etc/nginx/conf.d/:rw,Z
- ./data/conf/rspamd/meta_exporter:/meta_exporter:ro,z
- sogo-web-vol-1:/usr/lib/GNUstep/SOGo/:z
- ports:
- - "${HTTPS_BIND:-0.0.0.0}:${HTTPS_PORT:-443}:${HTTPS_PORT:-443}"
- - "${HTTP_BIND:-0.0.0.0}:${HTTP_PORT:-80}:${HTTP_PORT:-80}"
+ #ports:
+ # - "${HTTPS_BIND:-0.0.0.0}:${HTTPS_PORT:-443}:${HTTPS_PORT:-443}"
+ # - "${HTTP_BIND:-0.0.0.0}:${HTTP_PORT:-80}:${HTTP_PORT:-80}"
restart: always
networks:
mailcow-network:
The overwrite compose file looks like this:
version: '2.1'
services:
nginx-mailcow:
networks:
traefik:
labels:
- traefik.enable=true
- traefik.http.routers.webmail-redirect.rule=Host(`webmail.example.com`)
- traefik.http.routers.webmail-redirect.tls.certresolver=le
- traefik.http.routers.webmail-redirect.middlewares=webmail-redirect
- traefik.http.middlewares.webmail-redirect.redirectregex.regex=^https://webmail.example.com(.*)
- traefik.http.middlewares.webmail-redirect.redirectregex.replacement=https://mail.example.com/SOGo$${1}
- traefik.tcp.routers.mailsecure.rule=${TRAEFIK_TCP_RULE}
- traefik.tcp.routers.mailsecure.entrypoints=websecure
- traefik.tcp.routers.mailsecure.tls.passthrough=true
- traefik.tcp.services.mailsecure.loadbalancer.server.port=12443
networks:
mailcow-network:
driver: bridge
driver_opts:
com.docker.network.bridge.name: br-mailcow
enable_ipv6: false
ipam:
driver: default
config:
- subnet: ${IPV4_NETWORK:-172.22.1}.0/24
- subnet: ${IPV6_NETWORK:-fd4d:6169:6c63:6f77::/64}
traefik:
external: true
From our env:
TRAEFIK_TCP_RULE=HostSNI(`mail.example.com`) || HostSNI(`autoconfig.example.com`) || HostSNI(`autodiscover.example.com`)
The traefik.toml file looks like this:
[global]
checkNewVersion = true
sendAnonymousUsage = true
[entryPoints]
[entryPoints.web]
address = ":80"
[entryPoints.websecure]
address = ":443"
[log]
#level = "DEBUG"
[api]
dashboard = true
[ping]
[providers]
providersThrottleDuration = "10s"
[providers.docker]
exposedByDefault = false
network = "traefik"
[certificatesResolvers.le.acme]
email = "certs@example.com"
storage = "/certs/acme.json"
# caServer = "https://acme-staging-v02.api.letsencrypt.org/directory" -> If testing to ensure not being limited by le
[certificatesResolvers.le.acme.tlsChallenge]
- traefik.tcp.services.mailsecure.loadbalancer.server.port=12443
does this imply that you set HTTPS_PORT to 12443 in your mailcow.conf?
docker-compose ps
only shows nginx listening on port 80.
Also do the Host and HostSNI here match those on mailcow.conf?
SKIP_ACME=n
?
Would be nice to see all files modifications together.
Has anyone else got this working?
@itsb Right, we changed the HTTPS_PORT to 12443 (HTTP_PORT to 1280) and HTTP_BIND and HTTPS_BIND to 127.0.0.1. 12443/1280 were picked randomly, just to avoid collisions with traefik bound to 80 and 443. As the nginx-mailcow container is using this port internally, we need to tell traefik which one it is (via the loadbalancer.server.port label). With changing the HTTP_BIND and HTTPS_BIND to 127.0.0.1, you can even leave the docker-compose.yml unchanged (no need to comment out the port bindings), as the service is then listening on the loopback interface and only available to the outside world via the reverse proxy. The other way round, if you change the docker-compose.yml, you can leave the mailcow.conf/.env unchanged (but remember to tell traefik it's port 443 and not port 12443 then).
You can see nginx listening on 12443 after running netstat -tulpn
in docker exec -it {nginx_container} sh
The hostname in the first part of the traefik rule (HostSNI(mail.example.com)
) is the same as the MAILCOW_HOSTNAME
in mailcow.conf/.env
We didn't change SKIP_ACME
and as mailcow handles its certs itself, ACME is actived (SKIP_ACME=n
).
Hi,
I didn't see Traefik in this documentation: https://mailcow.github.io/mailcow-dockerized-docs/firststeps-rp/ Can you please add it? Thank you in advance!