$linuxjunkies
>

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.

AdvancedUbuntuDebianFedoraArch12 min readUpdated June 7, 2026

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)
  • Nginxreverse 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:

  1. Install the bridge package or pip install it into its own virtualenv.
  2. Generate its config: point it at your Synapse homeserver URL and Postgres DB.
  3. Register the bridge with Synapse by adding its registration YAML to app_service_config_files in homeserver.yaml.
  4. 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.server JSON file is accessible and the Nginx location block matches /_matrix.
  • High memory usage: Synapse is memory-hungry. Tune caches.global_factor in homeserver.yaml (try 0.5) and ensure cp_max in 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-timesyncd or chrony is running: timedatectl status.
tested on:Ubuntu 24.04Debian 12Fedora 40Arch rolling

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