Tools: How to Self-Host Matrix Synapse with Docker Compose

Tools: How to Self-Host Matrix Synapse with Docker Compose

What Is Matrix Synapse?

Prerequisites

Docker Compose Configuration

Generate the Synapse Configuration

Configure PostgreSQL

Start the Stack

Initial Setup

Create an Admin User

Element Web Client Setup (Optional)

Configuration

Enable Public Registration

Federation

Media Storage Limits

Reverse Proxy

Backup

Database Backup

Restore

Troubleshooting

Federation Not Working

Registration Disabled Error

Database Connection Errors

Media Upload Failures

High Memory Usage

Signing Key Issues After Migration

Verdict

Related Matrix is an open, decentralized communication protocol for real-time messaging, voice, and video. Synapse is the reference homeserver implementation -- the software you run to participate in the Matrix network. Think of it like email: you run your own server, but you can communicate with anyone on any other Matrix server worldwide. Updated March 2026: Verified with latest Docker images and configurations. Synapse replaces Slack, Discord, Microsoft Teams, and other centralized chat platforms. Pair it with Element (the most popular Matrix client) and you get end-to-end encrypted messaging, voice/video calls, file sharing, and bridging to other platforms -- all under your control. Federation means your users can chat with anyone on the Matrix network, not just people on your server. Create a project directory: Create a .env file with your configuration values: Generate your secrets now: Create a docker-compose.yml file: Before starting Synapse for the first time, you need to generate the homeserver.yaml configuration file. Run this command (replace example.com with your actual server name): This creates homeserver.yaml inside the synapse_data volume. You need to edit it to use PostgreSQL instead of the default SQLite. Find the generated config file. With named volumes, copy it out, edit it, and copy it back: Open homeserver.yaml and find the database section. Replace the entire database block with: Use the same POSTGRES_USER, POSTGRES_PASSWORD, and POSTGRES_DB values from your .env file. The host is synapse_db -- the container name on the Docker network. While you have the file open, also verify or set these values: Copy the edited config back into the volume: Ensure correct ownership (Synapse runs as UID 991:GID 991 by default): Check that both containers are healthy: Verify Synapse is responding: You should get a JSON response listing supported Matrix API versions. With registration disabled (the safe default), create your first admin user via the command line: You can now log in with any Matrix client using your server URL (https://matrix.example.com) and the credentials you just created. Element Web is the most popular Matrix client. You can self-host it alongside Synapse for a complete setup. Add this service to your docker-compose.yml: Create element-config.json: Replace matrix.example.com and example.com with your actual domains. Point your reverse proxy at port 8080 for the Element Web subdomain (e.g., element.example.com). By default, registration is disabled. To allow anyone to register on your server, edit homeserver.yaml: For production use, require email verification instead: Restart Synapse after config changes: Federation is enabled by default. For other servers to find yours, you need proper DNS and well-known delegation. If your server_name is example.com but Synapse runs on matrix.example.com, set up delegation with one of these methods: Method 1: .well-known (recommended) Serve this JSON at https://example.com/.well-known/matrix/server: And this at https://example.com/.well-known/matrix/client: Method 2: DNS SRV record Create a DNS SRV record: Test federation with the Matrix Federation Tester. Control upload sizes and storage in homeserver.yaml: For servers with limited disk space, consider setting up media storage on a separate volume or enabling the media retention settings to automatically clean up old remote media. Synapse should sit behind a reverse proxy that handles TLS. Your reverse proxy must: If you also host Element Web, add a second proxy rule for element.example.com pointing to http://127.0.0.1:8080. For detailed reverse proxy configuration with Nginx Proxy Manager, Traefik, or Caddy, see Reverse Proxy Setup. Critical data to back up: Back up both the database and the data volume daily. Store backups off-server. See Backup Strategy for a complete 3-2-1 backup approach. Symptom: Users on other Matrix servers cannot find or message your users. The Federation Tester shows errors. Fix: Check these in order: Symptom: Users see "Registration has been disabled" when trying to create an account. Fix: This is the default and intentional. Either: Symptom: Synapse logs show psycopg2.OperationalError: could not connect to server or similar PostgreSQL errors. It must return UTF8. If not, delete the database volume and recreate it: Symptom: Users cannot upload files or images. Errors about request body too large. Symptom: Synapse consumes 2 GB+ of RAM and the server becomes unresponsive. Fix: Synapse is known for high memory usage, especially on active servers. Mitigate it: Symptom: Federation breaks after moving Synapse to a new server. Other servers reject your messages. Fix: The signing key in /data/example.com.signing.key must be preserved across migrations. If you lost it, you will need to generate new keys and wait for other servers to pick up the change. Always include the entire /data directory in your backups. Matrix Synapse is the best self-hosted chat platform for most people. Nothing else gives you decentralized federation, end-to-end encryption, bridging to other platforms (Slack, Discord, IRC, Telegram), and a mature ecosystem of clients. The Matrix protocol is an open standard, so you are never locked into a single implementation. The trade-off is complexity. Synapse is harder to set up and run than simpler alternatives, and it uses more RAM than you would expect. Federation, while powerful, adds operational overhead -- DNS delegation, certificate management, and debugging inter-server communication are not trivial. If you just need a Slack replacement for your team and do not care about federation or bridging, Mattermost is simpler to deploy and lighter on resources. If you want the full power of decentralized, encrypted communication with the ability to talk to anyone on the Matrix network, Synapse is the clear choice. Templates let you quickly answer FAQs or store snippets for re-use. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse

Command

Copy

$ mkdir -p /opt/matrix-synapse && cd /opt/matrix-synapse mkdir -p /opt/matrix-synapse && cd /opt/matrix-synapse mkdir -p /opt/matrix-synapse && cd /opt/matrix-synapse # .env # REQUIRED: Change all of these before starting # Your Matrix server name — this is permanent and cannot be changed later. # Use your base domain (example.com), not a subdomain. # Users will be @user:example.com SYNAPSE_SERVER_NAME=example.com # Domain where Synapse is actually reachable (can differ from server name) SYNAPSE_SERVER_HOSTNAME=matrix.example.com # PostgreSQL credentials POSTGRES_DB=synapse POSTGRES_USER=synapse POSTGRES_PASSWORD=change-this-to-a-strong-password # Synapse secrets — generate with: openssl rand -hex 32 SYNAPSE_REGISTRATION_SHARED_SECRET=generate-a-long-random-string-here SYNAPSE_MACAROON_SECRET_KEY=generate-another-long-random-string-here SYNAPSE_FORM_SECRET=generate-yet-another-long-random-string-here # .env # REQUIRED: Change all of these before starting # Your Matrix server name — this is permanent and cannot be changed later. # Use your base domain (example.com), not a subdomain. # Users will be @user:example.com SYNAPSE_SERVER_NAME=example.com # Domain where Synapse is actually reachable (can differ from server name) SYNAPSE_SERVER_HOSTNAME=matrix.example.com # PostgreSQL credentials POSTGRES_DB=synapse POSTGRES_USER=synapse POSTGRES_PASSWORD=change-this-to-a-strong-password # Synapse secrets — generate with: openssl rand -hex 32 SYNAPSE_REGISTRATION_SHARED_SECRET=generate-a-long-random-string-here SYNAPSE_MACAROON_SECRET_KEY=generate-another-long-random-string-here SYNAPSE_FORM_SECRET=generate-yet-another-long-random-string-here # .env # REQUIRED: Change all of these before starting # Your Matrix server name — this is permanent and cannot be changed later. # Use your base domain (example.com), not a subdomain. # Users will be @user:example.com SYNAPSE_SERVER_NAME=example.com # Domain where Synapse is actually reachable (can differ from server name) SYNAPSE_SERVER_HOSTNAME=matrix.example.com # PostgreSQL credentials POSTGRES_DB=synapse POSTGRES_USER=synapse POSTGRES_PASSWORD=change-this-to-a-strong-password # Synapse secrets — generate with: openssl rand -hex 32 SYNAPSE_REGISTRATION_SHARED_SECRET=generate-a-long-random-string-here SYNAPSE_MACAROON_SECRET_KEY=generate-another-long-random-string-here SYNAPSE_FORM_SECRET=generate-yet-another-long-random-string-here echo "SYNAPSE_REGISTRATION_SHARED_SECRET=$(openssl rand -hex 32)" >> .env echo "SYNAPSE_MACAROON_SECRET_KEY=$(openssl rand -hex 32)" >> .env echo "SYNAPSE_FORM_SECRET=$(openssl rand -hex 32)" >> .env echo "SYNAPSE_REGISTRATION_SHARED_SECRET=$(openssl rand -hex 32)" >> .env echo "SYNAPSE_MACAROON_SECRET_KEY=$(openssl rand -hex 32)" >> .env echo "SYNAPSE_FORM_SECRET=$(openssl rand -hex 32)" >> .env echo "SYNAPSE_REGISTRATION_SHARED_SECRET=$(openssl rand -hex 32)" >> .env echo "SYNAPSE_MACAROON_SECRET_KEY=$(openssl rand -hex 32)" >> .env echo "SYNAPSE_FORM_SECRET=$(openssl rand -hex 32)" >> .env services: synapse: image: matrixdotorg/synapse:v1.149.1 container_name: synapse -weight: 500;">restart: unless-stopped environment: - SYNAPSE_CONFIG_PATH=/data/homeserver.yaml volumes: - synapse_data:/data ports: # Client API — expose to reverse proxy only - "127.0.0.1:8008:8008" # Federation — expose publicly if you want inter-server communication - "8448:8448" depends_on: synapse_db: condition: service_healthy networks: - matrix healthcheck: test: ["CMD-SHELL", "-weight: 500;">curl -fSs http://localhost:8008/health || exit 1"] interval: 30s timeout: 10s retries: 3 start_period: 40s synapse_db: image: postgres:15-alpine container_name: synapse_db -weight: 500;">restart: unless-stopped environment: POSTGRES_DB: ${POSTGRES_DB} POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} # Required: Synapse needs UTF-8 encoding with C locale POSTGRES_INITDB_ARGS: "--encoding=UTF8 --locale=C" volumes: - synapse_db_data:/var/lib/postgresql/data networks: - matrix healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"] interval: 10s timeout: 5s retries: 5 volumes: synapse_data: driver: local synapse_db_data: driver: local networks: matrix: driver: bridge services: synapse: image: matrixdotorg/synapse:v1.149.1 container_name: synapse -weight: 500;">restart: unless-stopped environment: - SYNAPSE_CONFIG_PATH=/data/homeserver.yaml volumes: - synapse_data:/data ports: # Client API — expose to reverse proxy only - "127.0.0.1:8008:8008" # Federation — expose publicly if you want inter-server communication - "8448:8448" depends_on: synapse_db: condition: service_healthy networks: - matrix healthcheck: test: ["CMD-SHELL", "-weight: 500;">curl -fSs http://localhost:8008/health || exit 1"] interval: 30s timeout: 10s retries: 3 start_period: 40s synapse_db: image: postgres:15-alpine container_name: synapse_db -weight: 500;">restart: unless-stopped environment: POSTGRES_DB: ${POSTGRES_DB} POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} # Required: Synapse needs UTF-8 encoding with C locale POSTGRES_INITDB_ARGS: "--encoding=UTF8 --locale=C" volumes: - synapse_db_data:/var/lib/postgresql/data networks: - matrix healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"] interval: 10s timeout: 5s retries: 5 volumes: synapse_data: driver: local synapse_db_data: driver: local networks: matrix: driver: bridge services: synapse: image: matrixdotorg/synapse:v1.149.1 container_name: synapse -weight: 500;">restart: unless-stopped environment: - SYNAPSE_CONFIG_PATH=/data/homeserver.yaml volumes: - synapse_data:/data ports: # Client API — expose to reverse proxy only - "127.0.0.1:8008:8008" # Federation — expose publicly if you want inter-server communication - "8448:8448" depends_on: synapse_db: condition: service_healthy networks: - matrix healthcheck: test: ["CMD-SHELL", "-weight: 500;">curl -fSs http://localhost:8008/health || exit 1"] interval: 30s timeout: 10s retries: 3 start_period: 40s synapse_db: image: postgres:15-alpine container_name: synapse_db -weight: 500;">restart: unless-stopped environment: POSTGRES_DB: ${POSTGRES_DB} POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} # Required: Synapse needs UTF-8 encoding with C locale POSTGRES_INITDB_ARGS: "--encoding=UTF8 --locale=C" volumes: - synapse_db_data:/var/lib/postgresql/data networks: - matrix healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"] interval: 10s timeout: 5s retries: 5 volumes: synapse_data: driver: local synapse_db_data: driver: local networks: matrix: driver: bridge -weight: 500;">docker compose run --rm -e SYNAPSE_SERVER_NAME=example.com -e SYNAPSE_REPORT_STATS=no synapse generate -weight: 500;">docker compose run --rm -e SYNAPSE_SERVER_NAME=example.com -e SYNAPSE_REPORT_STATS=no synapse generate -weight: 500;">docker compose run --rm -e SYNAPSE_SERVER_NAME=example.com -e SYNAPSE_REPORT_STATS=no synapse generate # Copy the config out of the volume -weight: 500;">docker compose cp synapse:/data/homeserver.yaml ./homeserver.yaml # Copy the config out of the volume -weight: 500;">docker compose cp synapse:/data/homeserver.yaml ./homeserver.yaml # Copy the config out of the volume -weight: 500;">docker compose cp synapse:/data/homeserver.yaml ./homeserver.yaml database: name: psycopg2 args: user: synapse password: change-this-to-a-strong-password database: synapse host: synapse_db port: 5432 cp_min: 5 cp_max: 10 database: name: psycopg2 args: user: synapse password: change-this-to-a-strong-password database: synapse host: synapse_db port: 5432 cp_min: 5 cp_max: 10 database: name: psycopg2 args: user: synapse password: change-this-to-a-strong-password database: synapse host: synapse_db port: 5432 cp_min: 5 cp_max: 10 server_name: "example.com" public_baseurl: "https://matrix.example.com/" listeners: - port: 8008 tls: false type: http x_forwarded: true resources: - names: [client, federation] compress: false registration_shared_secret: "your-generated-secret-here" macaroon_secret_key: "your-generated-secret-here" form_secret: "your-generated-secret-here" enable_registration: false server_name: "example.com" public_baseurl: "https://matrix.example.com/" listeners: - port: 8008 tls: false type: http x_forwarded: true resources: - names: [client, federation] compress: false registration_shared_secret: "your-generated-secret-here" macaroon_secret_key: "your-generated-secret-here" form_secret: "your-generated-secret-here" enable_registration: false server_name: "example.com" public_baseurl: "https://matrix.example.com/" listeners: - port: 8008 tls: false type: http x_forwarded: true resources: - names: [client, federation] compress: false registration_shared_secret: "your-generated-secret-here" macaroon_secret_key: "your-generated-secret-here" form_secret: "your-generated-secret-here" enable_registration: false -weight: 500;">docker compose cp ./homeserver.yaml synapse:/data/homeserver.yaml -weight: 500;">docker compose cp ./homeserver.yaml synapse:/data/homeserver.yaml -weight: 500;">docker compose cp ./homeserver.yaml synapse:/data/homeserver.yaml -weight: 500;">docker compose run --rm synapse chown 991:991 /data/homeserver.yaml -weight: 500;">docker compose run --rm synapse chown 991:991 /data/homeserver.yaml -weight: 500;">docker compose run --rm synapse chown 991:991 /data/homeserver.yaml -weight: 500;">docker compose up -d -weight: 500;">docker compose up -d -weight: 500;">docker compose up -d -weight: 500;">docker compose ps -weight: 500;">docker compose ps -weight: 500;">docker compose ps -weight: 500;">curl http://localhost:8008/_matrix/client/versions -weight: 500;">curl http://localhost:8008/_matrix/client/versions -weight: 500;">curl http://localhost:8008/_matrix/client/versions -weight: 500;">docker compose exec synapse register_new_matrix_user -u admin -p your-secure-password -a -c /data/homeserver.yaml http://localhost:8008 -weight: 500;">docker compose exec synapse register_new_matrix_user -u admin -p your-secure-password -a -c /data/homeserver.yaml http://localhost:8008 -weight: 500;">docker compose exec synapse register_new_matrix_user -u admin -p your-secure-password -a -c /data/homeserver.yaml http://localhost:8008 element: image: vectorim/element-web:v1.12.12 container_name: element -weight: 500;">restart: unless-stopped volumes: - ./element-config.json:/app/config.json:ro ports: - "127.0.0.1:8080:80" networks: - matrix element: image: vectorim/element-web:v1.12.12 container_name: element -weight: 500;">restart: unless-stopped volumes: - ./element-config.json:/app/config.json:ro ports: - "127.0.0.1:8080:80" networks: - matrix element: image: vectorim/element-web:v1.12.12 container_name: element -weight: 500;">restart: unless-stopped volumes: - ./element-config.json:/app/config.json:ro ports: - "127.0.0.1:8080:80" networks: - matrix { "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", "bug_report_endpoint_url": "https://element.io/bugreports/submit", "showLabsSettings": true, "default_theme": "dark" } { "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", "bug_report_endpoint_url": "https://element.io/bugreports/submit", "showLabsSettings": true, "default_theme": "dark" } { "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", "bug_report_endpoint_url": "https://element.io/bugreports/submit", "showLabsSettings": true, "default_theme": "dark" } -weight: 500;">docker compose up -d -weight: 500;">docker compose up -d -weight: 500;">docker compose up -d enable_registration: true enable_registration_without_verification: true enable_registration: true enable_registration_without_verification: true enable_registration: true enable_registration_without_verification: true enable_registration: true registrations_require_3pid: - email email: smtp_host: smtp.example.com smtp_port: 587 smtp_user: "[email protected]" smtp_pass: "smtp-password" notif_from: "Matrix <[email protected]>" enable_registration: true registrations_require_3pid: - email email: smtp_host: smtp.example.com smtp_port: 587 smtp_user: "[email protected]" smtp_pass: "smtp-password" notif_from: "Matrix <[email protected]>" enable_registration: true registrations_require_3pid: - email email: smtp_host: smtp.example.com smtp_port: 587 smtp_user: "[email protected]" smtp_pass: "smtp-password" notif_from: "Matrix <[email protected]>" -weight: 500;">docker compose -weight: 500;">restart synapse -weight: 500;">docker compose -weight: 500;">restart synapse -weight: 500;">docker compose -weight: 500;">restart synapse { "m.server": "matrix.example.com:443" } { "m.server": "matrix.example.com:443" } { "m.server": "matrix.example.com:443" } { "m.homeserver": { "base_url": "https://matrix.example.com" } } { "m.homeserver": { "base_url": "https://matrix.example.com" } } { "m.homeserver": { "base_url": "https://matrix.example.com" } } _matrix._tcp.example.com. 3600 IN SRV 10 0 443 matrix.example.com. _matrix._tcp.example.com. 3600 IN SRV 10 0 443 matrix.example.com. _matrix._tcp.example.com. 3600 IN SRV 10 0 443 matrix.example.com. # Maximum upload size in bytes (default 50MB) max_upload_size: 50M # Maximum image size for URL previews max_image_pixels: 32M # How long to keep remote media cached (default 90 days) # Remote media is content fetched from other homeservers media_retention: remote_media_lifetime: 90d # Maximum upload size in bytes (default 50MB) max_upload_size: 50M # Maximum image size for URL previews max_image_pixels: 32M # How long to keep remote media cached (default 90 days) # Remote media is content fetched from other homeservers media_retention: remote_media_lifetime: 90d # Maximum upload size in bytes (default 50MB) max_upload_size: 50M # Maximum image size for URL previews max_image_pixels: 32M # How long to keep remote media cached (default 90 days) # Remote media is content fetched from other homeservers media_retention: remote_media_lifetime: 90d -weight: 500;">docker compose exec synapse_db pg_dump -U synapse synapse > synapse_backup_$(date +%Y%m%d).sql -weight: 500;">docker compose exec synapse_db pg_dump -U synapse synapse > synapse_backup_$(date +%Y%m%d).sql -weight: 500;">docker compose exec synapse_db pg_dump -U synapse synapse > synapse_backup_$(date +%Y%m%d).sql -weight: 500;">docker compose exec -T synapse_db psql -U synapse synapse < synapse_backup_20260224.sql -weight: 500;">docker compose exec -T synapse_db psql -U synapse synapse < synapse_backup_20260224.sql -weight: 500;">docker compose exec -T synapse_db psql -U synapse synapse < synapse_backup_20260224.sql -weight: 500;">curl https://example.com/.well-known/matrix/server -weight: 500;">curl https://example.com/.well-known/matrix/server -weight: 500;">curl https://example.com/.well-known/matrix/server -weight: 500;">docker compose exec synapse_db psql -U synapse -c "SHOW server_encoding;" -weight: 500;">docker compose exec synapse_db psql -U synapse -c "SHOW server_encoding;" -weight: 500;">docker compose exec synapse_db psql -U synapse -c "SHOW server_encoding;" -weight: 500;">docker compose down -weight: 500;">docker volume rm matrix-synapse_synapse_db_data -weight: 500;">docker compose up -d -weight: 500;">docker compose down -weight: 500;">docker volume rm matrix-synapse_synapse_db_data -weight: 500;">docker compose up -d -weight: 500;">docker compose down -weight: 500;">docker volume rm matrix-synapse_synapse_db_data -weight: 500;">docker compose up -d df -h -weight: 500;">docker system df df -h -weight: 500;">docker system df df -h -weight: 500;">docker system df database: args: cp_min: 5 cp_max: 10 database: args: cp_min: 5 cp_max: 10 database: args: cp_min: 5 cp_max: 10 federation_rr_transactions_per_room_per_second: 20 federation_rr_transactions_per_room_per_second: 20 federation_rr_transactions_per_room_per_second: 20 - A Linux server (Ubuntu 22.04+ recommended) - Docker and Docker Compose installed (guide) - A domain name pointed at your server (e.g., matrix.example.com) - 2 GB of RAM minimum (4 GB+ recommended for more than a handful of users) - 20 GB of free disk space (media uploads grow over time) - Ports 8448 (federation) and 443 (reverse proxy) accessible from the internet if you want federation - -u admin -- the username (will be @admin:example.com) - -p your-secure-password -- the password (change this) - -a -- makes this user a server admin - -c /data/homeserver.yaml -- path to the config inside the container - Proxy https://matrix.example.com to http://127.0.0.1:8008 - Forward the X-Forwarded-For and X-Forwarded-Proto headers - Allow large request bodies (for file uploads): set client_max_body_size 50M in Nginx or equivalent - Proxy WebSocket connections for real-time sync - PostgreSQL database -- contains all messages, room state, user accounts, and encryption keys. This is the most important backup target. - Synapse data volume (synapse_data) -- contains homeserver.yaml, media uploads, signing keys, and log configuration. - Verify .well-known/matrix/server is accessible from the internet: - Confirm port 8448 is open in your firewall (or port 443 if using well-known delegation). - Check that your reverse proxy forwards traffic to Synapse correctly. - Verify your TLS certificate is valid and not self-signed -- federation requires trusted certificates. - Run the Federation Tester and fix any reported issues. - Create users manually with register_new_matrix_user (see Initial Setup above) - Enable registration in homeserver.yaml by setting enable_registration: true and -weight: 500;">restart Synapse - Verify the database container is running: -weight: 500;">docker compose ps synapse_db - Check that host in the database section of homeserver.yaml matches the database -weight: 500;">service name (synapse_db). - Confirm the username, password, and database name match between homeserver.yaml and your .env file. - Ensure the database was initialized with the correct encoding: - Check max_upload_size in homeserver.yaml (default is 50M). - Your reverse proxy must also allow large request bodies. For Nginx, add client_max_body_size 50m; to the server block. For Caddy, set request_body max size. - Check disk space on the server -- if the data volume is full, uploads will fail: - Add connection pooling limits to the database config in homeserver.yaml: - Limit the number of federation connections by adding to homeserver.yaml: - Set up workers for large deployments (50+ active users). Synapse supports splitting work across multiple worker processes. See the Synapse workers documentation. - Consider adding a Redis container for inter-worker communication if you -weight: 500;">enable workers. - RAM: 1 GB idle with no users. 2 GB minimum for a small deployment (under 20 users). 4 GB+ recommended for 100+ users. Synapse is memory-hungry -- plan accordingly. - CPU: Low for small deployments. Medium for active servers with federation. CPU usage spikes during media processing and room state resolution. - Disk: 500 MB for the application itself. Media storage grows unbounded with usage -- budget at least 20 GB and monitor growth. PostgreSQL database grows roughly 1 GB per 100,000 messages. - Matrix vs Discord: Self-Hosted Chat Compared - Matrix vs XMPP: Federated Chat Protocols Compared - Matrix vs Zulip: Which Chat Server to Self-Host? - Best Self-Hosted Communication & Chat - Matrix Synapse vs Mattermost - Matrix Synapse vs Rocket.Chat - Self-Hosted Alternatives to Slack - Self-Hosted Alternatives to Discord - Docker Compose Basics - Reverse Proxy Setup - Backup Strategy