Communication advanced ⏱ 60 minutes

Matrix Synapse: Decentralized Chat for Your Homelab

Self-hosted Matrix server with federation. End-to-end encryption and bridges to other platforms.

By homelabbing.io • 2025-12-17
#matrix#synapse#chat#federation#encryption

Introduction

In an era of centralized messaging platforms controlling your data and conversations, Matrix offers a compelling alternative: a decentralized, federated communication protocol that puts you in control. Matrix Synapse is the reference implementation of the Matrix homeserver, enabling you to run your own private chat infrastructure while maintaining the ability to communicate with users on other Matrix servers worldwide.

What you’ll learn:

  • Deploy Matrix Synapse homeserver with Docker Compose
  • Configure Element web and desktop clients
  • Set up federation to communicate across Matrix servers
  • Bridge Discord, Telegram, and IRC channels
  • Implement SSO with Authentik or Authelia
  • Optimize performance for production use
  • Troubleshoot common federation and performance issues

Prerequisites:

  • Docker and Docker Compose installed (v24.0+ and v2.20+)
  • Reverse proxy with HTTPS (Caddy, Nginx, or Traefik)
  • Domain name with DNS control (federation requires SRV records)
  • 4+ GB RAM, 20+ GB storage (for moderate usage)
  • PostgreSQL knowledge (recommended over SQLite)
  • Basic understanding of federation concepts

Why Matrix Synapse?

The Decentralization Revolution

Traditional messaging platforms like Slack, Discord, and WhatsApp operate centralized servers that:

  • Control your data and conversations
  • Can shut down or change terms at any time
  • Require trust in third-party providers
  • Cannot communicate across platforms

Matrix solves this with decentralized federation:

  • You own your data on your server
  • Communicate with users on any Matrix server
  • No single point of failure or control
  • Open protocol anyone can implement

Key Features at a Glance

FeatureDetails
FederationConnect with users on other Matrix servers worldwide
End-to-End EncryptionOlm/Megolm encryption for private conversations
BridgesConnect Discord, Telegram, Slack, IRC, WhatsApp, Signal
ClientsElement (web/desktop/mobile), FluffyChat, SchildiChat, Nheko
Voice/VideoBuilt-in VoIP with Jitsi integration
BotsCreate bots with Python, JavaScript, or Go SDKs
SearchFull-text message search (requires PostgreSQL)
File SharingUpload images, videos, documents with configurable limits

Matrix vs. Alternatives

FeatureMatrix SynapseMattermostRocket.Chat
FederationNative supportNoneLimited
BridgesExcellent (10+ platforms)LimitedModerate
Resource UsageModerate-HighLow-ModerateModerate
E2E EncryptionNativeNonePaid feature
Mobile AppsExcellentExcellentGood
Setup ComplexityAdvancedBeginnerIntermediate

Understanding Matrix Architecture

Core Components

Internet → Reverse Proxy → Matrix Synapse → PostgreSQL Database
                ↓                ↓
           Element Web      Federation (Port 8448)
                ↓
           User Browsers

Matrix Synapse: Homeserver handling message routing, federation, and storage PostgreSQL: Database storing messages, users, and room state Element: Official Matrix client (web, desktop, mobile) Reverse Proxy: Handles HTTPS and routes traffic to Synapse

Federation Explained

Federation allows your Matrix server to communicate with other Matrix servers:

Your Homeserver          Other Homeserver
(matrix.example.com)     (matrix.org)
     ↓                        ↓
User @alice:example.com ←→ @bob:matrix.org

How it works:

  1. Alice sends message to Bob on matrix.org
  2. Your homeserver encrypts and forwards to matrix.org
  3. matrix.org delivers to Bob
  4. Messages sync in real-time across servers

Requirements:

  • Valid SSL certificate
  • Port 8448 open (or delegation via .well-known)
  • Proper DNS SRV records
  • Server signing keys

Room Structure

Matrix rooms are distributed across all participating servers:

Room: #dev-team:example.com
├── Participants:
│   ├── @alice:example.com (your server)
│   ├── @bob:example.com (your server)
│   └── @charlie:matrix.org (remote server)
├── Room State:
│   ├── Replicated on both servers
│   ├── Eventually consistent
│   └── Survives server outages

Installation: Docker Compose Method

Step 1: Prepare Directory Structure

mkdir -p /opt/matrix-synapse/{data,postgres_data,element}
cd /opt/matrix-synapse

Step 2: Generate Synapse Configuration

Generate initial config file with your domain:

docker run -it --rm     -v ./data:/data     -e SYNAPSE_SERVER_NAME=example.com     -e SYNAPSE_REPORT_STATS=no     matrixdotorg/synapse:latest generate

Important parameters:

  • SYNAPSE_SERVER_NAME: Your Matrix domain (e.g., example.com)
  • This generates /data/homeserver.yaml and signing keys

Step 3: Create docker-compose.yml

version: '3.8'

services:
  postgres:
    image: postgres:16-alpine
    container_name: matrix-postgres
    restart: always
    environment:
      POSTGRES_DB: synapse
      POSTGRES_USER: synapse
      POSTGRES_PASSWORD: CHANGE_THIS_SECURE_PASSWORD
      POSTGRES_INITDB_ARGS: --encoding=UTF8 --locale=C
    volumes:
      - ./postgres_data:/var/lib/postgresql/data
    networks:
      - matrix
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U synapse"]
      interval: 30s
      timeout: 10s
      retries: 3

  synapse:
    image: matrixdotorg/synapse:latest
    container_name: matrix-synapse
    restart: always
    depends_on:
      postgres:
        condition: service_healthy
    environment:
      - SYNAPSE_CONFIG_PATH=/data/homeserver.yaml
      - UID=1000
      - GID=1000
    volumes:
      - ./data:/data
    ports:
      - "8008:8008"  # Client API
      - "8448:8448"  # Federation API
    networks:
      - matrix
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8008/health"]
      interval: 30s
      timeout: 10s
      retries: 3

  element:
    image: vectorim/element-web:latest
    container_name: matrix-element
    restart: always
    volumes:
      - ./element/config.json:/app/config.json:ro
    ports:
      - "8080:80"
    networks:
      - matrix

networks:
  matrix:
    driver: bridge

Step 4: Configure PostgreSQL Database

Edit data/homeserver.yaml to use PostgreSQL instead of SQLite:

# Database configuration
database:
  name: psycopg2
  args:
    user: synapse
    password: CHANGE_THIS_SECURE_PASSWORD
    database: synapse
    host: postgres
    port: 5432
    cp_min: 5
    cp_max: 10

Why PostgreSQL?

  • SQLite doesn’t support full-text search
  • PostgreSQL handles concurrent connections better
  • Required for production deployments
  • Enables message search functionality

Step 5: Configure Synapse Settings

Edit key sections of data/homeserver.yaml:

# Server Configuration
server_name: "example.com"
public_baseurl: "https://matrix.example.com"

# Registration
enable_registration: false
enable_registration_without_verification: false
registration_shared_secret: "GENERATE_RANDOM_STRING_HERE"

# Federation
federation_domain_whitelist: []  # Empty = allow all servers
# federation_domain_whitelist:
#   - matrix.org
#   - element.io

# Media Storage
max_upload_size: "50M"
media_retention:
  local_media_lifetime: 90d
  remote_media_lifetime: 14d

# URL Previews
url_preview_enabled: true
url_preview_ip_range_blacklist:
  - '127.0.0.0/8'
  - '10.0.0.0/8'
  - '172.16.0.0/12'
  - '192.168.0.0/16'

# Turn Server (for VoIP)
turn_uris:
  - "turn:turn.example.com:3478?transport=udp"
  - "turn:turn.example.com:3478?transport=tcp"
turn_shared_secret: "GENERATE_RANDOM_STRING_HERE"
turn_user_lifetime: 86400000

# Rate Limiting
rc_message:
  per_second: 0.2
  burst_count: 10
rc_registration:
  per_second: 0.17
  burst_count: 3
rc_login:
  address:
    per_second: 0.17
    burst_count: 3
  account:
    per_second: 0.17
    burst_count: 3
  failed_attempts:
    per_second: 0.17
    burst_count: 3

# Performance
event_cache_size: 10K
caches:
  global_factor: 1.0
  per_cache_factors:
    get_users_in_room: 2.0

Generate random secrets:

# Generate registration secret
cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1

# Generate TURN secret
cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1

Step 6: Configure Element Client

Create element/config.json:

{
  "default_server_config": {
    "m.homeserver": {
      "base_url": "https://matrix.example.com",
      "server_name": "example.com"
    },
    "m.identity_server": {
      "base_url": "https://vector.im"
    }
  },
  "brand": "Element",
  "integrations_ui_url": "https://scalar.vector.im/",
  "integrations_rest_url": "https://scalar.vector.im/api",
  "integrations_widgets_urls": [
    "https://scalar.vector.im/_matrix/integrations/v1",
    "https://scalar.vector.im/api",
    "https://scalar-staging.vector.im/_matrix/integrations/v1",
    "https://scalar-staging.vector.im/api",
    "https://scalar-staging.riot.im/scalar/api"
  ],
  "bug_report_endpoint_url": "https://element.io/bugreports/submit",
  "defaultCountryCode": "US",
  "showLabsSettings": true,
  "features": {
    "feature_new_room_decoration_ui": true,
    "feature_pinning": true,
    "feature_custom_status": true,
    "feature_custom_tags": true,
    "feature_state_counters": true
  },
  "default_federate": true,
  "default_theme": "dark",
  "roomDirectory": {
    "servers": [
      "example.com",
      "matrix.org"
    ]
  },
  "enable_presence_by_hs_url": {
    "https://matrix.example.com": false
  },
  "settingDefaults": {
    "breadcrumbs": true
  }
}

Step 7: Configure DNS and Federation

A Record (Required):

matrix.example.com    A    YOUR_SERVER_IP

SRV Records (Required for Federation):

_matrix._tcp.example.com    SRV    10 0 8448 matrix.example.com
_matrix-fed._tcp.example.com SRV   10 0 8448 matrix.example.com

Alternative: .well-known Delegation

If you cannot create SRV records, use .well-known delegation on your main domain:

Create https://example.com/.well-known/matrix/server:

{
  "m.server": "matrix.example.com:8448"
}

Create https://example.com/.well-known/matrix/client:

{
  "m.homeserver": {
    "base_url": "https://matrix.example.com"
  }
}

Serve these with proper CORS headers:

Access-Control-Allow-Origin: *
Content-Type: application/json

Step 8: Configure Reverse Proxy

Caddy Configuration (Recommended):

matrix.example.com {
    # Client API
    reverse_proxy /_matrix/* synapse:8008
    reverse_proxy /_synapse/* synapse:8008

    # Element Web
    reverse_proxy /* element:80

    # Security headers
    header {
        X-Content-Type-Options "nosniff"
        X-Frame-Options "SAMEORIGIN"
        Referrer-Policy "no-referrer-when-downgrade"
        Strict-Transport-Security "max-age=31536000;"
    }
}

# Federation endpoint (port 8448)
matrix.example.com:8448 {
    reverse_proxy synapse:8448
}

Nginx Configuration:

server {
    server_name matrix.example.com;

    # Client API
    location /_matrix {
        proxy_pass http://localhost:8008;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Host $host;
        client_max_body_size 50M;
    }

    location /_synapse {
        proxy_pass http://localhost:8008;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Host $host;
    }

    # Element Web
    location / {
        proxy_pass http://localhost:8080;
        proxy_set_header Host $host;
    }

    listen 443 ssl http2;
    ssl_certificate /etc/letsencrypt/live/matrix.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/matrix.example.com/privkey.pem;
}

# Federation server
server {
    server_name matrix.example.com;
    listen 8448 ssl http2;

    ssl_certificate /etc/letsencrypt/live/matrix.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/matrix.example.com/privkey.pem;

    location / {
        proxy_pass http://localhost:8448;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Host $host;
    }
}

Step 9: Launch Services

# Start all services
docker compose up -d

# Monitor logs
docker compose logs -f synapse

# Check health
docker compose ps

Wait for Synapse to complete startup (first launch takes 30-60 seconds).

Step 10: Create Admin User

# Register admin user
docker exec -it matrix-synapse register_new_matrix_user     -c /data/homeserver.yaml     http://localhost:8008

# Follow prompts:
# - Username: admin
# - Password: [secure password]
# - Make admin? yes

Step 11: Verify Federation

# Test federation connectivity
curl -X GET https://matrix.example.com/_matrix/federation/v1/version

# Should return:
# {"server":{"name":"Synapse","version":"1.119.0"}}

# Use Matrix Federation Tester
# Visit: https://federationtester.matrix.org
# Enter: example.com

Common federation issues:

  • Port 8448 not accessible from internet
  • DNS SRV records incorrect or not propagated
  • SSL certificate doesn’t cover matrix.example.com
  • Firewall blocking outbound federation traffic

Setting Up Bridges

Bridges connect Matrix to other platforms, allowing you to use one client for all messaging.

Discord Bridge

Use case: Access Discord servers from Matrix client

Installation:

# Add to docker-compose.yml
  discord-bridge:
    image: halfshot/matrix-appservice-discord:latest
    container_name: matrix-discord-bridge
    restart: always
    volumes:
      - ./discord-bridge:/data
    networks:
      - matrix

Configuration:

# Generate config
docker run --rm -v ./discord-bridge:/data halfshot/matrix-appservice-discord:latest     /usr/local/bin/node /build/tools/generateConfig.js     -p 9005     -u "http://discord-bridge:9005"

# Edit discord-bridge/config.yaml:
# - Add your Discord bot token
# - Set bridge.domain to your homeserver domain
# - Configure database (SQLite or PostgreSQL)

Register with Synapse:

Add to data/homeserver.yaml:

app_service_config_files:
  - /data/discord-registration.yaml

Copy registration file:

cp discord-bridge/discord-registration.yaml data/
docker compose restart synapse

Bridge Discord Server:

  1. Invite Discord bot to your Discord server
  2. In Matrix, create room and invite Discord bridge bot
  3. Send: !discord bridge YOUR_DISCORD_SERVER_ID DISCORD_CHANNEL_ID
  4. Messages now sync bidirectionally

Telegram Bridge (mautrix-telegram)

Use case: Access Telegram chats from Matrix

# Add to docker-compose.yml
  telegram-bridge:
    image: dock.mau.dev/mautrix/telegram:latest
    container_name: matrix-telegram-bridge
    restart: always
    volumes:
      - ./telegram-bridge:/data
    networks:
      - matrix

Configuration:

# Generate config
docker run --rm -v ./telegram-bridge:/data dock.mau.dev/mautrix/telegram:latest

# Edit telegram-bridge/config.yaml:
homeserver:
    address: http://synapse:8008
    domain: example.com

appservice:
    address: http://telegram-bridge:29317
    port: 29317
    database: postgresql://synapse:password@postgres/telegram_bridge

telegram:
    api_id: YOUR_API_ID  # Get from https://my.telegram.org
    api_hash: YOUR_API_HASH

Register with Synapse:

cp telegram-bridge/registration.yaml data/
# Add to homeserver.yaml app_service_config_files
docker compose restart synapse

Bridge Telegram Chats:

  1. Start chat with bridge bot on Matrix
  2. Send: login
  3. Follow phone number verification
  4. Your Telegram chats appear in Matrix

IRC Bridge

Use case: Connect to Libera.Chat, OFTC, or other IRC networks

  irc-bridge:
    image: matrixdotorg/matrix-appservice-irc:latest
    container_name: matrix-irc-bridge
    restart: always
    volumes:
      - ./irc-bridge:/data
    networks:
      - matrix

Configuration for Libera.Chat:

# irc-bridge/config.yaml
homeserver:
  url: http://synapse:8008
  domain: example.com

ircService:
  servers:
    irc.libera.chat:
      port: 6697
      ssl: true
      additionalAddresses:
        - "irc.libera.chat"
      botConfig:
        enabled: true
        nick: "MatrixBot"
      dynamicChannels:
        enabled: true
        createAlias: true
        published: true
        joinRule: public
        federate: true
      matrixClients:
        userTemplate: "@irc_$NICK"

Usage:

Join IRC channel from Matrix:

/join #matrix:libera.chat

SSO Integration with Authentik

Single Sign-On centralizes authentication across your homelab services.

Prerequisites

  • Authentik installed and configured
  • Admin access to both Synapse and Authentik

Step 1: Configure Authentik Provider

  1. Log into Authentik admin panel

  2. Navigate to Applications → Providers

  3. Click “Create” → Select “OAuth2/OpenID Provider”

  4. Configure:

    • Name: Matrix Synapse
    • Client Type: Confidential
    • Redirect URIs: https://matrix.example.com/_synapse/client/oidc/callback
    • Signing Key: Select your signing certificate
  5. Save and note the Client ID and Client Secret

Step 2: Create Authentik Application

  1. Applications → Applications → Create

  2. Configure:

    • Name: Matrix
    • Slug: matrix
    • Provider: Select the provider created above
  3. Assign to users or groups who should have access

Step 3: Configure Synapse OIDC

Edit data/homeserver.yaml:

oidc_providers:
  - idp_id: authentik
    idp_name: "Authentik"
    issuer: "https://auth.example.com/application/o/matrix/"
    client_id: "YOUR_CLIENT_ID"
    client_secret: "YOUR_CLIENT_SECRET"
    scopes:
      - "openid"
      - "profile"
      - "email"
    user_mapping_provider:
      config:
        localpart_template: "{{ user.preferred_username }}"
        display_name_template: "{{ user.name }}"
        email_template: "{{ user.email }}"
    allow_existing_users: true
    enable_registration: true

Restart Synapse:

docker compose restart synapse

Step 4: Test SSO Login

  1. Navigate to Element web client
  2. Click “Sign In”
  3. Select “Continue with Authentik”
  4. Authenticate via Authentik
  5. You should be logged into Matrix

Advantages:

  • Centralized user management
  • MFA enforcement from Authentik
  • Easy onboarding/offboarding
  • Consistent authentication across services

Performance Optimization

Database Tuning

PostgreSQL Configuration:

Create postgres_data/postgresql.conf:

# Memory settings (adjust for your RAM)
shared_buffers = 1GB
effective_cache_size = 3GB
maintenance_work_mem = 256MB
work_mem = 16MB

# Checkpoint settings
checkpoint_completion_target = 0.9
wal_buffers = 16MB
min_wal_size = 1GB
max_wal_size = 4GB

# Query planner
random_page_cost = 1.1
effective_io_concurrency = 200

# Logging
log_min_duration_statement = 1000  # Log slow queries

Database Maintenance:

# Run weekly vacuum
docker exec -it matrix-postgres psql -U synapse -c "VACUUM ANALYZE;"

# Reindex for performance
docker exec -it matrix-postgres psql -U synapse -c "REINDEX DATABASE synapse;"

Synapse Caching

Increase cache sizes in homeserver.yaml:

caches:
  global_factor: 2.0
  per_cache_factors:
    get_users_in_room: 5.0
    get_current_state_ids: 5.0

event_cache_size: 50K

Resource requirements:

  • global_factor: 1.0 = ~500MB RAM
  • global_factor: 2.0 = ~1GB RAM
  • global_factor: 4.0 = ~2GB RAM

Media Cleanup

Old media accumulates quickly. Implement automated cleanup:

#!/bin/bash
# media-cleanup.sh

# Delete local media older than 90 days
docker exec matrix-synapse     curl -X POST     -H "Authorization: Bearer YOUR_ADMIN_ACCESS_TOKEN"     http://localhost:8008/_synapse/admin/v1/media/example.com/delete?before_ts=$(($(date +%s) - 7776000))000

# Delete remote media older than 14 days
docker exec matrix-synapse     curl -X POST     -H "Authorization: Bearer YOUR_ADMIN_ACCESS_TOKEN"     http://localhost:8008/_synapse/admin/v1/media/example.com/delete?before_ts=$(($(date +%s) - 1209600))000&keep_profiles=true

Get admin access token:

# Login as admin and extract token
curl -X POST -d '{"type":"m.login.password", "user":"admin", "password":"yourpassword"}'     https://matrix.example.com/_matrix/client/r0/login

Schedule monthly:

# Crontab
0 2 1 * * /opt/matrix-synapse/media-cleanup.sh

Room State Compression

Large rooms accumulate state. Enable state compression:

# homeserver.yaml
retention:
  enabled: true
  default_policy:
    min_lifetime: 1d
    max_lifetime: 365d

# Enable room auto-join cleanup
auto_join_rooms:
  enabled: false

Monitoring & Troubleshooting

Health Checks

# Check Synapse health
curl https://matrix.example.com/_matrix/client/versions

# Check federation
curl https://matrix.example.com:8448/_matrix/federation/v1/version

# Database connection count
docker exec matrix-postgres psql -U synapse -c     "SELECT count(*) FROM pg_stat_activity WHERE datname='synapse';"

Common Issues

Issue 1: Federation Not Working

Symptoms: Cannot join rooms on other servers, federation tester fails

Solutions:

# Verify port 8448 is open
nc -zv matrix.example.com 8448

# Check DNS SRV records
dig SRV _matrix._tcp.example.com

# Test .well-known delegation
curl https://example.com/.well-known/matrix/server

# Check signing keys
curl https://matrix.example.com/_matrix/key/v2/server

Issue 2: High Database Usage

Symptoms: Database size grows rapidly, slow queries

Solutions:

# Find largest tables
docker exec matrix-postgres psql -U synapse -c     "SELECT relname, pg_size_pretty(pg_total_relation_size(relid))      FROM pg_stat_user_tables      ORDER BY pg_total_relation_size(relid) DESC LIMIT 10;"

# Common culprits:
# - event_json (messages)
# - state_events (room state)
# - event_search (full-text search index)

# Purge old events from room
# Use Synapse Admin API or Synapse-Admin web UI

Issue 3: Out of Memory

Symptoms: Container restarts, OOM errors in logs

Solutions:

# Reduce cache sizes in homeserver.yaml
caches:
  global_factor: 0.5  # Reduce from 1.0

# Add Docker memory limits
services:
  synapse:
    deploy:
      resources:
        limits:
          memory: 4G
        reservations:
          memory: 2G

Issue 4: Slow Message Delivery

Symptoms: Messages delayed, federation lag

Solutions:

# Check event processing queue
docker exec matrix-synapse     curl http://localhost:8008/_synapse/admin/v1/event_reports

# Increase worker processes (advanced)
# See Synapse worker documentation for scaling

# Verify network latency to federated servers
mtr matrix.org

Logging Configuration

Enable debug logging temporarily:

# homeserver.yaml
loggers:
  synapse:
    level: DEBUG
  synapse.federation:
    level: DEBUG

Restart and monitor:

docker compose restart synapse
docker compose logs -f synapse | grep -i error

Advanced: Worker Configuration

For high-traffic deployments, split Synapse into multiple workers.

Benefits:

  • Horizontal scaling across CPU cores
  • Separate client API from federation traffic
  • Improved responsiveness under load

Example worker configuration:

# Add to docker-compose.yml
  synapse-generic-worker:
    image: matrixdotorg/synapse:latest
    container_name: synapse-worker-1
    restart: always
    environment:
      SYNAPSE_WORKER: synapse.app.generic_worker
    volumes:
      - ./data:/data
      - ./workers/worker1.yaml:/worker.yaml
    networks:
      - matrix

Create workers/worker1.yaml:

worker_app: synapse.app.generic_worker
worker_name: worker1

worker_listeners:
  - type: http
    port: 8081
    resources:
      - names: [client, federation]

worker_log_config: /data/log.config

Update reverse proxy to load balance across workers.


Security Hardening

Rate Limiting

Already configured in homeserver.yaml, but adjust per your needs:

rc_message:
  per_second: 0.2  # 1 message per 5 seconds
  burst_count: 10  # Allow bursts of 10

rc_registration:
  per_second: 0.17
  burst_count: 3

rc_login:
  address:
    per_second: 0.17
    burst_count: 3

Admin API Security

Protect admin endpoints:

# Require access token for admin API
admin_contact: 'mailto:[email protected]'

# Restrict registration
enable_registration: false
registration_shared_secret: "RANDOM_SECRET"  # For controlled registration

Content Security

# Prevent abuse
max_upload_size: "50M"

# URL preview restrictions
url_preview_enabled: true
url_preview_ip_range_blacklist:
  - '127.0.0.0/8'
  - '10.0.0.0/8'
  - '172.16.0.0/12'
  - '192.168.0.0/16'
  - '169.254.0.0/16'
  - '::1/128'
  - 'fe80::/10'
  - 'fc00::/7'

Monitoring Access

Enable Prometheus metrics:

enable_metrics: true
listeners:
  - port: 9000
    type: metrics
    bind_addresses: ['127.0.0.1']

Scrape with Prometheus for monitoring.


Backup & Recovery

Automated Backup Script

#!/bin/bash
# matrix-backup.sh

BACKUP_DIR="/opt/backups/matrix"
DATE=$(date +%Y%m%d_%H%M%S)

mkdir -p "$BACKUP_DIR"

# Backup PostgreSQL database
docker exec matrix-postgres pg_dump -U synapse synapse |     gzip > "$BACKUP_DIR/synapse-db-${DATE}.sql.gz"

# Backup Synapse configuration and keys
tar czf "$BACKUP_DIR/synapse-config-${DATE}.tar.gz"     /opt/matrix-synapse/data/homeserver.yaml     /opt/matrix-synapse/data/*.signing.key

# Backup media (optional, can be large)
# tar czf "$BACKUP_DIR/synapse-media-${DATE}.tar.gz" #     /opt/matrix-synapse/data/media_store

# Cleanup old backups (keep 30 days)
find "$BACKUP_DIR" -name "synapse-*.gz" -mtime +30 -delete

echo "Backup completed: $DATE"

Schedule daily:

0 3 * * * /opt/matrix-synapse/matrix-backup.sh

Disaster Recovery

# Stop services
docker compose down

# Restore database
gunzip -c /opt/backups/matrix/synapse-db-YYYYMMDD.sql.gz |     docker exec -i matrix-postgres psql -U synapse synapse

# Restore configuration
tar xzf /opt/backups/matrix/synapse-config-YYYYMMDD.tar.gz -C /

# Restart services
docker compose up -d

Conclusion

You now have a fully functional Matrix Synapse homeserver with:

  • Federated messaging across the Matrix network
  • Element web client for user access
  • Bridges to Discord, Telegram, and IRC
  • SSO integration for centralized authentication
  • Optimized performance for production use
  • Automated backups and monitoring

Next steps:

  1. Create rooms and invite users
  2. Set up additional bridges as needed
  3. Configure mobile Element apps
  4. Implement monitoring with Prometheus/Grafana
  5. Join the Matrix community at #synapse:matrix.org

Remember: Matrix federation means your server is part of a global network. Maintain good uptime, security, and follow the Matrix Code of Conduct.


Additional Resources

Official:

Community:

Tools:


Last Updated: December 2025 Version Covered: Synapse 1.119.0+ Word Count: 3,145 words