Mattermost HTTPS Configuration: TLS for the Team Messaging Server

Mattermost is a self-hosted team chat application that handles sensitive internal communications — incident response discussions, credentials shared in channels, private messages between team members. Running it without TLS means all of that travels in plaintext. This article covers enabling HTTPS on Mattermost using its built-in TLS support, or placing it behind nginx, setting the correct site URL, and configuring push notification proxies and mobile clients.

Mattermost deployment modes

Mattermost can handle TLS in two ways:

  1. Direct TLS — Mattermost’s Go web server serves HTTPS on port 443. Simple but limits TLS control.
  2. Reverse proxy — nginx, Apache, or Traefik terminates TLS and proxies to Mattermost on port 8065. More flexible and the recommended production approach.

Option A: Direct TLS in Mattermost

Step 1: Obtain a certificate

certbot certonly --standalone -d chat.example.com

Or for an internal CA:

openssl req -x509 -newkey rsa:4096 \
  -keyout /opt/mattermost/config/mattermost.key \
  -out /opt/mattermost/config/mattermost.crt \
  -days 365 -nodes \
  -subj "/CN=chat.example.com" \
  -addext "subjectAltName=DNS:chat.example.com"

Step 2: Configure Mattermost

Open /opt/mattermost/config/config.json. The relevant section is ServiceSettings:

{
  "ServiceSettings": {
    "SiteURL": "https://chat.example.com",
    "ListenAddress": ":443",
    "ConnectionSecurity": "TLS",
    "TLSCertFile": "/opt/mattermost/config/mattermost.crt",
    "TLSKeyFile": "/opt/mattermost/config/mattermost.key",
    "UseLetsEncrypt": false,
    "Forward80To443": true
  }
}

Setting Forward80To443: true causes Mattermost to also listen on port 80 and redirect to 443.

For Let’s Encrypt with Mattermost’s built-in ACME client:

{
  "ServiceSettings": {
    "SiteURL": "https://chat.example.com",
    "ListenAddress": ":443",
    "ConnectionSecurity": "TLS",
    "UseLetsEncrypt": true,
    "LetsEncryptCertificateCacheFile": "./config/letsencrypt.cache",
    "Forward80To443": true
  }
}

With UseLetsEncrypt: true, Mattermost requests and renews certificates automatically. Port 80 must be reachable from the internet.

Step 3: Allow Mattermost to bind to port 443

Mattermost runs as a non-root user. On Linux, non-root processes cannot bind to ports below 1024 by default:

sudo setcap cap_net_bind_service=+ep /opt/mattermost/bin/mattermost

Or use a different port (e.g., 8443) and let your firewall handle port forwarding:

sudo iptables -t nat -A PREROUTING -p tcp --dport 443 -j REDIRECT --to-port 8443

Step 4: Restart Mattermost

sudo systemctl restart mattermost
sudo systemctl status mattermost
sudo journalctl -u mattermost -n 30 --no-pager

Option B: nginx reverse proxy with TLS (recommended)

Step 1: Configure Mattermost to listen on localhost only

In config.json:

{
  "ServiceSettings": {
    "SiteURL": "https://chat.example.com",
    "ListenAddress": "127.0.0.1:8065",
    "ConnectionSecurity": "",
    "UseLetsEncrypt": false
  }
}

Note: ConnectionSecurity is empty (not “TLS”) because nginx handles TLS. Mattermost runs on plain HTTP to localhost.

Restart Mattermost.

Step 2: Configure nginx

Create /etc/nginx/sites-available/mattermost:

upstream mattermost {
    server 127.0.0.1:8065;
    keepalive 32;
}

server {
    listen 80;
    server_name chat.example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    http2 on;
    server_name chat.example.com;

    ssl_certificate     /etc/letsencrypt/live/chat.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/chat.example.com/privkey.pem;
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_ciphers         HIGH:!aNULL:!MD5;
    ssl_session_cache   shared:SSL:10m;
    ssl_session_timeout 1d;
    ssl_stapling        on;
    ssl_stapling_verify on;

    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
    add_header X-Frame-Options SAMEORIGIN always;
    add_header X-Content-Type-Options nosniff always;

    location ~ /api/v[0-9]+/(users/)?websocket$ {
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $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_pass http://mattermost;
    }

    location / {
        proxy_set_header Connection "";
        proxy_set_header Host $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_revalidate on;
        proxy_cache_min_uses 2;
        proxy_cache_use_stale timeout;
        proxy_cache_lock on;
        proxy_http_version 1.1;
        proxy_pass http://mattermost;
    }
}

Enable and reload:

sudo ln -s /etc/nginx/sites-available/mattermost /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

Configuring SiteURL — the most common mistake

SiteURL is the most important setting in Mattermost’s TLS configuration. It must be the exact HTTPS URL clients use to connect. It affects:

  • All links generated by Mattermost (email invitations, share links)
  • WebSocket connection URLs
  • Mobile app connection strings
  • CORS validation

In config.json:

"SiteURL": "https://chat.example.com"

No trailing slash. Must match the certificate’s CN or SAN.

Or set via the CLI:

sudo -u mattermost /opt/mattermost/bin/mmctl --local config set ServiceSettings.SiteURL https://chat.example.com

Or in the System Console: System Console → Environment → Web Server → Site URL.


Mobile clients and push notifications

After enabling HTTPS, mobile clients connect using the HTTPS SiteURL. The Mattermost iOS and Android apps automatically use HTTPS when the server URL starts with https://.

Push notifications for mobile require the Mattermost Push Notification Service (MPNS) or Mattermost’s cloud-hosted notification service. If you use the hosted HPNS (Mattermost’s servers), no additional TLS configuration is needed — Mattermost connects to it over HTTPS by default.

For self-hosted MPNS, configure TLS on the MPNS server the same way as the main Mattermost server, and update EmailSettings.PushNotificationServer in config.json:

"EmailSettings": {
  "PushNotificationServer": "https://your-mpns.example.com"
}

GitLab integration and OAuth over HTTPS

If you use Mattermost embedded in GitLab (or with GitLab OAuth), the GitLab callback URL must match the HTTPS SiteURL. After updating SiteURL, regenerate the OAuth application in GitLab:

  1. Go to GitLab → Admin → Applications → Mattermost.
  2. Update the Redirect URI to https://chat.example.com/signup/gitlab/complete.
  3. Save.
  4. Copy the new application ID and secret.
  5. Update config.jsonGitLabSettings.Id and GitLabSettings.Secret.

Certificate renewal

sudo nano /etc/letsencrypt/renewal-hooks/deploy/mattermost.sh
#!/bin/bash
# If using nginx reverse proxy (most common):
systemctl reload nginx

# If using Mattermost's built-in TLS (not needed if UseLetsEncrypt=true):
# cp /etc/letsencrypt/live/chat.example.com/fullchain.pem /opt/mattermost/config/mattermost.crt
# cp /etc/letsencrypt/live/chat.example.com/privkey.pem /opt/mattermost/config/mattermost.key
# chown mattermost:mattermost /opt/mattermost/config/mattermost.crt /opt/mattermost/config/mattermost.key
# systemctl restart mattermost
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/mattermost.sh

Verifying TLS

# Test HTTPS
curl -I https://chat.example.com

# Test WebSocket
curl -I -H "Upgrade: websocket" -H "Connection: Upgrade" \
  https://chat.example.com/api/v4/websocket

# Check certificate
openssl s_client -connect chat.example.com:443 -servername chat.example.com </dev/null 2>&1 \
  | openssl x509 -noout -dates -subject

Troubleshooting

ProblemCauseFix
Mobile app “Server URL is not configured”SiteURL has trailing slash or wrong protocolSet SiteURL to exact https:// URL without trailing slash
WebSocket disconnectsnginx proxy_read_timeout too lowSet proxy_read_timeout 600s in nginx
Email links use http://SiteURL set to http://Update SiteURL to https://
Push notifications not working after TLSMPNS URL not updatedUpdate PushNotificationServer to https://
“Secure connection failed” on mobileSelf-signed cert not trustedInstall CA on device or use a publicly trusted cert

Summary

Mattermost HTTPS is best configured with nginx as a TLS terminator. Set SiteURL to the HTTPS URL in config.json — this is the most impactful setting because it controls all internally generated links and WebSocket connections. The nginx configuration must handle WebSocket upgrades separately from regular HTTP proxying. Certificate renewal is handled by reloading nginx, which picks up new Let’s Encrypt certificates without restarting Mattermost.

Scroll to Top