← All articles
COMMUNICATION Running Matrix Synapse: Self-Hosted Encrypted Messag... 2026-02-09 · matrix · synapse · encrypted-messaging

Running Matrix Synapse: Self-Hosted Encrypted Messaging at Scale

Communication 2026-02-09 matrix synapse encrypted-messaging federation homeserver

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.

CLIENTS Element Web/Desktop Element Mobile Other clients Client-Server API Synapse Matrix homeserver E2EE, rooms, users Federation & bridges PostgreSQL Required for production Other Homeservers matrix.org, others Federation API Bridges Slack, Discord, Signal Telegram, IRC, WhatsApp Application Service API Media Store Images, files, avatars

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:

  1. DNS SRV record or .well-known delegation
  2. Port 8448 accessible from the internet (or reverse proxy)
  3. 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:

  1. Federation sender — Handles outbound federation traffic
  2. Media repository — Handles image/file uploads and thumbnails
  3. Sync workers — Handle client /sync requests (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 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.