How to Self-Host Matrix Synapse with Docker Compose
What Is Matrix Synapse?
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.
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.
Prerequisites
- 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
Docker Compose Configuration
Create a project directory:
mkdir -p /opt/matrix-synapse && cd /opt/matrix-synapse
Create a .env file with your configuration values:
# .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
Generate your secrets now:
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
Create a docker-compose.yml file:
services:
synapse:
image: matrixdotorg/synapse:v1.147.1
container_name: synapse
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", "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
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
Generate the Synapse Configuration
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):
docker compose run --rm -e SYNAPSE_SERVER_NAME=example.com -e SYNAPSE_REPORT_STATS=no synapse generate
This creates homeserver.yaml inside the synapse_data volume. You need to edit it to use PostgreSQL instead of the default SQLite.
Configure PostgreSQL
Find the generated config file. With named volumes, copy it out, edit it, and copy it back:
# Copy the config out of the volume
docker compose cp synapse:/data/homeserver.yaml ./homeserver.yaml
Open homeserver.yaml and find the database section. Replace the entire database block with:
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
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:
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
Copy the edited config back into the volume:
docker compose cp ./homeserver.yaml synapse:/data/homeserver.yaml
Ensure correct ownership (Synapse runs as UID 991:GID 991 by default):
docker compose run --rm synapse chown 991:991 /data/homeserver.yaml
Start the Stack
docker compose up -d
Check that both containers are healthy:
docker compose ps
Verify Synapse is responding:
curl http://localhost:8008/_matrix/client/versions
You should get a JSON response listing supported Matrix API versions.
Initial Setup
Create an Admin User
With registration disabled (the safe default), create your first admin user via the command line:
docker compose exec synapse register_new_matrix_user -u admin -p your-secure-password -a -c /data/homeserver.yaml http://localhost:8008
Flags:
-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
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 Client Setup (Optional)
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:
element:
image: vectorim/element-web:v1.11.96
container_name: element
restart: unless-stopped
volumes:
- ./element-config.json:/app/config.json:ro
ports:
- "127.0.0.1:8080:80"
networks:
- matrix
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",
"bug_report_endpoint_url": "https://element.io/bugreports/submit",
"showLabsSettings": true,
"default_theme": "dark"
}
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).
Apply the changes:
docker compose up -d
Configuration
Enable Public Registration
By default, registration is disabled. To allow anyone to register on your server, edit homeserver.yaml:
enable_registration: true
enable_registration_without_verification: true
For production use, require email verification instead:
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]>"
Restart Synapse after config changes:
docker compose restart synapse
Federation
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:
{
"m.server": "matrix.example.com:443"
}
And this at https://example.com/.well-known/matrix/client:
{
"m.homeserver": {
"base_url": "https://matrix.example.com"
}
}
Method 2: DNS SRV record
Create a DNS SRV record:
_matrix._tcp.example.com. 3600 IN SRV 10 0 443 matrix.example.com.
Test federation with the Matrix Federation Tester.
Media Storage Limits
Control upload sizes and storage in homeserver.yaml:
# 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
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.
Reverse Proxy
Synapse should sit behind a reverse proxy that handles TLS. Your reverse proxy must:
- Proxy
https://matrix.example.comtohttp://127.0.0.1:8008 - Forward the
X-Forwarded-ForandX-Forwarded-Protoheaders - Allow large request bodies (for file uploads): set
client_max_body_size 50Min Nginx or equivalent - Proxy WebSocket connections for real-time sync
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.
Backup
Critical data to back up:
- PostgreSQL database — contains all messages, room state, user accounts, and encryption keys. This is the most important backup target.
- Synapse data volume (
synapse_data) — containshomeserver.yaml, media uploads, signing keys, and log configuration.
Database Backup
docker compose exec synapse_db pg_dump -U synapse synapse > synapse_backup_$(date +%Y%m%d).sql
Restore
docker compose exec -T synapse_db psql -U synapse synapse < synapse_backup_20260224.sql
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.
Troubleshooting
Federation Not Working
Symptom: Users on other Matrix servers cannot find or message your users. The Federation Tester shows errors.
Fix: Check these in order:
- Verify
.well-known/matrix/serveris accessible from the internet:curl https://example.com/.well-known/matrix/server - 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.
Registration Disabled Error
Symptom: Users see “Registration has been disabled” when trying to create an account.
Fix: This is the default and intentional. Either:
- Create users manually with
register_new_matrix_user(see Initial Setup above) - Enable registration in
homeserver.yamlby settingenable_registration: trueand restart Synapse
Database Connection Errors
Symptom: Synapse logs show psycopg2.OperationalError: could not connect to server or similar PostgreSQL errors.
Fix:
- Verify the database container is running:
docker compose ps synapse_db - Check that
hostin thedatabasesection ofhomeserver.yamlmatches the database service name (synapse_db). - Confirm the username, password, and database name match between
homeserver.yamland your.envfile. - Ensure the database was initialized with the correct encoding:
It must returndocker compose exec synapse_db psql -U synapse -c "SHOW server_encoding;"UTF8. If not, delete the database volume and recreate it:docker compose down docker volume rm matrix-synapse_synapse_db_data docker compose up -d
Media Upload Failures
Symptom: Users cannot upload files or images. Errors about request body too large.
Fix:
- Check
max_upload_sizeinhomeserver.yaml(default is50M). - Your reverse proxy must also allow large request bodies. For Nginx, add
client_max_body_size 50m;to the server block. For Caddy, setrequest_bodymax size. - Check disk space on the server — if the data volume is full, uploads will fail:
df -h docker system df
High Memory Usage
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:
- Add connection pooling limits to the database config in
homeserver.yaml:database: args: cp_min: 5 cp_max: 10 - Limit the number of federation connections by adding to
homeserver.yaml:federation_rr_transactions_per_room_per_second: 20 - 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 enable workers.
Signing Key Issues After Migration
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.
Resource Requirements
- 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.
Verdict
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.
Related
Get self-hosting tips in your inbox
New guides, comparisons, and setup tutorials — delivered weekly. No spam.