Mattermost, Inc.

Alternative reverse proxy - Traefik v2

Hi there,
as it is hard to find documentation on how to run Matermost behind Traefik v2, I share my configuration here, hoping to make the life of other sys-admins less stressful.
I still have some question. It would be great if they will be answered.

Traefik

Static configuration
global:
  checkNewVersion: true
entryPoints:
  web:
    address: :80
  websecure:
    address: :443

api:
   debug: true

providers:
  docker:
    exposedByDefault: false
    network: bridge_proxy_traefikv2

log:
  level: DEBUG
  filePath: /var/log/traefik-log.log

accessLog:
  filePath: /var/log/traefik-access.log

certificatesResolvers:
  tlsChallenge_letsencrypt:
    acme:
      email: my.secret@gmail.com
      storage: /etc/ssl/certs/letsencrypt/acme.json
      tlsChallenge: {}

Mattermost

docker-compose file
version: "3"

services:

  db:
    build: db
    container_name: mattermost_db
    read_only: true
    restart: unless-stopped
    volumes:
      - ./volumes/db/var/lib/postgresql/data:/var/lib/postgresql/data
      - /etc/localtime:/etc/localtime:ro
    environment:
      - POSTGRES_USER=mmuser
      - POSTGRES_PASSWORD=mmuser_password
      - POSTGRES_DB=mattermost
    # uncomment the following to enable backup
    #  - AWS_ACCESS_KEY_ID=XXXX
    #  - AWS_SECRET_ACCESS_KEY=XXXX
    #  - WALE_S3_PREFIX=s3://BUCKET_NAME/PATH
    #  - AWS_REGION=us-east-1
    
    networks:
      - mattermost

  app:
    build: app    
      # change `build:app` to `build:` and uncomment following lines for team edition or change UID/GID
      # context: app
      # args:
      #   - edition=team
      #   - PUID=1000
      #   - PGID=1000
    container_name: mattermost_app
    restart: unless-stopped
    
    volumes:
      - ./volumes/app/mattermost/config:/mattermost/config:rw
      - ./volumes/app/mattermost/data:/mattermost/data:rw
      - ./volumes/app/mattermost/logs:/mattermost/logs:rw
      - ./volumes/app/mattermost/plugins:/mattermost/plugins:rw
      - ./volumes/app/mattermost/client-plugins:/mattermost/client/plugins:rw
      - /etc/localtime:/etc/localtime:ro
    
    environment:
      # set same as db credentials and dbname
      - MM_USERNAME=mmuser
      - MM_PASSWORD=mmuser_password
      - MM_DBNAME=mattermost

      # use the credentials you've set above, in the format:
      # MM_SQLSETTINGS_DATASOURCE=postgres://${MM_USERNAME}:${MM_PASSWORD}@db:5432/${MM_DBNAME}?sslmode=disable&connect_timeout=10
      - MM_SQLSETTINGS_DATASOURCE=postgres://mmuser:mmuser_password@db:5432/mattermost?sslmode=disable&connect_timeout=10

      # in case your config is not in default location
      #- MM_CONFIG=/mattermost/config/config.json
      
    networks:
      - mattermost

  web:
    build: web
    container_name: mattermost_web
    read_only: true
    restart: unless-stopped
    
    ports:
      #- "80:80"
      #- "443:443"
      - "80"      # http
      #- "443"     # https
      - "40080:80"
      - "40443:443"
        
    volumes:
      # This directory must have cert files if you want to enable SSL
      - ./volumes/web/cert:/cert:ro
      - /etc/localtime:/etc/localtime:ro
    # Uncomment for SSL
    # environment:
    #  - MATTERMOST_ENABLE_SSL=true
    
    labels:
      - traefik.enable=true
      - traefik.docker.network=bridge_proxy_traefikv2
      
           
      #---HTTP ROUTER SECTION
      - traefik.http.routers.mattermost.rule=Host(`chat.fairbnb.community`)
            
         #---HTTP SECTION
      -  traefik.http.routers.mattermost.entrypoints=web
      -  traefik.http.routers.mattermost.middlewares=mattermost_redirect2https      
      
      #---HTTPS ROUTER SECTION
      - traefik.http.routers.mattermost_secure.rule=Host(`chat.fairbnb.community`)
           
         #---HTTPS SECTION
      -  traefik.http.routers.mattermost_secure.entrypoints=websecure      
      
         #---TLS SECTION
      -  traefik.http.routers.mattermost_secure.tls.certresolver=tlsChallenge_letsencrypt
      
      #---MIDDLEWARE SECTION redirect http to https
      - traefik.http.middlewares.mattermost_redirect2https.redirectscheme.scheme=https

      
   # - "traefik.http.services.mattermost.LoadBalancer.server.port=8000"
   #   - "traefik.enable=true"
   #   - "traefik.docker.network=public-web"
   #   - "traefik.http.routers.mattermost_http.entryPoints=web"
   #   - "traefik.http.routers.mattermost_http.rule=Host(`mattermost.google.com`)"
   #   - "traefik.http.routers.mattermost_https.entryPoints=websecure"
   #   - "traefik.http.routers.mattermost_https.rule=Host(`mattermost.google.com`)"
   #   - "traefik.http.routers.mattermost_https.tls.certresolver=basic"
   #   - "traefik.http.middlewares.https_redirect.redirectscheme.scheme=https"
    
    networks:
      - traefik
      - mattermost
      
networks:
  traefik:
    external:
      name: bridge_proxy_traefikv2    
  mattermost:
    external:
      name: bridge_mattermost

For additional settings see:

If you only activate TLS in Traefik, it sends a minimum header as shown in the response of whoami

whoami response
Hostname: 25a.....
IP: 127.0.0.1
IP: 172.20.0.5
RemoteAddr: 172.20.0.3:38662
GET / HTTP/1.1
Host: whoami.xxx.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:72.0) Gecko/20100101 Firefox/72.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: en-GB,en;q=0.5
Cookie: ajs_user_id=%22wbzw8rfz57dz7gf41mn9z35n4r%22; ajs_group_id=null; ajs_anonymous_id=%2200000000000000000000000000%22
Dnt: 1
Te: trailers
Upgrade-Insecure-Requests: 1
X-Forwarded-For: 185.001.002.003
X-Forwarded-Host: whoami.xxx.com
X-Forwarded-Port: 443
X-Forwarded-Proto: https
X-Forwarded-Server: 3eac5....
X-Real-Ip: 185.001.002.003

but there are more option propagated in

Appache

recommended configuration
  # Set web sockets
  RewriteEngine On
  RewriteCond %{REQUEST_URI} /api/v[0-9]+/(users/)?websocket [NC,OR]
  RewriteCond %{HTTP:UPGRADE} ^WebSocket$ [NC,OR]
  RewriteCond %{HTTP:CONNECTION} ^Upgrade$ [NC]
  RewriteRule .* ws://127.0.0.1:8065%{REQUEST_URI} [P,QSA,L]

  <Location />
        Require all granted
        ProxyPass http://127.0.0.1:8065/
        ProxyPassReverse http://127.0.0.1:8065/
        ProxyPassReverseCookieDomain 127.0.0.1 mysubdomain.mydomain.com
  </Location>

NGINX

recommended configuration
server {
   listen 80;
   server_name    mattermost.example.com;

   location ~ /api/v[0-9]+/(users/)?websocket$ {
       proxy_set_header Upgrade $http_upgrade;
       proxy_set_header Connection "upgrade";
       client_max_body_size 50M;
       proxy_set_header Host $http_host;
       proxy_set_header X-Real-IP $remote_addr;
       proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
       proxy_set_header X-Forwarded-Proto $scheme;
       proxy_set_header X-Frame-Options SAMEORIGIN;
       proxy_buffers 256 16k;
       proxy_buffer_size 16k;
       client_body_timeout 60;
       send_timeout 300;
       lingering_timeout 5;
       proxy_connect_timeout 90;
       proxy_send_timeout 300;
       proxy_read_timeout 90s;
       proxy_pass http://backend;
   }

   location / {
       client_max_body_size 50M;
       proxy_set_header Connection "";
       proxy_set_header Host $http_host;
       proxy_set_header X-Real-IP $remote_addr;
       proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
       proxy_set_header X-Forwarded-Proto $scheme;
       proxy_set_header X-Frame-Options SAMEORIGIN;
       proxy_buffers 256 16k;
       proxy_buffer_size 16k;
       proxy_read_timeout 600s;
       proxy_cache mattermost_cache;
       proxy_cache_revalidate on;
       proxy_cache_min_uses 2;
       proxy_cache_use_stale timeout;
       proxy_cache_lock on;
       proxy_http_version 1.1;
       proxy_pass http://backend;
   }
}

so what about the following settings. Are the absolutely neccessary or only optional?

  • proxy_set_header X-Frame-Options SAMEORIGIN;
  • proxy_pass http://backend;
  • location ~ /api/v[0-9]+/(users/)?websocket$
  • chache settings

if that is necessary it can properly solved like

- "traefik.http.middlewares.cloud.headers.customFrameOptionsValue=SAMEORIGIN"

it is copied from liberty-server/nextcloud at master · bedawi/liberty-server.

I haven’t tested the impact yet as documentation is quite limited in
customFrameOptionsValue