If your team relies on Slack but you’re tired of message history limits, per-seat pricing, and having your conversations live on someone else’s servers — Mattermost is the answer. It’s an open-source, self-hosted team communication platform that looks and feels like Slack, but runs entirely on your hardware.
What is Mattermost?
Mattermost is a collaboration platform built for teams that need full control over their communication data. It offers channels, direct messages, threads, file sharing, search, and a plugin ecosystem — all packaged in a modern web interface that’ll feel familiar if you’ve used Slack or Microsoft Teams.
Why Self-Host Your Team Chat?
- Full data ownership: Messages, files, and metadata stay on your server
- No message limits: Unlimited history, no paywall for scrolling back
- No per-seat costs: Add as many users as your hardware supports
- Compliance ready: Meet data residency requirements without enterprise contracts
- Extensible: Webhooks, slash commands, plugins, and a REST API
- Offline capable: Works on your LAN even if the internet goes down
Mattermost Editions
Mattermost offers three editions:
| Edition | Price | Best For |
|---|---|---|
| Team Edition | Free/Open Source | Small teams, homelab |
| Professional | $10/user/month | Mid-size teams needing SSO/LDAP |
| Enterprise | Custom pricing | Large orgs with compliance needs |
This guide uses the free Team Edition — it covers everything most self-hosters need.
Prerequisites
Before you begin, make sure you have:
- A Linux server (Ubuntu 22.04+ or Debian 12+ recommended)
- Docker and Docker Compose installed
- At least 2GB RAM and 10GB disk space
- A domain name (optional but recommended for HTTPS)
- Basic familiarity with the terminal
Step 1: Create the Project Directory
mkdir -p ~/mattermost/{config,data,logs,plugins,client-plugins,bleve-indexes}
cd ~/mattermost
Mattermost stores quite a bit of data across these directories:
config/— Server configurationdata/— Uploaded files and attachmentslogs/— Application logsplugins/— Server-side pluginsclient-plugins/— Client-side pluginsbleve-indexes/— Full-text search indexes
Step 2: Set Up Docker Compose
Create your docker-compose.yml:
services:
mattermost:
image: mattermost/mattermost-team-edition:latest
container_name: mattermost
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
environment:
- TZ=UTC
- MM_SQLSETTINGS_DRIVERNAME=postgres
- MM_SQLSETTINGS_DATASOURCE=postgres://mmuser:${POSTGRES_PASSWORD}@postgres:5432/mattermost?sslmode=disable&connect_timeout=10
- MM_BLEVESETTINGS_INDEXDIR=/mattermost/bleve-indexes
- MM_SERVICESETTINGS_SITEURL=${SITE_URL:-http://localhost:8065}
ports:
- "8065:8065"
volumes:
- ./config:/mattermost/config:rw
- ./data:/mattermost/data:rw
- ./logs:/mattermost/logs:rw
- ./plugins:/mattermost/plugins:rw
- ./client-plugins:/mattermost/client/plugins:rw
- ./bleve-indexes:/mattermost/bleve-indexes:rw
security_opt:
- no-new-privileges:true
postgres:
image: postgres:16-alpine
container_name: mattermost-db
restart: unless-stopped
environment:
- POSTGRES_USER=mmuser
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_DB=mattermost
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD", "pg_isready", "-U", "mmuser", "-d", "mattermost"]
interval: 10s
timeout: 5s
retries: 5
volumes:
postgres_data:
Step 3: Configure Environment Variables
Create an .env file with your secrets:
# Generate a strong password
POSTGRES_PASSWORD=$(openssl rand -base64 32)
echo "POSTGRES_PASSWORD=$POSTGRES_PASSWORD" > .env
echo "SITE_URL=https://chat.yourdomain.com" >> .env
Your .env file should look like:
POSTGRES_PASSWORD=your_generated_password_here
SITE_URL=https://chat.yourdomain.com
Important: Set
SITE_URLto your actual domain. Mattermost uses this for email links, OAuth redirects, and WebSocket connections. If you’re running locally, usehttp://localhost:8065.
Step 4: Start Mattermost
docker compose up -d
Watch the logs to make sure everything starts cleanly:
docker compose logs -f mattermost
You should see messages about database migrations running (first launch only) and then:
Visit http://your-server-ip:8065 in your browser. You’ll see the Mattermost setup wizard.
Step 5: Initial Setup
The first user you create becomes the System Administrator. Choose wisely:
- Enter your email, username, and password
- Create your first team (e.g., “Home” or your org name)
- You’ll land in the Town Square channel — this is your default public channel
Essential Admin Settings
Navigate to System Console (click the grid icon → System Console) and configure:
Site Configuration → Notifications:
Authentication → Signup:
Site Configuration → Customization:
Reverse Proxy with Nginx
For production use, you’ll want HTTPS. Here’s an Nginx config:
server {
listen 80;
server_name chat.yourdomain.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name chat.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/chat.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/chat.yourdomain.com/privkey.pem;
location ~ /api/v[0-9]+/(users/)?websocket$ {
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
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_buffers 256 16k;
proxy_buffer_size 16k;
client_max_body_size 50M;
proxy_pass http://localhost:8065;
}
location / {
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;
client_max_body_size 50M;
proxy_pass http://localhost:8065;
}
}
The key detail here is the WebSocket proxy block — Mattermost uses WebSockets for real-time messaging, and without proper upgrade headers, you’ll get a degraded experience with polling fallback.
If you’re using Caddy instead:
Caddy handles WebSocket upgrades automatically — no extra config needed.
Enabling Plugins
Mattermost has a plugin marketplace built in. Some highlights:
- Jira — Link Jira issues to channels
- GitHub — Get PR notifications and slash commands
- Zoom/Jitsi — One-click video calls from any channel
- Playbooks — Incident response runbooks
- Boards — Kanban boards (Trello-like) built into Mattermost
Enable the marketplace in System Console → Plugin Management:
Then browse and install plugins directly from the UI.
Mobile and Desktop Apps
Mattermost offers native apps for all platforms:
- Desktop: Windows, macOS, Linux — download here
- Mobile: iOS and Android — available in app stores
- Web: Works great in any modern browser
Point any client at your SITE_URL and log in. Push notifications require configuring the Hosted Push Notification Service (HPNS) or running your own push proxy.
Backups
Back up both the database and file storage:
#!/bin/bash
# backup-mattermost.sh
BACKUP_DIR="/backups/mattermost/$(date +%Y%m%d)"
mkdir -p "$BACKUP_DIR"
# Database dump
docker exec mattermost-db pg_dump -U mmuser mattermost | gzip > "$BACKUP_DIR/db.sql.gz"
# File storage
tar czf "$BACKUP_DIR/data.tar.gz" -C ~/mattermost data/
# Config
cp ~/mattermost/config/config.json "$BACKUP_DIR/config.json"
echo "Backup complete: $BACKUP_DIR"
To restore:
# Restore database
gunzip -c db.sql.gz | docker exec -i mattermost-db psql -U mmuser mattermost
# Restore files
tar xzf data.tar.gz -C ~/mattermost/
# Restart
docker compose restart mattermost
Troubleshooting
“WebSocket connection failed”
This almost always means your reverse proxy isn’t forwarding WebSocket upgrade headers. Check the Nginx config above — you need the Upgrade and Connection headers in the websocket location block.
Database connection errors on startup
Make sure PostgreSQL is healthy before Mattermost starts. The depends_on with condition: service_healthy in the Compose file handles this, but if you see issues:
docker compose logs postgres
docker exec mattermost-db pg_isready -U mmuser
High memory usage
Mattermost can be memory-hungry with many users. Tune these in System Console → Environment → Performance:
- Maximum Users Per Team: Set a reasonable limit
- Maximum Channels Per Team: Default 2000 is fine for most
- Reduce Web Server Read/Write Timeout if connections are hanging
File upload failures
Check that the data/ directory has correct permissions:
sudo chown -R 2000:2000 ~/mattermost/data
Mattermost runs as UID 2000 inside the container.
Search not working
If full-text search returns no results, rebuild the Bleve indexes:
Go to System Console → Experimental → Bleve and click “Purge and rebuild indexes.”
Mattermost vs Slack vs Rocket.Chat
| Feature | Mattermost | Slack | Rocket.Chat |
|---|---|---|---|
| Self-hosted | ✅ | ❌ | ✅ |
| Free tier message limit | Unlimited | 90 days | Unlimited |
| File storage | Your disk | 5GB free | Your disk |
| Threads | ✅ | ✅ | ✅ |
| Plugin ecosystem | Good | Excellent | Good |
| Mobile apps | ✅ | ✅ | ✅ |
| SSO/LDAP (free) | ❌ (Pro+) | ❌ (Pro+) | ✅ |
| Resource usage | Moderate | N/A | Higher |
| Ease of setup | Easy | N/A | Moderate |
Mattermost wins on stability and enterprise polish — it feels like a product, not a project. Rocket.Chat offers more features in the free tier (like LDAP), but Mattermost’s UX is noticeably smoother.
Conclusion
Mattermost gives you a polished, Slack-like team chat experience without handing your conversations to a third party. The Docker setup takes about 10 minutes, the admin console is intuitive, and the mobile apps actually work well. For small teams and homelabs, the free Team Edition is more than enough — you get unlimited users, unlimited history, and full API access. If you outgrow it, the upgrade path to Professional or Enterprise is straightforward without needing to migrate data.
Your team’s conversations belong on your server. Mattermost makes that easy.