How to Self-Host Matrix + Element: Open Source Slack Alternative 2026
TL;DR
Matrix is an open, decentralized messaging protocol. Synapse (Apache 2.0, ~12K stars, Python) is the reference homeserver. Element (Apache 2.0) is the most popular Matrix client — web, iOS, Android, and desktop. Together they provide Slack-like team messaging with end-to-end encryption, bridges to external platforms (Slack, Discord, WhatsApp, Telegram), and federation across Matrix homeservers worldwide. Slack Pro costs $7.25/user/month. Self-hosted Matrix is free.
Key Takeaways
- Matrix: Open protocol — your server, your data, federated with matrix.org and others
- Synapse: Python-based reference server — feature-complete, higher RAM (~500MB–1GB)
- Conduit: Rust-based homeserver — lightweight (~50MB RAM), fewer features than Synapse
- Element: Best Matrix client — web app + iOS/Android + desktop
- Bridges: Connect Matrix rooms to Slack, Discord, WhatsApp, Telegram channels
- E2EE: End-to-end encryption in all 1:1 chats and rooms with cross-signing
Matrix vs Mattermost vs Slack
| Feature | Matrix + Synapse | Mattermost CE | Slack Pro |
|---|---|---|---|
| License | Apache 2.0 | MIT | Proprietary |
| Cost | Free (hosting) | Free (hosting) | $7.25/user/mo |
| Federation | Yes (Matrix protocol) | No | No |
| Bridges | 30+ (Slack, Discord, WA) | Limited | Yes (paid) |
| E2EE | Yes | Yes (beta) | No |
| Threads | Yes | Yes | Yes |
| Calls | Yes (Element Call) | Yes | Yes |
| RAM | ~500MB–1GB | ~500MB | — |
Option A: Synapse (Full-Featured)
Docker Setup
# docker-compose.yml
services:
synapse:
image: matrixdotorg/synapse:latest
container_name: synapse
restart: unless-stopped
ports:
- "8448:8448" # Federation
volumes:
- synapse_data:/data
environment:
SYNAPSE_CONFIG_PATH: /data/homeserver.yaml
depends_on:
- db
db:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_DB: synapse
POSTGRES_USER: synapse
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C"
volumes:
- synapse_db:/var/lib/postgresql/data
volumes:
synapse_data:
synapse_db:
Generate Initial Config
# Generate homeserver.yaml:
docker run --rm \
-v synapse_data:/data \
-e SYNAPSE_SERVER_NAME=matrix.yourdomain.com \
-e SYNAPSE_REPORT_STATS=no \
matrixdotorg/synapse:latest generate
homeserver.yaml (key settings)
server_name: "matrix.yourdomain.com"
public_baseurl: "https://matrix.yourdomain.com"
database:
name: psycopg2
args:
user: synapse
password: "${POSTGRES_PASSWORD}"
database: synapse
host: db
cp_min: 5
cp_max: 10
# Disable open registration:
enable_registration: false
# Allow registration with admin token:
registration_shared_secret: "generate-a-random-secret"
# Enable E2EE key backup:
enable_media_repo: true
listeners:
- port: 8008
tls: false
type: http
x_forwarded: true
resources:
- names: [client, federation]
compress: false
Part 2: Caddy Configuration
Matrix requires special routing for federation:
matrix.yourdomain.com {
# Client-Server API:
reverse_proxy /_matrix/* localhost:8008
reverse_proxy /_synapse/* localhost:8008
# Well-known delegation:
handle /.well-known/matrix/client {
header Content-Type application/json
header Access-Control-Allow-Origin *
respond `{"m.homeserver":{"base_url":"https://matrix.yourdomain.com"}}`
}
handle /.well-known/matrix/server {
header Content-Type application/json
respond `{"m.server":"matrix.yourdomain.com:443"}`
}
}
Option B: Conduit — Lightweight Rust Server
Conduit (Apache 2.0, Rust) is a Matrix homeserver for personal/small team use. ~50MB RAM, single binary.
services:
conduit:
image: conduitim/conduit:latest
container_name: conduit
restart: unless-stopped
ports:
- "6167:6167"
volumes:
- conduit_data:/var/lib/conduit
environment:
CONDUIT_SERVER_NAME: "matrix.yourdomain.com"
CONDUIT_DATABASE_PATH: "/var/lib/conduit"
CONDUIT_DATABASE_BACKEND: "rocksdb"
CONDUIT_PORT: "6167"
CONDUIT_MAX_REQUEST_SIZE: "20971520"
CONDUIT_ALLOW_REGISTRATION: "false"
CONDUIT_ALLOW_FEDERATION: "true"
CONDUIT_TRUSTED_SERVERS: '["matrix.org"]'
matrix.yourdomain.com {
reverse_proxy /_matrix/* localhost:6167
reverse_proxy /_conduit/* localhost:6167
handle /.well-known/matrix/client {
respond `{"m.homeserver":{"base_url":"https://matrix.yourdomain.com"}}`
}
}
Conduit tradeoffs: No bridges, no admin API, limited to ~100 users. Great for personal/family use. Use Synapse for team deployments with bridges.
Part 3: Create Admin Account
# With Synapse — create admin user:
docker exec synapse register_new_matrix_user \
-c /data/homeserver.yaml \
-u admin \
-p your-password \
-a \
http://localhost:8008
Part 4: Element Web Client
services:
element-web:
image: vectorim/element-web:latest
container_name: element-web
restart: unless-stopped
ports:
- "8080:80"
volumes:
- ./element-config.json:/app/config.json:ro
{
"default_server_config": {
"m.homeserver": {
"base_url": "https://matrix.yourdomain.com",
"server_name": "matrix.yourdomain.com"
}
},
"brand": "Matrix",
"default_theme": "dark",
"room_directory": {
"servers": ["matrix.yourdomain.com"]
}
}
chat.yourdomain.com {
reverse_proxy localhost:8080
}
Part 5: Bridges
Bridges connect Matrix rooms to external platforms. Users in the Matrix room see messages from Slack/Discord users and vice versa.
Discord Bridge
services:
mautrix-discord:
image: dock.mau.dev/mautrix/discord:latest
container_name: mautrix-discord
restart: unless-stopped
volumes:
- ./discord-bridge:/data
# Generate config:
docker run --rm -v ./discord-bridge:/data dock.mau.dev/mautrix/discord:latest
# Edit config.yaml:
# homeserver.address: https://matrix.yourdomain.com
# homeserver.domain: matrix.yourdomain.com
# appservice.id: discord
# Register bridge with Synapse (add to homeserver.yaml):
# app_service_config_files:
# - /data/discord-bridge/registration.yaml
Slack Bridge
services:
mautrix-slack:
image: dock.mau.dev/mautrix/slack:latest
volumes:
- ./slack-bridge:/data
Available bridges:
- mautrix-discord — Discord bridge
- mautrix-slack — Slack bridge
- mautrix-whatsapp — WhatsApp bridge
- mautrix-telegram — Telegram bridge
- mautrix-signal — Signal bridge
- mx-puppet-twitter — Twitter/X DMs
Part 6: Element Call (Video Calls)
Element Call provides WebRTC video calling without third-party infrastructure:
- In Element → Start a call in any room
- Or install Element Call standalone:
git clone https://github.com/element-hq/element-call - Requires a TURN server for calls outside your LAN:
services:
coturn:
image: coturn/coturn:latest
network_mode: host
command: >
-n --log-file=stdout
--realm=matrix.yourdomain.com
--static-auth-secret="${TURN_SECRET}"
In homeserver.yaml:
turn_uris:
- "turn:turn.matrix.yourdomain.com:3478?transport=udp"
- "turn:turn.matrix.yourdomain.com:3478?transport=tcp"
turn_shared_secret: "your-turn-secret"
turn_user_lifetime: 86400000
Maintenance
# Update Synapse:
docker compose pull
docker compose up -d
# Backup:
# PostgreSQL:
docker exec synapse-db-1 pg_dump -U synapse synapse | gzip > synapse-db-$(date +%Y%m%d).sql.gz
# Media files:
tar -czf synapse-media-$(date +%Y%m%d).tar.gz \
$(docker volume inspect synapse_synapse_data --format '{{.Mountpoint}}')/media_store
# Purge old room history (reduce database size):
# Admin API: POST /_synapse/admin/v1/purge_history/{roomId}
See all open source team communication tools at OSSAlt.com/alternatives/slack.