Running Matrix Synapse: Self-Hosted Encrypted Messaging at Scale
Matrix is a decentralized, end-to-end encrypted communication protocol, and Synapse is its reference homeserver implementation. If you want to run your own messaging server that federates with the broader Matrix network — where your users can chat with anyone on matrix.org or any other homeserver — Synapse is the battle-tested choice.
This guide focuses on the operational details of running Synapse well: database optimization, worker architecture for scaling, federation tuning, and the practical realities of keeping a homeserver running smoothly.
Why Synapse Specifically?
There are several Matrix homeserver implementations:
| Server | Language | Maturity | Best for |
|---|---|---|---|
| Synapse | Python | Production-ready | Full compatibility, bridges, all features |
| Dendrite | Go | Beta | Lower resource usage, simpler deployments |
| Conduit | Rust | Experimental | Lightweight single-user/small setups |
Synapse is the implementation that supports every Matrix feature, every bridge, and every client. If something works on Matrix, it works on Synapse. The trade-off is resource consumption — Synapse uses more RAM and CPU than the alternatives, especially once you federate with large rooms.
Docker Deployment
# docker-compose.yml
services:
synapse:
image: matrixdotorg/synapse:latest
volumes:
- synapse_data:/data
environment:
SYNAPSE_SERVER_NAME: matrix.yourdomain.com
SYNAPSE_REPORT_STATS: "no"
ports:
- "8008:8008"
- "8448:8448"
depends_on:
- db
restart: unless-stopped
db:
image: postgres:16
volumes:
- synapse_db:/var/lib/postgresql/data
environment:
POSTGRES_DB: synapse
POSTGRES_USER: synapse
POSTGRES_PASSWORD: synapsepass
POSTGRES_INITDB_ARGS: "--encoding=UTF8 --lc-collate=C --lc-ctype=C"
restart: unless-stopped
volumes:
synapse_data:
synapse_db:
Generate the configuration
Before starting, generate the initial config:
docker run -it --rm \
-v synapse_data:/data \
-e SYNAPSE_SERVER_NAME=matrix.yourdomain.com \
-e SYNAPSE_REPORT_STATS=no \
matrixdotorg/synapse:latest generate
This creates homeserver.yaml in the data volume. You'll need to edit it for PostgreSQL.
Configure PostgreSQL
Edit homeserver.yaml (mount the volume and edit, or exec into the container):
database:
name: psycopg2
args:
user: synapse
password: synapsepass
database: synapse
host: db
cp_min: 5
cp_max: 10
Critical: Use PostgreSQL in production. Synapse's default SQLite database will grind to a halt with any real usage. This isn't optional — it's a hard requirement for anything beyond testing.
docker compose up -d
Create your first user
docker exec -it synapse-synapse-1 register_new_matrix_user \
http://localhost:8008 \
-c /data/homeserver.yaml \
-a # creates an admin user
Federation Setup
Federation allows your users to join rooms and message users on other Matrix homeservers. It requires:
- DNS SRV record or
.well-knowndelegation - Port 8448 accessible from the internet (or reverse proxy)
- Valid TLS certificate
Option A: .well-known delegation
Serve a JSON file at https://yourdomain.com/.well-known/matrix/server:
{
"m.server": "matrix.yourdomain.com:443"
}
Option B: DNS SRV record
_matrix._tcp.yourdomain.com. 3600 IN SRV 10 5 443 matrix.yourdomain.com.
Reverse proxy (Caddy)
matrix.yourdomain.com {
reverse_proxy /_matrix/* synapse:8008
reverse_proxy /_synapse/* synapse:8008
}
Test federation at https://federationtester.matrix.org/.
Performance Tuning
Synapse can be resource-hungry. Here's how to keep it manageable:
Memory optimization
In homeserver.yaml:
# Reduce cache factor (default 0.5, lower = less RAM)
caches:
global_factor: 0.3
# Limit federation connections
federation_sender:
per_destination_queue_size: 200
Database maintenance
PostgreSQL needs regular maintenance for Synapse:
-- Run weekly: clean up old state
VACUUM ANALYZE;
-- Check table sizes
SELECT relname, pg_size_pretty(pg_total_relation_size(relid))
FROM pg_catalog.pg_statio_user_tables
ORDER BY pg_total_relation_size(relid) DESC
LIMIT 10;
The state_groups_state table grows fastest. Synapse includes a state compressor tool:
# Install and run the state compressor
pip install synapse-auto-compressor
synapse_auto_compressor -p "host=db user=synapse password=synapsepass dbname=synapse"
Purge old history
For large rooms, purge old messages you no longer need:
curl -X POST "http://localhost:8008/_synapse/admin/v1/purge_history/!roomid:yourdomain.com" \
-H "Authorization: Bearer admin-access-token" \
-H 'Content-Type: application/json' \
-d '{"delete_local_events": true, "purge_up_to_ts": 1672531200000}'
Worker Architecture for Scaling
If your Synapse instance handles more than 50-100 concurrent users, you can split work across multiple worker processes:
# homeserver.yaml
worker_app: synapse.app.homeserver
worker_listeners:
- type: http
port: 8008
# Enable workers
federation_sender_instances:
- federation_sender1
media_instance_running_background_jobs: "media_worker1"
Key workers to split off first:
- Federation sender — Handles outbound federation traffic
- Media repository — Handles image/file uploads and thumbnails
- Sync workers — Handle client
/syncrequests (the heaviest endpoint)
For a homelab with under 20 users, you don't need workers. A single Synapse process with PostgreSQL is sufficient.
Bridges
Bridges connect Matrix to other chat platforms. Popular bridges:
| Bridge | Platform | Maintained by |
|---|---|---|
| mautrix-telegram | Telegram | mautrix |
| mautrix-signal | Signal | mautrix |
| mautrix-whatsapp | mautrix | |
| mautrix-discord | Discord | mautrix |
| mautrix-slack | Slack | mautrix |
| heisenbridge | IRC | hifi |
Setting up a bridge (example with mautrix-telegram):
# Add to docker-compose.yml
mautrix-telegram:
image: dock.mau.dev/mautrix/telegram:latest
volumes:
- ./mautrix-telegram:/data
depends_on:
- synapse
restart: unless-stopped
Generate the bridge config, then register it with Synapse by adding the registration file to homeserver.yaml:
app_service_config_files:
- /data/mautrix-telegram-registration.yaml
Bridges are Synapse's killer feature. You can consolidate messages from Telegram, Discord, Signal, and Slack into one Matrix client.
Monitoring
Monitor Synapse with Prometheus (it exposes metrics natively):
# homeserver.yaml
enable_metrics: true
metrics_flags:
known_servers: true
listeners:
- port: 9000
type: metrics
bind_addresses: ['0.0.0.0']
Import the Synapse Grafana dashboard for pre-built panels covering federation lag, request latency, active users, and database performance.
The Bottom Line
Running Synapse is a commitment. It's more resource-intensive than Dendrite or Conduit, and it needs PostgreSQL and regular maintenance to stay healthy. But it's the only Matrix homeserver that supports the full protocol, every bridge, and every client feature. If you want to run a serious Matrix deployment — especially one with bridges to other platforms — Synapse is the proven choice. Start with a single instance, PostgreSQL, and a reverse proxy. Add workers and bridges as your needs grow.