Matrix Synapse on a LYLIX VPS — federation, performance tuning, the database trap
Matrix Synapse is the most-deployed Matrix homeserver. It runs as a single VPS server fine; the failure modes are usually either federation traffic surprising you or the database growing without bound. This article covers the production patterns.
Sizing
- Solo / small family: 2 GB RAM, 2 CPU, 20 GB. Workable but tight.
- Community of 20-100 users: 4-8 GB RAM, 4 CPU, 50-100 GB.
- Larger: 8+ GB, dedicated DB box, media on S3.
RAM is the most-constrained resource — Synapse caches aggressively for performance.
The components
- Synapse — the homeserver, Python.
- PostgreSQL — primary store. Don't use SQLite for anything beyond evaluation.
- nginx — reverse proxy + TLS.
- Optional: workers for scaling specific functions.
Install
The official Synapse APT repository:
# Add Matrix repo
wget -O /usr/share/keyrings/matrix-org-archive-keyring.gpg \
https://packages.matrix.org/debian/matrix-org-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/matrix-org-archive-keyring.gpg] \
https://packages.matrix.org/debian/ $(lsb_release -cs) main" \
> /etc/apt/sources.list.d/matrix-org.list
apt update
apt install matrix-synapse-py3 postgresql nginx
During install, you'll be asked for the server name — this is what's in @user:server-name. Pick carefully; it's hard to change.
PostgreSQL setup
Default Synapse uses SQLite. Migrate immediately:
# Create the DB and user
sudo -u postgres psql <<EOF
CREATE USER synapse_user WITH PASSWORD '<password>';
CREATE DATABASE synapse
ENCODING 'UTF8'
LC_COLLATE='C'
LC_CTYPE='C'
template=template0
OWNER synapse_user;
EOF
The LC_COLLATE='C' matters — Synapse specifically requires C collation. Getting it wrong now means rebuilding the database later.
Edit Synapse config to point at PostgreSQL:
# /etc/matrix-synapse/homeserver.yaml
database:
name: psycopg2
args:
user: synapse_user
password: <password>
database: synapse
host: localhost
cp_min: 5
cp_max: 10
Restart Synapse, verify it connects.
Federation port (8448)
Matrix federation uses port 8448 by default for server-to-server traffic. You can run it on the same port as client traffic with a .well-known/matrix/server file:
{
"m.server": "matrix.example.com:443"
}
Serve this at https://example.com/.well-known/matrix/ server. Other servers fetch it before federation and respect the port you specify.
And the client-side .well-known/matrix/client:
{
"m.homeserver": {
"base_url": "https://matrix.example.com"
}
}
This means users can log in with handles like @user:example.com while the homeserver itself runs on matrix.example.com.
The database trap
Synapse's database grows continuously. Without intervention:
- State events accumulate forever.
- Media metadata accumulates forever.
- Federation cache accumulates.
By month 6 of an active community, your DB can be 50+ GB. By month 12, it's a problem.
Mitigations:
- State compression — synapse-compress- state tool reduces state event storage by 30-90%. Run monthly.
- Media retention — configure media_retention in homeserver.yaml. Local media you set retention by user; remote media by hours of age.
- Purge history API — for rooms you no longer need history for, the admin API can purge old events.
- Vacuum and analyze — set autovacuum to be aggressive on the synapse DB.
nginx config
Two server blocks: one for the main host (the .well-known files), one for the actual Synapse:
server {
listen 443 ssl;
server_name matrix.example.com;
location ~* ^(\/_matrix|\/_synapse\/client) {
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;
}
}
Media on S3
For larger deployments, use synapse_s3_storage_provider to move media to S3:
media_storage_providers:
- module: s3_storage_provider.S3StorageProviderBackend
store_local: True
store_remote: True
config:
bucket: matrix-media
endpoint_url: https://s3.us-west-001.backblazeb2.com
access_key_id: ...
secret_access_key: ...
Existing media stays local until you migrate it; new media goes to S3.
Workers for scaling
Past a few hundred concurrent users, the monolithic Synapse process becomes the bottleneck. The worker model splits responsibilities:
- federation_sender — sends outbound federation.
- federation_reader — receives inbound.
- synchrotron — handles /sync (the heaviest client request).
- media_repository — handles media uploads/downloads.
Adding workers is a configuration exercise — main server's homeserver.yaml + per-worker config files + nginx routing. The Synapse docs cover this comprehensively.
Federation traffic surprises
- One big room you joined (Matrix HQ, etc.) brings traffic from thousands of servers. Plan accordingly.
- Federation can spike when a popular event happens — servers announce state to each other in bursts.
- If federation is overwhelming your VPS, you can rate-limit specific federation peers in homeserver.yaml.
Useful admin API operations
# Get an admin access token (one-time setup), then:
# List users
curl -H "Authorization: Bearer <token>" \
https://matrix.example.com/_synapse/admin/v2/users
# Deactivate a user
curl -X POST -H "Authorization: Bearer <token>" \
-d '{"erase": true}' \
https://matrix.example.com/_synapse/admin/v1/deactivate/@user:example.com
# Purge a room's history older than N
curl -X POST -H "Authorization: Bearer <token>" \
-d '{"purge_up_to_ts": <timestamp>}' \
https://matrix.example.com/_synapse/admin/v1/purge_history/!room_id:example.com
Backups
- PostgreSQL — nightly pg_dump.
- homeserver.yaml — contains keys; treat like a secret.
- signing_key — if you lose this, federation breaks permanently. Back up separately.
- Media — if on local, back up the media_store dir; if on S3, the bucket itself.
Alternatives
If Synapse's resource profile is too heavy for your use case, consider:
- Dendrite — Matrix's Go-based homeserver. Lower resource use, less mature than Synapse but catching up.
- Conduit / Conduwuit — Rust-based, single binary, very light. Great for solo use; smaller ecosystem.
Also Read
Powered by WHMCompleteSolution