Self-Host a Matrix Server (Synapse)
Deploy a production Matrix Synapse homeserver with PostgreSQL, Nginx TLS reverse proxy, federation, and optional bridges on your own Linux VPS.
Before you start
- ▸A VPS or dedicated server with a public IP address and at least 1 GB RAM
- ▸A registered domain name with the ability to add DNS records
- ▸Ports 80 and 443 open in your server firewall
- ▸Root or sudo access on the target machine
Matrix is an open, federated messaging protocol. Synapse is the reference homeserver implementation: battle-tested, Python-based, and capable of bridging to Slack, Discord, IRC, and more. Running your own homeserver gives you full control over your data, lets you federate with the wider Matrix network, and acts as a jumping-off point for bridges and bots. This is a non-trivial setup — plan for 30–60 minutes and a VPS with at least 1 GB RAM (2 GB recommended).
Prerequisites and Architecture Overview
You need a public-facing server with a fully qualified domain name. Matrix federation requires valid TLS and specific DNS records. The stack you will build:
- Synapse — the homeserver process
- PostgreSQL — production-grade storage (SQLite is for testing only)
- Nginx — reverse proxy terminating TLS and handling federation routing
- Certbot — Let's Encrypt certificates
Throughout this guide, replace matrix.example.com with your actual server hostname and example.com with the domain you want user IDs to appear under (e.g., @alice:example.com). These can be the same domain or different ones (the latter requires delegation).
Step 1: Install PostgreSQL
Synapse's SQLite backend is explicitly unsupported for production. Install and configure Postgres first.
Debian / Ubuntu
sudo apt update
sudo apt install -y postgresql postgresql-contrib
Fedora / RHEL / Rocky
sudo dnf install -y postgresql-server postgresql-contrib
sudo postgresql-setup --initdb
sudo systemctl enable --now postgresql
Arch
sudo pacman -S postgresql
sudo -u postgres initdb -D /var/lib/postgres/data
sudo systemctl enable --now postgresql
Create the Synapse database and user:
sudo -u postgres psql <<'SQL'
CREATE USER synapse WITH PASSWORD 'changeme_strong_password';
CREATE DATABASE synapse
ENCODING 'UTF8'
LC_COLLATE 'C'
LC_CTYPE 'C'
TEMPLATE template0
OWNER synapse;
SQL
The LC_COLLATE='C' setting is required by Synapse — do not skip it.
Step 2: Install Synapse
The Matrix.org team maintains official packages. Using them gives you timely security updates.
Debian / Ubuntu
sudo apt install -y lsb-release wget apt-transport-https
wget -qO /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" \
| sudo tee /etc/apt/sources.list.d/matrix-org.list
sudo apt update
sudo apt install -y matrix-synapse-py3
The installer will prompt for your server name — enter example.com (the domain users will be identified under, not necessarily the server hostname).
Fedora / RHEL / Rocky
sudo dnf install -y python3-pip python3-devel libffi-devel openssl-devel libjpeg-devel
pip3 install --user matrix-synapse
For RHEL-family servers, the pip install is the most reliable route. Consider a dedicated virtualenv under /opt/synapse for isolation.
Arch
sudo pacman -S matrix-synapse
Step 3: Configure Synapse
Generate a starting configuration (skip if the Debian package installer already created one):
sudo -u synapse python3 -m synapse.app.homeserver \
--server-name example.com \
--config-path /etc/matrix-synapse/homeserver.yaml \
--generate-config \
--report-stats=no
Edit /etc/matrix-synapse/homeserver.yaml. The critical sections:
sudo nano /etc/matrix-synapse/homeserver.yaml
Find and update the database block:
database:
name: psycopg2
args:
user: synapse
password: changeme_strong_password
database: synapse
host: localhost
cp_min: 5
cp_max: 10
Set the public base URL and disable open registration (strongly recommended):
public_baseurl: https://matrix.example.com/
enable_registration: false
Enable the federation sender and ensure the listener block includes port 8448 or relies on the reverse proxy (preferred — see next step):
listeners:
- port: 8008
tls: false
type: http
x_forwarded: true
bind_addresses: ['127.0.0.1']
resources:
- names: [client, federation]
compress: false
Step 4: Reverse Proxy with Nginx and TLS
Install Nginx and Certbot:
Debian / Ubuntu
sudo apt install -y nginx certbot python3-certbot-nginx
Fedora / Rocky
sudo dnf install -y nginx certbot python3-certbot-nginx
Arch
sudo pacman -S nginx certbot certbot-nginx
Obtain a certificate (Nginx must be running and port 80 open):
sudo systemctl enable --now nginx
sudo certbot --nginx -d matrix.example.com
Create /etc/nginx/sites-available/matrix (Debian/Ubuntu) or /etc/nginx/conf.d/matrix.conf (Fedora/Arch):
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name matrix.example.com;
ssl_certificate /etc/letsencrypt/live/matrix.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/matrix.example.com/privkey.pem;
# Federation and client traffic
location ~ ^(/_matrix|/_synapse/client) {
proxy_pass http://127.0.0.1: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;
}
}
# Redirect HTTP to HTTPS
server {
listen 80;
server_name matrix.example.com;
return 301 https://$host$request_uri;
}
On Debian/Ubuntu, enable the site:
sudo ln -s /etc/nginx/sites-available/matrix /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
Step 5: Federation DNS and Well-Known Delegation
If your Matrix domain (example.com) differs from your server hostname (matrix.example.com), set up delegation so other servers can find yours. The simplest method is well-known delegation.
Create the file served at https://example.com/.well-known/matrix/server:
{"m.server": "matrix.example.com:443"}
Serve it from whatever hosts example.com. Alternatively, add an SRV DNS record:
_matrix._tcp.example.com. 3600 IN SRV 10 5 443 matrix.example.com.
Verify federation using the Matrix Federation Tester — enter example.com and confirm all checks pass before proceeding.
Step 6: Start and Enable Synapse
sudo systemctl enable --now matrix-synapse
sudo systemctl status matrix-synapse
Watch the logs for startup errors:
sudo journalctl -u matrix-synapse -f
Step 7: Create Your First Admin User
With open registration disabled, create users via the CLI:
sudo register_new_matrix_user -c /etc/matrix-synapse/homeserver.yaml http://localhost:8008
You will be prompted for username, password, and whether the account is an admin. Connect using Element or any Matrix client pointing to https://matrix.example.com.
Step 8: Install a Bridge (Optional)
Bridges connect Matrix to other platforms. mautrix-telegram and mautrix-discord are well-maintained Go/Python bridges. The pattern is the same for all:
- Install the bridge package or pip install it into its own virtualenv.
- Generate its config: point it at your Synapse homeserver URL and Postgres DB.
- Register the bridge with Synapse by adding its registration YAML to
app_service_config_filesinhomeserver.yaml. - Restart Synapse, then start the bridge as its own systemd service.
# Example: add bridge registration to Synapse config
app_service_config_files:
- /etc/matrix-synapse/telegram-registration.yaml
Verification
Run a full end-to-end check:
# Confirm the client API responds
curl https://matrix.example.com/_matrix/client/versions
# Confirm federation endpoint
curl https://matrix.example.com/_matrix/federation/v1/version
Both should return JSON. Use the Federation Tester for a thorough remote validation.
Troubleshooting
- Synapse fails to start with DB errors: Double-check the
LC_COLLATE='C'setting. Drop and recreate the database if needed — Synapse will repopulate it on first run. - Federation tester shows "connection refused" on port 8448: You are using well-known delegation via 443; make sure the
m.serverJSON file is accessible and the Nginx location block matches/_matrix. - High memory usage: Synapse is memory-hungry. Tune
caches.global_factorinhomeserver.yaml(try0.5) and ensurecp_maxin the DB config is not too high for your RAM. - Certbot renewal: Verify the systemd timer is active:
sudo systemctl status certbot.timer. A dry-run test:sudo certbot renew --dry-run. - Clock skew errors in federation: Matrix is sensitive to time drift. Ensure
systemd-timesyncdorchronyis running:timedatectl status.
Frequently asked questions
- How much RAM does a Synapse server realistically need?
- Plan for at least 1 GB for a personal server with a few users. A community server with dozens of active users and several bridged rooms should have 2–4 GB. Synapse caches rooms and users aggressively; you can tune this with the global_factor cache setting.
- Can I use a subdomain for the server but have @user:example.com identities?
- Yes — this is called delegation. Serve a JSON file at https://example.com/.well-known/matrix/server pointing to matrix.example.com:443, or publish an SRV DNS record. You must set this up before creating any accounts, as the server name cannot be changed afterward.
- Is SQLite okay for a small personal homeserver?
- The Synapse developers explicitly discourage SQLite for any production use, including single-user setups. It lacks the write concurrency Synapse needs and can corrupt under load. PostgreSQL is not much harder to set up and is the right choice from day one.
- How do I keep Synapse updated?
- If you installed via the official apt or pacman repository, standard system updates (apt upgrade / pacman -Syu) will upgrade Synapse. Always read the upgrade notes at github.com/element-hq/synapse/releases before major version bumps, as some releases require database migrations.
- What firewall ports does a Matrix server need open?
- Ports 80 (HTTP, for ACME challenges) and 443 (HTTPS, for both clients and federation via well-known delegation) are sufficient when using Nginx as a reverse proxy. Legacy setups expose port 8448 directly for federation, but the well-known method over 443 is preferred and avoids an extra open port.
Related guides
Configure Prometheus Alertmanager
Configure Prometheus Alertmanager with routing trees, receivers, inhibition rules, grouping, Go templates, and PagerDuty/Slack on-call integrations.
Build an Intranet Server on Linux
Set up a complete small-office intranet on one Linux box: Nginx web server, dnsmasq local DNS, Samba file sharing, and a Wiki.js team wiki.
Build an nftables Firewall Script
Build a complete nftables firewall from scratch: tables, chains, sets, default-deny input policy, service allowlisting, and persistent systemd configuration.
Caddy as a Reverse Proxy
Set up Caddy as a reverse proxy with automatic HTTPS, load balancing, WebSocket passthrough, reusable snippets, and header control — no certbot required.