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.

• 7 min read
self-hostedmatrixdendritechatfederationprivacydocker
Self-Hosted Matrix Chat Server with Dendrite

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:

FeatureDendriteSynapse
LanguageGoPython
Memory Usage100-500MB500MB-2GB
MaturityBetaMature
Best ForHomelabs, small teamsLarge organizations
Setup ComplexityLowMedium
Resource EfficiencyExcellentModerate

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:

ConfigurationCPURAMStorageUsers
Minimum2 cores2GB10GB1-10
Recommended4 cores4GB50GB10-50
Raspberry Pi4 cores4-8GB32GB SSD1-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

ClientPlatformFeaturesBest For
Element XiOS, Android, DesktopFull-featured, modernDaily use
Element WebBrowser, DesktopComplete Matrix supportDesktop use
FluffyChatiOS, Android, WebLightweight, E2EEMobile
SchildiChatDesktopElement fork, better UIPower users
NhekoLinux, Windows, macOSNative Qt appLinux 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:

  1. Generate registration file: mautrix-$bridge -g
  2. Add registration to Dendrite config
  3. Configure bridge settings
  4. Restart both services

Known Working Bridges

BridgePlatformStatus
mautrix-telegramTelegram✅ Stable
mautrix-whatsappWhatsApp✅ Stable
mautrix-discordDiscord✅ Stable
mautrix-signalSignal⚠️ Beta
mautrix-facebookFacebook/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:

FilePurposeFrequency
matrix_key.pemSigning key (cannot be recovered!)Once
dendrite.yamlConfigurationAs needed
PostgreSQL DBAll messages, usersDaily
media_store/Uploaded filesDaily

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-known configuration
  • 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_size in 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:

OptionMonthly CostUsersStorage
Discord Nitro$101500MB
Slack Pro$8.75/userTeamLimited
Self-hosted Dendrite$5-15UnlimitedYour 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

ScenarioBetter Choice
Large community (500+ users)Synapse
Enterprise requirementsElement Server Suite
Zero maintenanceMatrix.org hosted
Raspberry Pi resource limitsConduit (Rust, minimal)

For most homelab use cases with family, friends, or small teams—Dendrite hits the sweet spot.

Next Steps

  1. Set up your domain with proper DNS and TLS certificates
  2. Deploy the Docker stack with the configuration above
  3. Register your first user: Element → Create Account → your homeserver
  4. Join Matrix rooms: Search for rooms like #home-server:matrix.org
  5. Add bridges for your other chat platforms

Your conversations now live on your infrastructure, under your control. Welcome to the federated future.

Anthony Lattanzio

Anthony Lattanzio

Tech Enthusiast & Builder

I'm a tech enthusiast who loves building things with hardware and software. By night, I run a homelab that's grown way beyond what any reasonable person needs. Check out about me for more.

Comments

Powered by GitHub Discussions