Self-Hosted Matrix Chat Server with Dendrite
Replace Discord and Slack with your own private, federated chat server. A complete guide to deploying Matrix Dendrite on your homelab with bridges, clients, and federation.
Table of Contents
- Why Matrix? Why Dendrite?
- The Centralization Problem
- Dendrite vs Synapse
- Hardware Requirements
- Docker Compose Setup
- Directory Structure
- docker-compose.yml
- Generating Configuration Files
- Caddyfile for Auto HTTPS
- Environment File (.env)
- Federation Configuration
- DNS Setup
- Testing Federation
- Connecting Clients
- Element Web (Self-Hosted)
- Client Recommendations
- Adding Bridges (Discord, Telegram, WhatsApp)
- Mautrix Bridge Template
- Known Working Bridges
- Security Hardening
- Reverse Proxy Security
- Rate Limiting
- Private Federation
- Backups and Maintenance
- What to Back Up
- Backup Script
- Update Process
- Troubleshooting
- Common Issues
- Logs and Debugging
- Performance Tuning
- Database Optimization
- Dendrite Configuration Tuning
- Cost Analysis
- When to Choose Alternatives
- Next Steps
Self-Hosted Matrix Chat Server with Dendrite
Discord knows every message you send. Slack archives your work conversations forever. Signal requires phone numbers. What if you could own your chat infrastructure entirely—messages, metadata, and all?
Matrix is an open standard for decentralized communication. Run your own server, own your data, and still chat with anyone on the federated network. Dendrite makes this practical for homelab setups.
In this guide, we’ll deploy a complete Matrix Dendrite homeserver with Docker, configure federation, and connect bridges to Discord, Telegram, and WhatsApp.
Why Matrix? Why Dendrite?
The Centralization Problem
Every mainstream chat platform has the same issues:
- No data ownership: Your messages live on someone else’s servers
- Privacy tradeoffs: Metadata, analytics, advertising
- Vendor lock-in: Can’t migrate conversations between platforms
- Single points of failure: When Discord goes down, everyone goes down
Matrix solves this through federation—anyone can run a homeserver that communicates with the rest of the network.
Dendrite vs Synapse
Matrix has two main homeserver implementations:
| Feature | Dendrite | Synapse |
|---|---|---|
| Language | Go | Python |
| Memory Usage | 100-500MB | 500MB-2GB |
| Maturity | Beta | Mature |
| Best For | Homelabs, small teams | Large organizations |
| Setup Complexity | Low | Medium |
| Resource Efficiency | Excellent | Moderate |
Choose Dendrite for:
- Personal/family servers (1-50 users)
- Low-power hardware (Raspberry Pi, mini PC)
- Simpler deployment and maintenance
Choose Synapse for:
- Large communities (100+ users)
- Production environments requiring 99.9% uptime
- Need for every Matrix feature
Hardware Requirements
Dendrite’s efficiency means it runs well on modest hardware:
| Configuration | CPU | RAM | Storage | Users |
|---|---|---|---|---|
| Minimum | 2 cores | 2GB | 10GB | 1-10 |
| Recommended | 4 cores | 4GB | 50GB | 10-50 |
| Raspberry Pi | 4 cores | 4-8GB | 32GB SSD | 1-20 |
Storage grows with media files and message history. For media-heavy servers, plan for 100GB+.
Docker Compose Setup
The easiest deployment uses Docker Compose. Here’s a complete stack:
Directory Structure
matrix/
├── docker-compose.yml
├── config/
│ ├── dendrite.yaml
│ └── matrix_key.pem
├── data/
│ └── (persistent storage)
└── caddy/
└── Caddyfile
docker-compose.yml
version: '3.8'
services:
dendrite:
image: matrixdotorg/dendrite-monolith:latest
container_name: dendrite
restart: unless-stopped
depends_on:
- postgres
volumes:
- ./config:/etc/dendrite
- ./data:/var/dendrite
ports:
- "8008:8008"
- "8448:8448"
environment:
- dendrite_server_name=${SERVER_NAME}
- dendrite_bind_address=0.0.0.0:8008
networks:
- matrix
postgres:
image: postgres:16-alpine
container_name: dendrite-postgres
restart: unless-stopped
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
- POSTGRES_USER=dendrite
- POSTGRES_PASSWORD=${DB_PASSWORD}
- POSTGRES_DB=dendrite
networks:
- matrix
caddy:
image: caddy:latest
container_name: dendrite-caddy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./caddy/Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
networks:
- matrix
volumes:
postgres_data:
caddy_data:
networks:
matrix:
Generating Configuration Files
First, create the signing key (do this once):
mkdir -p config data
# Generate Matrix signing key
docker run --rm --entrypoint="/usr/bin/generate-keys" \
-v $(pwd)/config:/mnt \
matrixdotorg/dendrite-monolith:latest \
-private-key /mnt/matrix_key.pem
Generate the configuration file:
# Replace with your domain
SERVER_NAME="matrix.yourdomain.com"
DB_PASSWORD="your-secure-password"
docker run --rm --entrypoint="/bin/sh" \
-v $(pwd)/config:/mnt \
matrixdotorg/dendrite-monolith:latest \
-c "/usr/bin/generate-config \
-dir /var/dendrite/ \
-db postgres://dendrite:${DB_PASSWORD}@postgres/dendrite?sslmode=disable \
-server ${SERVER_NAME} > /mnt/dendrite.yaml"
Caddyfile for Auto HTTPS
matrix.yourdomain.com {
reverse_proxy /_matrix/* dendrite:8008
reverse_proxy /_synapse/* dendrite:8008
tls {
dns cloudflare {env.CF_API_TOKEN}
}
}
# Federation endpoint (port 8448)
:8448 {
reverse_proxy dendrite:8448
}
The /_matrix/* and /_synapse/* paths are required for Matrix clients. Port 8448 handles federation traffic from other homeservers.
Environment File (.env)
SERVER_NAME=matrix.yourdomain.com
DB_PASSWORD=your-secure-password-here
CF_API_TOKEN=your-cloudflare-api-token
Federation Configuration
Federation is what makes Matrix powerful—your server communicates with servers worldwide.
DNS Setup
You need DNS configured for federation:
Option 1: SRV Record (Recommended)
_matrix._tcp.yourdomain.com. IN SRV 10 5 8448 matrix.yourdomain.com.
This tells other servers to find your Matrix server at matrix.yourdomain.com:8448.
Option 2: .well-known File
Create a file at https://yourdomain.com/.well-known/matrix/server:
{"m.server": "matrix.yourdomain.com:8448"}
This is simpler if you’re using a reverse proxy like Caddy or Traefik.
Testing Federation
# Federation tester
curl "https://matrix.yourdomain.com/_matrix/federation/v1/version"
# Or use the official tester
# https://federationtester.matrix.org/
You should see a response like:
{"server":{"name":"Dendrite","version":"0.14.0"}}
Connecting Clients
Matrix has excellent client support. The primary client is Element, but alternatives exist.
Element Web (Self-Hosted)
Add Element Web to your compose stack:
element:
image: vectorim/element-web:latest
container_name: element-web
restart: unless-stopped
ports:
- "8009:80"
volumes:
- ./element-config.json:/app/config.json
networks:
- matrix
element-config.json:
{
"default_server_config": {
"m.homeserver": {
"base_url": "https://matrix.yourdomain.com",
"server_name": "matrix.yourdomain.com"
}
},
"default_country_code": "US",
"brand": "Your Brand",
"integrations_ui_url": "",
"integrations_rest_url": "",
"show_labs_settings": true,
"features": {
"feature_thread": true,
"feature_spaces": true
}
}
Client Recommendations
| Client | Platform | Features | Best For |
|---|---|---|---|
| Element X | iOS, Android, Desktop | Full-featured, modern | Daily use |
| Element Web | Browser, Desktop | Complete Matrix support | Desktop use |
| FluffyChat | iOS, Android, Web | Lightweight, E2EE | Mobile |
| SchildiChat | Desktop | Element fork, better UI | Power users |
| Nheko | Linux, Windows, macOS | Native Qt app | Linux users |
Adding Bridges (Discord, Telegram, WhatsApp)
Bridges connect Matrix to other chat platforms. When you send a message in Matrix, it appears in Discord, Telegram, etc.
Mautrix Bridge Template
The mautrix bridges work well with Dendrite:
# Add to docker-compose.yml
mautrix-telegram:
image: dock.mau.dev/tulir/mautrix-telegram:latest
container_name: mautrix-telegram
restart: unless-stopped
volumes:
- ./mautrix-telegram:/data
networks:
- matrix
For each bridge:
- Generate registration file:
mautrix-$bridge -g - Add registration to Dendrite config
- Configure bridge settings
- Restart both services
Known Working Bridges
| Bridge | Platform | Status |
|---|---|---|
| mautrix-telegram | Telegram | ✅ Stable |
| mautrix-whatsapp | ✅ Stable | |
| mautrix-discord | Discord | ✅ Stable |
| mautrix-signal | Signal | ⚠️ Beta |
| mautrix-facebook | Facebook/Messenger | ⚠️ Experimental |
To use a bridge, start a chat with the bridge bot and follow authentication instructions:
# Telegram bridge
@telegrambot:yourdomain.com
# Discord bridge
@discordbot:yourdomain.com
# WhatsApp bridge
@whatsappbot:yourdomain.com
Security Hardening
Reverse Proxy Security
Caddy handles most security automatically, but add these headers:
header {
Strict-Transport-Security "max-age=31536000; include-subdomains; preload"
X-Content-Type-Options "nosniff"
X-Frame-Options "DENY"
X-XSS-Protection "1; mode=block"
}
Rate Limiting
Add rate limiting to Dendrite’s config:
# In dendrite.yaml
rate_limiting:
enabled: true
threshold: 20 # requests
cooloff_ms: 500 # cooldown between bursts
Private Federation
To disable federation entirely (private server):
# In dendrite.yaml
global:
disable_federation: true
Your server becomes a private chat server with no external connections.
Backups and Maintenance
What to Back Up
Critical files that must be preserved:
| File | Purpose | Frequency |
|---|---|---|
matrix_key.pem | Signing key (cannot be recovered!) | Once |
dendrite.yaml | Configuration | As needed |
| PostgreSQL DB | All messages, users | Daily |
media_store/ | Uploaded files | Daily |
Backup Script
#!/bin/bash
BACKUP_DIR="/backup/matrix"
DATE=$(date +%Y%m%d)
# Database backup
docker exec dendrite-postgres pg_dump -U dendrite dendrite \
> "${BACKUP_DIR}/db_${DATE}.sql"
# Key backup (critical!)
cp config/matrix_key.pem "${BACKUP_DIR}/key_${DATE}.pem"
# Compress and rotate
tar -czf "${BACKUP_DIR}/backup_${DATE}.tar.gz" \
"${BACKUP_DIR}/db_${DATE}.sql"
rm "${BACKUP_DIR}/db_${DATE}.sql"
# Keep last 30 days
find "${BACKUP_DIR}" -name "backup_*.tar.gz" -mtime +30 -delete
Update Process
# Pull latest image
docker compose pull
# Stop and recreate
docker compose up -d
# Check logs
docker compose logs -f dendrite
Troubleshooting
Common Issues
Federation not working:
- Verify DNS SRV or
.well-knownconfiguration - Check that port 8448 is accessible from outside
- Test with
https://federationtester.matrix.org/
Cannot join large rooms:
- Dendrite may struggle with massive rooms (10K+ members)
- Either use Synapse for large servers or wait for room optimization
Bridge not connecting:
- Ensure registration file is properly linked in
dendrite.yaml - Check bridge logs:
docker logs mautrix-telegram
High memory usage:
- Dendrite caches messages in memory for performance
- Adjust
cache_sizein config if needed
Logs and Debugging
# Dendrite logs
docker compose logs -f dendrite
# Postgres logs
docker compose logs -f postgres
# Check federation
curl -v https://matrix.yourdomain.com/_matrix/federation/v1/version
Performance Tuning
Database Optimization
For PostgreSQL:
-- Faster joins
ALTER SYSTEM SET max_connections = 200;
ALTER SYSTEM SET shared_buffers = '256MB';
-- Restart to apply
SELECT pg_reload_conf();
Dendrite Configuration Tuning
# dendrite.yaml optimizations
# Connection pooling (monolith mode)
database:
max_open_conns: 100
max_idle_conns: 10
conn_max_lifetime: 30m
# Media limits
media:
max_file_size_bytes: 104857600 # 100MB
thumbnail_sizes:
- width: 32
height: 32
- width: 96
height: 96
- width: 320
height: 240
Cost Analysis
Running Dendrite vs paid alternatives:
| Option | Monthly Cost | Users | Storage |
|---|---|---|---|
| Discord Nitro | $10 | 1 | 500MB |
| Slack Pro | $8.75/user | Team | Limited |
| Self-hosted Dendrite | $5-15 | Unlimited | Your storage |
Self-hosting economics:
A $5/month VPS or existing homelab hardware can host Matrix for your entire family or small team with no per-user fees. Storage scales with your needs, not someone else’s pricing tier.
When to Choose Alternatives
| Scenario | Better Choice |
|---|---|
| Large community (500+ users) | Synapse |
| Enterprise requirements | Element Server Suite |
| Zero maintenance | Matrix.org hosted |
| Raspberry Pi resource limits | Conduit (Rust, minimal) |
For most homelab use cases with family, friends, or small teams—Dendrite hits the sweet spot.
Next Steps
- Set up your domain with proper DNS and TLS certificates
- Deploy the Docker stack with the configuration above
- Register your first user: Element → Create Account → your homeserver
- Join Matrix rooms: Search for rooms like
#home-server:matrix.org - Add bridges for your other chat platforms
Your conversations now live on your infrastructure, under your control. Welcome to the federated future.
Comments
Powered by GitHub Discussions