You've already forked Mailu
mirror of
https://github.com/Mailu/Mailu.git
synced 2025-07-17 01:32:29 +02:00
Merge #2903
2903: Make traefik work r=mergify[bot] a=nextgens ## What type of PR? enhancement ## What does this PR do? Document how traefik is meant to be configured: only Mailu needs certificates, we don't need certdumper. Add a check to ensure we don't "burn" credit with letsencrypt because of a reverse proxy misconfiguration (where port 80 isn't redirected to Mailu) ### Related issue(s) - closes #1422 - closes #1038 - #2855 - closes #2683 - #1502 ## Prerequisites Before we can consider review and merge, please make sure the following list is done and checked. If an entry in not applicable, you can check it or remove it from the list. - [x] In case of feature or enhancement: documentation updated accordingly - [x] Unless it's docs or a minor change: add [changelog](https://mailu.io/master/contributors/workflow.html#changelog) entry file. Co-authored-by: Florent Daigniere <nextgens@freenetproject.org> Co-authored-by: Florent Daigniere <nextgens@users.noreply.github.com>
This commit is contained in:
@ -28,8 +28,6 @@ def nginx_authentication():
|
||||
response = flask.Response()
|
||||
response.headers['Auth-Status'] = status
|
||||
response.headers['Auth-Error-Code'] = code
|
||||
if int(flask.request.headers['Auth-Login-Attempt']) < 10:
|
||||
response.headers['Auth-Wait'] = '3'
|
||||
return response
|
||||
raw_password = urllib.parse.unquote(headers['Auth-Pass']) if 'Auth-Pass' in headers else ''
|
||||
headers = nginx.handle_authentication(flask.request.headers)
|
||||
@ -45,8 +43,6 @@ def nginx_authentication():
|
||||
response = flask.Response()
|
||||
response.headers['Auth-Status'] = status
|
||||
response.headers['Auth-Error-Code'] = code
|
||||
if int(flask.request.headers['Auth-Login-Attempt']) < 10:
|
||||
response.headers['Auth-Wait'] = '3'
|
||||
return response
|
||||
is_valid_user = True
|
||||
if headers.get("Auth-Status") == "OK":
|
||||
|
@ -22,7 +22,7 @@ http {
|
||||
|
||||
{% if REAL_IP_HEADER %}
|
||||
real_ip_header {{ REAL_IP_HEADER }};
|
||||
{% elif PROXY_PROTOCOL in ['all', 'http'] %}
|
||||
{% elif PROXY_PROTOCOL in ['all', 'all-but-http', 'http'] %}
|
||||
real_ip_header proxy_protocol;
|
||||
{% endif %}
|
||||
|
||||
@ -104,9 +104,9 @@ http {
|
||||
|
||||
# Only enable HTTPS if TLS is enabled with no error and not on kubernetes
|
||||
{% if not KUBERNETES_INGRESS and TLS and not TLS_ERROR %}
|
||||
listen 443 ssl http2{% if PROXY_PROTOCOL in ['all', 'http'] %} proxy_protocol{% endif %};
|
||||
listen 443 ssl http2{% if PROXY_PROTOCOL in ['all', 'all-but-http', 'http'] %} proxy_protocol{% endif %};
|
||||
{% if SUBNET6 %}
|
||||
listen [::]:443 ssl http2{% if PROXY_PROTOCOL in ['all', 'http'] %} proxy_protocol{% endif %};
|
||||
listen [::]:443 ssl http2{% if PROXY_PROTOCOL in ['all', 'all-but-http', 'http'] %} proxy_protocol{% endif %};
|
||||
{% endif %}
|
||||
|
||||
include /etc/nginx/tls.conf;
|
||||
@ -315,7 +315,7 @@ mail {
|
||||
ssl_session_cache shared:SSLMAIL:3m;
|
||||
{% endif %}
|
||||
|
||||
{% if PROXY_PROTOCOL in ['all', 'mail'] and REAL_IP_FROM %}{% for from_ip in REAL_IP_FROM.split(',') %}
|
||||
{% if PROXY_PROTOCOL in ['all', 'all-but-http', 'mail'] and REAL_IP_FROM %}{% for from_ip in REAL_IP_FROM.split(',') %}
|
||||
set_real_ip_from {{ from_ip }};
|
||||
{% endfor %}{% endif %}
|
||||
|
||||
@ -324,9 +324,9 @@ mail {
|
||||
|
||||
# SMTP is always enabled, to avoid losing emails when TLS is failing
|
||||
server {
|
||||
listen 25{% if PROXY_PROTOCOL in ['all', 'mail'] %} proxy_protocol{% endif %};
|
||||
listen 25{% if PROXY_PROTOCOL in ['all', 'all-but-http', 'mail'] %} proxy_protocol{% endif %};
|
||||
{% if SUBNET6 %}
|
||||
listen [::]:25{% if PROXY_PROTOCOL in ['all', 'mail'] %} proxy_protocol{% endif %};
|
||||
listen [::]:25{% if PROXY_PROTOCOL in ['all', 'all-but-http', 'mail'] %} proxy_protocol{% endif %};
|
||||
{% endif %}
|
||||
{% if TLS and not TLS_ERROR %}
|
||||
{% if TLS_FLAVOR in ['letsencrypt','mail-letsencrypt'] %}
|
||||
|
@ -73,7 +73,7 @@ service managesieve-login {
|
||||
executable = managesieve-login
|
||||
inet_listener sieve {
|
||||
port = 4190
|
||||
{%- if PROXY_PROTOCOL in ['all', 'mail'] %}
|
||||
{%- if PROXY_PROTOCOL in ['all', 'all-but-http', 'mail'] %}
|
||||
haproxy = yes
|
||||
{% endif %}
|
||||
}
|
||||
@ -90,7 +90,7 @@ protocol imap {
|
||||
service imap-login {
|
||||
inet_listener imap {
|
||||
port = 143
|
||||
{%- if PROXY_PROTOCOL in ['all', 'mail'] %}
|
||||
{%- if PROXY_PROTOCOL in ['all', 'all-but-http', 'mail'] %}
|
||||
haproxy = yes
|
||||
{% endif %}
|
||||
}
|
||||
@ -99,7 +99,7 @@ service imap-login {
|
||||
{%- if TLS %}
|
||||
ssl = yes
|
||||
{% endif %}
|
||||
{%- if PROXY_PROTOCOL in ['all', 'mail'] %}
|
||||
{%- if PROXY_PROTOCOL in ['all', 'all-but-http', 'mail'] %}
|
||||
haproxy = yes
|
||||
{% endif %}
|
||||
}
|
||||
@ -111,7 +111,7 @@ service imap-login {
|
||||
service pop3-login {
|
||||
inet_listener pop3 {
|
||||
port = 110
|
||||
{%- if PROXY_PROTOCOL in ['all', 'mail'] %}
|
||||
{%- if PROXY_PROTOCOL in ['all', 'all-but-http', 'mail'] %}
|
||||
haproxy = yes
|
||||
{% endif %}
|
||||
}
|
||||
@ -120,7 +120,7 @@ service pop3-login {
|
||||
{%- if TLS %}
|
||||
ssl = yes
|
||||
{% endif %}
|
||||
{%- if PROXY_PROTOCOL in ['all', 'mail'] %}
|
||||
{%- if PROXY_PROTOCOL in ['all', 'all-but-http', 'mail'] %}
|
||||
haproxy = yes
|
||||
{% endif %}
|
||||
}
|
||||
@ -137,7 +137,7 @@ service lmtp {
|
||||
service submission-login {
|
||||
inet_listener submission {
|
||||
port = 587
|
||||
{%- if PROXY_PROTOCOL in ['all', 'mail'] %}
|
||||
{%- if PROXY_PROTOCOL in ['all', 'all-but-http', 'mail'] %}
|
||||
haproxy = yes
|
||||
{% endif %}
|
||||
}
|
||||
@ -146,7 +146,7 @@ service submission-login {
|
||||
{%- if TLS %}
|
||||
ssl = yes
|
||||
{% endif %}
|
||||
{%- if PROXY_PROTOCOL in ['all', 'mail'] %}
|
||||
{%- if PROXY_PROTOCOL in ['all', 'all-but-http', 'mail'] %}
|
||||
haproxy = yes
|
||||
{% endif %}
|
||||
}
|
||||
|
@ -1,9 +1,15 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import logging as log
|
||||
import os
|
||||
import time
|
||||
import requests
|
||||
import sys
|
||||
import subprocess
|
||||
import time
|
||||
from threading import Thread
|
||||
from http.server import HTTPServer, SimpleHTTPRequestHandler
|
||||
|
||||
log.basicConfig(stream=sys.stderr, level="WARNING")
|
||||
hostnames = ','.join(set(host.strip() for host in os.environ['HOSTNAMES'].split(',')))
|
||||
|
||||
command = [
|
||||
@ -39,8 +45,34 @@ command2 = [
|
||||
# Wait for nginx to start
|
||||
time.sleep(5)
|
||||
|
||||
class MyRequestHandler(SimpleHTTPRequestHandler):
|
||||
def do_GET(self):
|
||||
if self.path == '/testing':
|
||||
self.send_response(204)
|
||||
else:
|
||||
self.send_response(404)
|
||||
self.send_header('Content-Type', 'text/plain')
|
||||
self.end_headers()
|
||||
|
||||
def serve_one_request():
|
||||
with HTTPServer(("0.0.0.0", 8008), MyRequestHandler) as server:
|
||||
server.handle_request()
|
||||
|
||||
# Run certbot every day
|
||||
while True:
|
||||
while True:
|
||||
hostname = os.environ['HOSTNAMES'].split(' ')[0]
|
||||
target = f'http://{hostname}/.well-known/acme-challenge/testing'
|
||||
thread = Thread(target=serve_one_request)
|
||||
thread.start()
|
||||
r = requests.get(target)
|
||||
if r.status_code != 204:
|
||||
log.error(f"Can't reach {target}!, please ensure it's fixed or change the TLS_FLAVOR.")
|
||||
time.sleep(5)
|
||||
else:
|
||||
break
|
||||
thread.join()
|
||||
|
||||
subprocess.call(command)
|
||||
subprocess.call(command2)
|
||||
time.sleep(86400)
|
||||
|
@ -1,144 +0,0 @@
|
||||
version: '2'
|
||||
|
||||
services:
|
||||
|
||||
# This would normally not be here, but where you define your system services
|
||||
traefik:
|
||||
image: traefik:alpine
|
||||
command: --docker
|
||||
restart: always
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
volumes:
|
||||
- "/var/run/docker.sock:/var/run/docker.sock"
|
||||
- "/data/traefik/acme.json:/acme.json"
|
||||
- "/data/traefik/traefik.toml:/traefik.toml"
|
||||
# This may be needed (plus defining mailu_default external: true) if traefik lives elsewhere
|
||||
# networks:
|
||||
# - mailu_default
|
||||
|
||||
certdumper:
|
||||
restart: always
|
||||
image: mailu/traefik-certdumper:$VERSION
|
||||
environment:
|
||||
# Make sure this is the same as the main=-domain in traefik.toml
|
||||
# !!! Also don’t forget to add "TRAEFIK_DOMAIN=[...]" to your .env!
|
||||
- DOMAIN=$TRAEFIK_DOMAIN
|
||||
# Set TRAEFIK_VERSION to v2 in your .env if you're using Traefik v2
|
||||
- TRAEFIK_VERSION=${TRAEFIK_VERSION:-v1}
|
||||
volumes:
|
||||
- "/data/traefik:/traefik"
|
||||
- "$ROOT/certs:/output"
|
||||
|
||||
front:
|
||||
image: mailu/nginx:$VERSION
|
||||
restart: always
|
||||
env_file: .env
|
||||
labels: # Traefik labels for simple reverse-proxying
|
||||
- "traefik.enable=true"
|
||||
- "traefik.port=80"
|
||||
- "traefik.frontend.rule=Host:$TRAEFIK_DOMAIN"
|
||||
- "traefik.docker.network=mailu_default"
|
||||
ports:
|
||||
- "$BIND_ADDRESS4:110:110"
|
||||
- "$BIND_ADDRESS4:143:143"
|
||||
- "$BIND_ADDRESS4:993:993"
|
||||
- "$BIND_ADDRESS4:995:995"
|
||||
- "$BIND_ADDRESS4:25:25"
|
||||
- "$BIND_ADDRESS4:465:465"
|
||||
- "$BIND_ADDRESS4:587:587"
|
||||
- "$BIND_ADDRESS6:110:110"
|
||||
- "$BIND_ADDRESS6:143:143"
|
||||
- "$BIND_ADDRESS6:993:993"
|
||||
- "$BIND_ADDRESS6:995:995"
|
||||
- "$BIND_ADDRESS6:25:25"
|
||||
- "$BIND_ADDRESS6:465:465"
|
||||
- "$BIND_ADDRESS6:587:587"
|
||||
volumes:
|
||||
- "$ROOT/overrides/nginx:/overrides"
|
||||
- /data/traefik/ssl/$TRAEFIK_DOMAIN.crt:/certs/cert.pem
|
||||
- /data/traefik/ssl/$TRAEFIK_DOMAIN.key:/certs/key.pem
|
||||
|
||||
redis:
|
||||
image: redis:alpine
|
||||
restart: always
|
||||
volumes:
|
||||
- "$ROOT/redis:/data"
|
||||
|
||||
imap:
|
||||
image: mailu/dovecot:$VERSION
|
||||
restart: always
|
||||
env_file: .env
|
||||
volumes:
|
||||
- "$ROOT/mail:/mail"
|
||||
- "$ROOT/overrides:/overrides"
|
||||
depends_on:
|
||||
- front
|
||||
|
||||
smtp:
|
||||
image: mailu/postfix:$VERSION
|
||||
restart: always
|
||||
env_file: .env
|
||||
volumes:
|
||||
- "$ROOT/overrides:/overrides"
|
||||
depends_on:
|
||||
- front
|
||||
|
||||
antispam:
|
||||
image: mailu/rspamd:$VERSION
|
||||
restart: always
|
||||
env_file: .env
|
||||
volumes:
|
||||
- "$ROOT/filter:/var/lib/rspamd"
|
||||
- "$ROOT/dkim:/dkim"
|
||||
- "$ROOT/overrides/rspamd:/etc/rspamd/override.d"
|
||||
depends_on:
|
||||
- front
|
||||
|
||||
antivirus:
|
||||
image: mailu/$ANTIVIRUS:$VERSION
|
||||
restart: always
|
||||
env_file: .env
|
||||
volumes:
|
||||
- "$ROOT/filter:/data"
|
||||
|
||||
webdav:
|
||||
image: mailu/$WEBDAV:$VERSION
|
||||
restart: always
|
||||
env_file: .env
|
||||
volumes:
|
||||
- "$ROOT/dav:/data"
|
||||
|
||||
admin:
|
||||
image: mailu/admin:$VERSION
|
||||
restart: always
|
||||
env_file: .env
|
||||
volumes:
|
||||
- "$ROOT/data:/data"
|
||||
- "$ROOT/dkim:/dkim"
|
||||
depends_on:
|
||||
- redis
|
||||
|
||||
webmail:
|
||||
image: "mailu/$WEBMAIL:$VERSION"
|
||||
restart: always
|
||||
env_file: .env
|
||||
volumes:
|
||||
- "$ROOT/webmail:/data"
|
||||
- "$ROOT/overrides/$WEBMAIL:/overrides:ro"
|
||||
depends_on:
|
||||
- imap
|
||||
|
||||
fetchmail:
|
||||
image: mailu/fetchmail:$VERSION
|
||||
restart: always
|
||||
env_file: .env
|
||||
|
||||
networks:
|
||||
default:
|
||||
driver: bridge
|
||||
ipam:
|
||||
driver: default
|
||||
config:
|
||||
- subnet: $SUBNET
|
@ -1,33 +0,0 @@
|
||||
# This is just boilerplate stuff you probably have in your own config
|
||||
logLevel = "INFO"
|
||||
defaultEntryPoints = ["https","http"]
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
[entryPoints.http.redirect]
|
||||
entryPoint = "https"
|
||||
[entryPoints.https]
|
||||
address = ":443"
|
||||
[entryPoints.https.tls]
|
||||
|
||||
[docker]
|
||||
endpoint = "unix:///var/run/docker.sock"
|
||||
watch = true
|
||||
exposedByDefault = false
|
||||
|
||||
# Make sure we get acme.json saved, and onHostRule enabled
|
||||
[acme]
|
||||
email = "your@mail.tld"
|
||||
storage = "acme.json"
|
||||
entryPoint = "https"
|
||||
onHostRule = true
|
||||
|
||||
[acme.httpChallenge]
|
||||
entryPoint = "http"
|
||||
|
||||
# This should include all of your mail domains, and main= should be your $TRAEFIK_DOMAIN
|
||||
[[acme.domains]]
|
||||
main = "mail.example.com"
|
||||
sans = ["web.mail.example.com", "smtp.mail.example.com", "imap.mail.example.com"]
|
||||
|
342
docs/reverse.rst
342
docs/reverse.rst
@ -2,258 +2,136 @@ Using an external reverse proxy
|
||||
===============================
|
||||
|
||||
One of Mailu's use cases is as part of a larger services platform, where maybe
|
||||
other Web services are available than just Mailu Webmail and Admin interfaces.
|
||||
other Web services are available on other FQDNs served from the same IP address.
|
||||
|
||||
In such a configuration, one would usually run a frontend reverse proxy to serve all
|
||||
Web contents based on criteria like the requested hostname (virtual hosts)
|
||||
and/or the requested path.
|
||||
|
||||
The Mailu Admin Web frontend is disabled in the default setup for security reasons,
|
||||
it is however expected that most users will enable it at some point. Also, due
|
||||
to the Docker Compose configuration structure, it is impossible for us to facilitate
|
||||
disabling the Web frontend with a configuration variable. This guide was written to
|
||||
help users setup such an architecture.
|
||||
|
||||
There are basically three options, from the most to the least recommended one:
|
||||
|
||||
- `have Mailu Web frontend listen locally and use your own Web frontend on top of it`_
|
||||
- `use Traefik in another container as central system-reverse-proxy`_
|
||||
- `override Mailu Web frontend configuration`_
|
||||
|
||||
All options will require that you modify the ``docker-compose.yml`` and ``mailu.env`` file.
|
||||
|
||||
Mailu must also be configured with the information what header is used by the reverse proxy for passing the remote client IP.
|
||||
This is configured in the mailu.env file. See the :ref:`configuration reference <reverse_proxy_headers>` for more information.
|
||||
|
||||
Have Mailu Web frontend listen locally
|
||||
--------------------------------------
|
||||
|
||||
The simplest and safest option is to modify the port forwards for Mailu Web frontend and have your own frontend point there.
|
||||
For instance, in the ``front`` section of Mailu ``docker-compose.yml``, use local ports 8080 and 8443 respectively for HTTP and HTTPS:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
front:
|
||||
# build: nginx
|
||||
image: mailu/nginx:$VERSION
|
||||
restart: always
|
||||
env_file: .env
|
||||
ports:
|
||||
- "127.0.0.1:8080:80"
|
||||
- "127.0.0.1:8443:443"
|
||||
...
|
||||
volumes:
|
||||
- "$ROOT/certs:/certs"
|
||||
|
||||
Then on your own frontend, point to these local ports. In practice, you only need to point to the HTTPS port
|
||||
(as the HTTP port simply redirects there). Here is an example Nginx configuration:
|
||||
|
||||
.. code-block:: nginx
|
||||
|
||||
server {
|
||||
listen 443;
|
||||
server_name mymailhost.tld;
|
||||
|
||||
# [...] here goes your standard configuration
|
||||
|
||||
location / {
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_pass https://localhost:8443;
|
||||
}
|
||||
}
|
||||
|
||||
.. code-block:: docker
|
||||
|
||||
#mailu.env file
|
||||
REAL_IP_HEADER=X-Real-IP
|
||||
REAL_IP_FROM=x.x.x.x,y.y.y.y.y
|
||||
#x.x.x.x,y.y.y.y.y is the static IP address your reverse proxy uses for connecting to Mailu.
|
||||
|
||||
Because the admin interface is served as ``/admin``, the RESTful API as ``/api``, the Webmail as ``/webmail``, the single sign on page as ``/sso``, webdav as ``/webdav``, the client-autoconfiguration and the static files endpoint as ``/static``, you may also want to use a single virtual host and serve other applications (still Nginx):
|
||||
|
||||
.. code-block:: nginx
|
||||
|
||||
server {
|
||||
# [...] here goes your standard configuration
|
||||
|
||||
location ~* ^/(admin|api|sso|static|webdav|webmail|(apple\.)?mobileconfig|(\.well\-known/autoconfig/)?mail/|Autodiscover/Autodiscover) {
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_pass https://localhost:8443;
|
||||
}
|
||||
|
||||
location /main_app {
|
||||
proxy_pass https://some-host;
|
||||
}
|
||||
|
||||
location /other_app {
|
||||
proxy_pass https://some-other-host;
|
||||
}
|
||||
|
||||
location /local_app {
|
||||
root /path/to/your/files;
|
||||
}
|
||||
|
||||
location / {
|
||||
return 301 $scheme://$host/main_app;
|
||||
}
|
||||
}
|
||||
|
||||
.. note:: Please don’t add a ``/`` at the end of the location pattern or all your redirects will fail with 404 because the ``/`` would be missing, and you would have to add it manually to move on
|
||||
|
||||
.. code-block:: docker
|
||||
|
||||
#mailu.env file
|
||||
REAL_IP_HEADER=X-Real-IP
|
||||
REAL_IP_FROM=x.x.x.x,y.y.y.y.y
|
||||
#x.x.x.x,y.y.y.y.y is the static IP address your reverse proxy uses for connecting to Mailu.
|
||||
|
||||
Finally, you might want to serve the admin interface on a separate virtual host but not expose the admin container
|
||||
directly (have your own HTTPS virtual hosts on top of Mailu, one public for the Webmail and one internal for administration for instance).
|
||||
|
||||
Here is an example configuration :
|
||||
|
||||
.. code-block:: nginx
|
||||
|
||||
server {
|
||||
listen <public_ip>:443;
|
||||
server_name external.example.com;
|
||||
# [...] here goes your standard configuration
|
||||
|
||||
location /webmail {
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_pass https://localhost:8443/webmail;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen <internal_ip>:443;
|
||||
server_name internal.example.com;
|
||||
# [...] here goes your standard configuration
|
||||
|
||||
location /admin {
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_pass https://localhost:8443/admin;
|
||||
proxy_set_header Host $http_host;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.. code-block:: docker
|
||||
|
||||
#mailu.env file
|
||||
REAL_IP_HEADER=X-Real-IP
|
||||
REAL_IP_FROM=x.x.x.x,y.y.y.y.y
|
||||
#x.x.x.x,y.y.y.y.y is the static IP address your reverse proxy uses for connecting to Mailu.
|
||||
|
||||
Depending on how you access the front server, you might want to add a ``proxy_redirect`` directive to your ``location`` blocks:
|
||||
|
||||
.. code-block:: nginx
|
||||
|
||||
proxy_redirect https://localhost https://example.com;
|
||||
|
||||
This will stop redirects (301 and 302) sent by the Webmail, nginx front and admin interface from sending you to ``localhost``.
|
||||
Web contents based on criteria like the requested hostname (virtual hosts).
|
||||
|
||||
.. _traefik_proxy:
|
||||
|
||||
Traefik as reverse proxy
|
||||
------------------------
|
||||
|
||||
`Traefik`_ is a popular reverse-proxy aimed at containerized systems.
|
||||
As such, many may wish to integrate Mailu into a system which already uses Traefik as its sole ingress/reverse-proxy.
|
||||
In your docker-compose.yml, add a section like follows:
|
||||
|
||||
As the ``mailu/front`` container uses Nginx not only for ``HTTP`` forwarding, but also for the mail-protocols like ``SMTP``, ``IMAP``, etc
|
||||
, we need to keep this container around even when using another ``HTTP`` reverse-proxy. Furthermore, Traefik is neither able to
|
||||
forward non-HTTP, nor can it easily forward HTTPS-to-HTTPS.
|
||||
.. code-block:: yaml
|
||||
|
||||
This, however, means 3 things:
|
||||
reverse-proxy:
|
||||
# The official v2 Traefik docker image
|
||||
image: traefik:v2.10
|
||||
# Enables the web UI and tells Traefik to listen to docker
|
||||
command:
|
||||
- "--providers.docker=true"
|
||||
- "--providers.docker.exposedbydefault=false"
|
||||
- "--providers.docker.allowEmptyServices=true"
|
||||
- "--entrypoints.web.address=:http"
|
||||
- "--entrypoints.websecure.address=:https"
|
||||
- "--entrypoints.smtp.address=:smtp"
|
||||
- "--entrypoints.submission.address=:submission"
|
||||
- "--entrypoints.submissions.address=:submissions"
|
||||
- "--entrypoints.imap.address=:imap"
|
||||
- "--entrypoints.imaps.address=:imaps"
|
||||
- "--entrypoints.pop3.address=:pop3"
|
||||
- "--entrypoints.pop3s.address=:pop3s"
|
||||
- "--entrypoints.sieve.address=:sieve"
|
||||
# - "--api.insecure=true"
|
||||
- "--log.level=DEBUG"
|
||||
ports:
|
||||
# The HTTP port
|
||||
- "25:25"
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
- "465:465"
|
||||
- "587:587"
|
||||
- "993:993"
|
||||
- "995:995"
|
||||
- "110:110"
|
||||
- "143:143"
|
||||
- "4190:4190"
|
||||
# The Web UI (enabled by --api.insecure=true)
|
||||
#- "8080:8080"
|
||||
volumes:
|
||||
# So that Traefik can listen to the Docker events
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
|
||||
- ``mailu/front`` needs to listen internally on ``HTTP`` rather than ``HTTPS``
|
||||
- ``mailu/front`` is not exposed to the outside world on ``HTTP``
|
||||
- ``mailu/front`` still needs ``SSL`` certificates (here, we assume ``letsencrypt``) for a well-behaved mail service
|
||||
|
||||
This makes the setup with Traefik a bit harder: Traefik saves its certificates in a proprietary *JSON* file, which is not readable
|
||||
by Nginx in the ``front``-container. To solve this, your ``acme.json`` needs to be exposed to the host or a ``docker-volume``.
|
||||
It will then be read by a script in another container, which will dump the certificates as ``PEM`` files, readable for
|
||||
Nginx. The ``front`` container will automatically reload Nginx whenever these certificates change.
|
||||
|
||||
To set this up, first set ``TLS_FLAVOR=mail`` in your ``.env``. This tells ``mailu/front`` not to try to request certificates using ``letsencrypt``,
|
||||
but to read provided certificates, and use them only for mail-protocols, not for ``HTTP``.
|
||||
Next, in your ``docker-compose.yml``, comment out the ``port`` lines of the ``front`` section for port ``…:80`` and ``…:443``.
|
||||
Add the respective Traefik labels for your domain/configuration, like
|
||||
and then add the following to the front section:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.port=80"
|
||||
- "traefik.frontend.rule=Host:$TRAEFIK_DOMAIN"
|
||||
|
||||
.. note:: Please don’t forget to add ``TRAEFIK_DOMAIN=[...]`` TO YOUR ``.env``
|
||||
# the second part is important to ensure Mailu can get certificates from letsencrypt
|
||||
- "traefik.http.routers.web.rule=Host(`mail.example.com`) || Path(`/.well-known/acme-challenge/`)"
|
||||
- "traefik.http.routers.web.entrypoints=web"
|
||||
- "traefik.http.services.web.loadbalancer.server.port=80"
|
||||
|
||||
If your Traefik is configured to automatically request certificates from *letsencrypt*, then you’ll have a certificate
|
||||
for ``mail.your.example.com`` now. However, ``mail.your.example.com`` might only be the location where you want the Mailu web-interfaces
|
||||
to live — your mail should be sent/received from ``your.example.com``, and this is the ``DOMAIN`` in your ``.env``?
|
||||
To support that use-case, Traefik can request ``SANs`` for your domain. The configuration for this will depend on your Traefik version.
|
||||
#other FQDNS can be added here:
|
||||
- "traefik.tcp.routers.websecure.rule=HostSNI(`mail.example.com`) || HostSNI(`autoconfig.example.com`) || HostSNI(`mta-sts.example.com`)"
|
||||
- "traefik.tcp.routers.websecure.entrypoints=websecure"
|
||||
- "traefik.tcp.routers.websecure.tls.passthrough=true"
|
||||
- "traefik.tcp.routers.websecure.service=websecure"
|
||||
- "traefik.tcp.services.websecure.loadbalancer.server.port=443"
|
||||
- "traefik.tcp.services.websecure.loadbalancer.proxyProtocol.version=2"
|
||||
|
||||
Mailu must also be configured with the information what header is used by the reverse proxy for passing the remote
|
||||
client IP. This is configured in mailu.env:
|
||||
- "traefik.tcp.routers.smtp.rule=HostSNI(`*`)"
|
||||
- "traefik.tcp.routers.smtp.entrypoints=smtp"
|
||||
- "traefik.tcp.routers.smtp.service=smtp"
|
||||
- "traefik.tcp.services.smtp.loadbalancer.server.port=25"
|
||||
- "traefik.tcp.services.smtp.loadbalancer.proxyProtocol.version=2"
|
||||
|
||||
- "traefik.tcp.routers.submission.rule=HostSNI(`*`)"
|
||||
- "traefik.tcp.routers.submission.entrypoints=submission"
|
||||
- "traefik.tcp.routers.submission.service=submission"
|
||||
- "traefik.tcp.services.submission.loadbalancer.server.port=587"
|
||||
- "traefik.tcp.services.submission.loadbalancer.proxyProtocol.version=2"
|
||||
|
||||
- "traefik.tcp.routers.submissions.rule=HostSNI(`*`)"
|
||||
- "traefik.tcp.routers.submissions.entrypoints=submissions"
|
||||
- "traefik.tcp.routers.submissions.service=submissions"
|
||||
- "traefik.tcp.services.submissions.loadbalancer.server.port=465"
|
||||
- "traefik.tcp.services.submissions.loadbalancer.proxyProtocol.version=2"
|
||||
|
||||
- "traefik.tcp.routers.imap.rule=HostSNI(`*`)"
|
||||
- "traefik.tcp.routers.imap.entrypoints=imap"
|
||||
- "traefik.tcp.routers.imap.service=imap"
|
||||
- "traefik.tcp.services.imap.loadbalancer.server.port=143"
|
||||
- "traefik.tcp.services.imap.loadbalancer.proxyProtocol.version=2"
|
||||
|
||||
- "traefik.tcp.routers.imaps.rule=HostSNI(`*`)"
|
||||
- "traefik.tcp.routers.imaps.entrypoints=imaps"
|
||||
- "traefik.tcp.routers.imaps.service=imaps"
|
||||
- "traefik.tcp.services.imaps.loadbalancer.server.port=993"
|
||||
- "traefik.tcp.services.imaps.loadbalancer.proxyProtocol.version=2"
|
||||
|
||||
- "traefik.tcp.routers.pop3.rule=HostSNI(`*`)"
|
||||
- "traefik.tcp.routers.pop3.entrypoints=pop3"
|
||||
- "traefik.tcp.routers.pop3.service=pop3"
|
||||
- "traefik.tcp.services.pop3.loadbalancer.server.port=110"
|
||||
- "traefik.tcp.services.pop3.loadbalancer.proxyProtocol.version=2"
|
||||
|
||||
- "traefik.tcp.routers.pop3s.rule=HostSNI(`*`)"
|
||||
- "traefik.tcp.routers.pop3s.entrypoints=pop3s"
|
||||
- "traefik.tcp.routers.pop3s.service=pop3s"
|
||||
- "traefik.tcp.services.pop3s.loadbalancer.server.port=995"
|
||||
- "traefik.tcp.services.pop3s.loadbalancer.proxyProtocol.version=2"
|
||||
|
||||
- "traefik.tcp.routers.sieve.rule=HostSNI(`*`)"
|
||||
- "traefik.tcp.routers.sieve.entrypoints=sieve"
|
||||
- "traefik.tcp.routers.sieve.service=sieve"
|
||||
- "traefik.tcp.services.sieve.loadbalancer.server.port=4190"
|
||||
- "traefik.tcp.services.sieve.loadbalancer.proxyProtocol.version=2"
|
||||
healthcheck:
|
||||
test: ['NONE']
|
||||
|
||||
in mailu.env:
|
||||
|
||||
.. code-block:: docker
|
||||
|
||||
#mailu.env file
|
||||
REAL_IP_HEADER=X-Real-Ip
|
||||
REAL_IP_FROM=x.x.x.x,y.y.y.y.y
|
||||
#x.x.x.x,y.y.y.y.y is the static IP address your reverse proxy uses for connecting to Mailu.
|
||||
|
||||
For more information see the :ref:`configuration reference <reverse_proxy_headers>` for more information.
|
||||
|
||||
Traefik 2.x using labels configuration
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Add the appropriate labels for your domain(s) to the ``front`` container in ``docker-compose.yml``.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
services:
|
||||
front:
|
||||
labels:
|
||||
# Enable TLS
|
||||
- "traefik.http.routers.mailu-secure.tls"
|
||||
# Your main domain
|
||||
- "traefik.http.routers.mailu-secure.tls.domains[0].main=your.example.com"
|
||||
# Optional SANs for your main domain
|
||||
- "traefik.http.routers.mailu-secure.tls.domains[0].sans=mail.your.example.com,webmail.your.example.com,smtp.your.example.com"
|
||||
# Optionally add other domains
|
||||
- "traefik.http.routers.mailu-secure.tls.domains[1].main=mail.other.example.com"
|
||||
- "traefik.http.routers.mailu-secure.tls.domains[1].sans=mail2.other.example.com,mail3.other.example.com"
|
||||
# Your ACME certificate resolver
|
||||
- "traefik.http.routers.mailu-secure.tls.certResolver=foo"
|
||||
|
||||
Of course, be sure to define the Certificate Resolver ``foo`` in the static configuration as well.
|
||||
|
||||
Alternatively, you can define SANs in the Traefik static configuration using routers, or in the static configuration using entrypoints.
|
||||
Refer to the Traefik documentation for more details.
|
||||
|
||||
.. _`Traefik`: https://traefik.io/
|
||||
|
||||
Override Mailu configuration
|
||||
----------------------------
|
||||
|
||||
If you do not have the resources for running a separate reverse proxy, you could override Mailu reverse proxy configuration by using :ref:`an override<override-label>`.
|
||||
Simply store your configuration file (Nginx format), in ``/mailu/overrides/nginx.conf``.
|
||||
All ``*.conf`` files will be included in the main server block of Mailu in nginx which listens on port 80/443.
|
||||
Add location blocks for any services that must be proxied.
|
||||
|
||||
You can also download the example configuration files:
|
||||
|
||||
- :download:`compose/traefik/docker-compose.yml`
|
||||
- :download:`compose/traefik/traefik.toml`
|
||||
|
||||
.. _have Mailu Web frontend listen locally and use your own Web frontend on top of it: #have-mailu-web-frontend-listen-locally
|
||||
.. _use Traefik in another container as central system-reverse-proxy: #traefik-as-reverse-proxy
|
||||
.. _override Mailu Web frontend configuration: #override-mailu-configuration
|
||||
REAL_IP_FROM=192.168.203.0/24
|
||||
PROXY_PROTOCOL=all-but-http
|
||||
TRAEFIK_VERSION=v2
|
||||
TLS_FLAVOR=mail-letsencrypt
|
||||
WEBROOT_REDIRECT=/sso/login
|
||||
|
||||
Using the above configuration, Traefik will proxy all the traffic related to Mailu's FQDNs without requiring dupplicate certificates.
|
||||
|
Reference in New Issue
Block a user