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:
- Direct TLS — Mattermost’s Go web server serves HTTPS on port 443. Simple but limits TLS control.
- 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:
- Go to GitLab → Admin → Applications → Mattermost.
- Update the Redirect URI to
https://chat.example.com/signup/gitlab/complete. - Save.
- Copy the new application ID and secret.
- Update
config.json→GitLabSettings.IdandGitLabSettings.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
| Problem | Cause | Fix |
|---|---|---|
| Mobile app “Server URL is not configured” | SiteURL has trailing slash or wrong protocol | Set SiteURL to exact https:// URL without trailing slash |
| WebSocket disconnects | nginx proxy_read_timeout too low | Set proxy_read_timeout 600s in nginx |
| Email links use http:// | SiteURL set to http:// | Update SiteURL to https:// |
| Push notifications not working after TLS | MPNS URL not updated | Update PushNotificationServer to https:// |
| “Secure connection failed” on mobile | Self-signed cert not trusted | Install 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.