Build your own secure chat system with Matrix

“This is your last chance.”
After this, there’s no turning back.

You take the green pill — you stay in comfort,
messaging through platforms that decide what’s best for you. You trust their terms, their updates, their eyes.

You take the red pill — and you stay in control.
No gatekeepers. No tracking. No compromises.
You host your own Matrix. You own your conversations.

All I’m offering is the truth. Nothing more.
Your server. Your rules. Your reality.

Take Back Trust. Take Back Control.

When you use WhatsApp, Signal, or Telegram, you’re not just using a tool — you’re depending on someone else’s infrastructure, decisions, and promises.
Even if the app is end-to-end encrypted, you still have to trust:

  • That the encryption works as advertised
  • That metadata isn’t logged
  • That your access won’t be limited, blocked, or monetized

You’re not really free — you’re just tolerated, until you’re not.

Self-hosting changes that.

With Matrix, you can run your own open, federated, encrypted messaging server.
That means:

  • You own your data
  • You decide who can use it
  • You don’t rely on Big Tech’s infrastructure or policies
  • And most importantly: you don’t have to trust — you can verify

This guide walks you through setting up Matrix Synapse on a clean RHEL 9 system (or Rocky, AlmaLinux), using a Python virtual environment for clean separation and PostgreSQL for production-grade data handling.

No Docker. No containers. No cloud lock-in.
Just your machine, your rules.


1. System Requirements & Dependencies

Before you can run your own Matrix server, you need to prepare a clean, Linux-based system with the necessary tools and libraries.
This guide assumes RHEL 9, Rocky Linux 9, or AlmaLinux 9, but it will work on most modern RPM-based distributions.

1.1 Basic Assumptions

  • You have root access to the system (either directly or via sudo)
  • You have a public domain name, e.g. matrix.example.com
  • You are comfortable working in a terminal
  • The server has open ports for:
    • 443/TCP (for HTTPS traffic)
    • 8448/TCP (for Matrix federation)

1.2 Install Required System Packages

Run the following commands to install development tools, Python libraries, and PostgreSQL:

sudo dnf install -y git gcc python3 python3-devel python3-virtualenv libpq-devel libffi-devel gcc openssl-devel postgresql postgresql-server postgresql-contrib

These packages are required to:

  • Build and run Python packages in isolation
  • Use PostgreSQL as a reliable backend database for Synapse
  • Connect Python (Synapse) to the PostgreSQL server via libpq

1.3 Initialize PostgreSQL (first-time setup)

If you’ve just installed PostgreSQL on this system, initialize the database cluster like this:

sudo postgresql-setup --initdb
sudo systemctl enable --now postgresql

This sets up a basic PostgreSQL environment and ensures it starts automatically on boot.


2. Application Setup in a Virtual Environment

To keep the system clean and avoid version conflicts, we install Synapse in a Python virtual environment (venv). This isolates all Python dependencies from the rest of the system and makes the installation easier to maintain, update, and remove later.

2.1 Why use a virtual environment?

Using a virtual environment ensures:

  • Synapse doesn’t interfere with other Python packages on your system
  • You can run different versions of Synapse (or other Python apps) side by side
  • Upgrades are isolated and easily reversible
  • You avoid permission issues or dependency conflicts with system Python

It’s the recommended way to run Python applications on bare metal or VMs.

2.2 Create Directory and Virtual Environment

Create a dedicated directory for your Matrix installation:

mkdir -p /opt/synapse
cd /opt/synapse

Now create the virtual environment:

python3 -m venv venv

Activate the environment:

source venv/bin/activate

You’ll now see the shell prompt prefixed with (venv) — this means the environment is active.

2.3 Upgrade Pip and Install Synapse

First, upgrade pip inside the virtual environment:

pip install --upgrade pip

Then install Synapse with PostgreSQL support:

pip install matrix-synapse[postgres]

If you want to test Synapse without PostgreSQL, it’s also possible to use the built-in SQLite backend — but this is not recommended for production use, especially with more than a few users or if federation is enabled.


3. Generate and Configure the Synapse Instance

With Synapse installed in the virtual environment, the next step is to generate the base configuration and adjust it to your setup — especially to use PostgreSQL as the backend database.

3.1 Generate the Initial Configuration

Still inside your virtual environment (source venv/bin/activate), run:

python -m synapse.app.homeserver \
  --server-name matrix.example.com \
  --config-path homeserver.yaml \
  --generate-config \
  --report-stats=no

Replace matrix.example.com with the actual domain name your Matrix server will use.

This will create:

  • homeserver.yaml — the main configuration file
  • TLS key material (can be ignored if you’re using a reverse proxy later)
  • Signing key files (keep these safe — they identify your server in the federation)

Tip: Always keep a backup of the homeserver.yaml and *.signing.key files. They are essential for server identity and federation.

3.2 Prepare for PostgreSQL Usage

By default, Synapse configures itself to use SQLite. For production use, you should switch to PostgreSQL.

Open homeserver.yaml in your preferred editor and locate the database: section.

Replace it with the following block:

database:
  name: psycopg2
  args:
    user: synapse
    password: YourStrongPassword
    database: synapse
    host: 127.0.0.1
    port: 5432

Don’t forget to replace YourStrongPassword with the actual password you’ll assign to the PostgreSQL user in the next step.

3.3 Optional: Disable Features You Don’t Need Yet

For a clean and simple first setup, you can comment out or disable the following sections in homeserver.yaml:

  • Email registration and password reset (unless you configure SMTP)
  • Auto-join rooms
  • Presence tracking or push notifications (if you’re not using mobile clients)

4. Set Up and Secure the PostgreSQL Database

Synapse supports both SQLite and PostgreSQL — but for anything beyond testing or single-user setups, PostgreSQL is the recommended choice. It offers better performance, reliability, and scalability for federated environments.

This chapter covers creating a dedicated PostgreSQL database and user for Synapse, using locale C for optimal performance and compatibility.

4.1 Connect to PostgreSQL as the Administrative User

Switch to the postgres system user:

sudo -u postgres psql

You’ll now see the PostgreSQL prompt:

postgres=#

4.2 Create the Database with Locale “C”

Run the following SQL statements exactly as shown:

CREATE DATABASE synapse
  ENCODING 'UTF8'
  LC_COLLATE='C'
  LC_CTYPE='C'
  template=template0
  OWNER postgres;

This creates a new database named synapse with clean, fast collation settings.
PostgreSQL needs to restart to pick up locale settings correctly — but this is already handled during creation with template0.

4.3 Create a Dedicated Database User

CREATE USER synapse WITH PASSWORD 'YourStrongPassword';
ALTER ROLE synapse WITH LOGIN;

Use a strong password and remember to insert it into your homeserver.yaml later.

4.4 Assign Permissions

Make the new user the owner of the database:

ALTER DATABASE synapse OWNER TO synapse;
GRANT ALL PRIVILEGES ON DATABASE synapse TO synapse;

Then exit the PostgreSQL shell:

\q

You now have:

  • A production-grade database called synapse
  • A restricted, named user with full access to that database
  • Synapse configured to connect using those credentials

5. Initialize the Synapse Database and Start the Server

At this point, all components are in place:

  • Synapse is installed inside a virtual environment
  • Configuration is prepared and points to PostgreSQL
  • The database and user are ready

Now it’s time to initialize the database schema and launch Synapse for the first time.

5.1 Run the Initialization Step

Still inside your virtual environment (source venv/bin/activate), run:

/opt/synapse/venv/bin/python -m synapse.app.homeserver \
  --config-path /opt/synapse/homeserver.yaml

This will:

  • Connect to the PostgreSQL database
  • Create all necessary tables and internal structures
  • Start Synapse in the foreground

You should see logging output as the homeserver boots up.

If there are any errors here, check file permissions, database credentials, or missing Python packages inside the virtual environment.

5.2 (Optional) Run Synapse in the Background

For now, you can stop the server with Ctrl + C. Later, you’ll want to set up a proper systemd service so Synapse runs automatically on boot and restarts on failure.

We’ll cover that in a later chapter.


6. Create Your First Admin and Regular User

You’re now ready to create user accounts for your homeserver. Matrix Synapse comes with a built-in CLI tool to register users directly — no web UI needed.

Make sure your virtual environment is active and you’re in the Synapse base directory (/opt/synapse in our example).

6.1 Create an Admin User (Morpheus)

cd /opt/synapse
venv/bin/register_new_matrix_user \
  -c homeserver.yaml \
  -u morpheus \
  -p RedPillOnly \
  --admin \
  http://localhost:8008

This creates a user @morpheus:example.com with admin privileges.

6.2 Create a Regular User (Neo)

venv/bin/register_new_matrix_user \
  -c homeserver.yaml \
  -u neo \
  -p WakeUpNeo \
  http://localhost:8008

This registers a standard Matrix user @neo:example.com — ready to log in using a Matrix client like Element.

You can create as many users as you need this way, or enable public registration later (not recommended for open federation servers without moderation in place).


6.3 User Management (Command-Line Basics)

Matrix Synapse doesn’t include a built-in UI for managing users. However, with the CLI tool that comes with Synapse, you can fully manage accounts.

Alle Befehle werden im Synapse-Verzeichnis ausgeführt, die virtuelle Umgebung muss vorher aktiviert werden:

cd /opt/synapse
source venv/bin/activate

Change a User’s Password

To generate a new password hash:

venv/bin/hash_password

Das Tool fragt nach einem neuen Passwort und gibt einen bcrypt-Hash zurück, z. B.:

$2b$12$KFuYjQn7wFxqTn1zKwhMR./qD5sYgR5w9/...

Dieser Hash kann manuell in die Datenbank geschrieben werden. Alternativ können Benutzer ihr Passwort über den Client selbst zurücksetzen – das ist der empfohlene Weg.

Deactivate a User

Ein deaktivierter Account kann sich nicht mehr einloggen, bleibt aber im System erhalten (z. B. für Rauminhalte):

venv/bin/deactivate_matrix_user \
  -c homeserver.yaml \
  -u neo

Damit wird @neo:example.com deaktiviert.

Promote or Demote Admin Rights (optional)

Es gibt keinen offiziellen CLI-Befehl zum Anpassen von Admin-Rechten. Über die Datenbank geht es manuell:

UPDATE users SET admin = TRUE WHERE name = '@neo:example.com';

Zum Entfernen von Admin-Rechten:

UPDATE users SET admin = FALSE WHERE name = '@neo:example.com';

Wichtig: Datenbank vorher sichern. Fehlerhafte Änderungen können den Server beschädigen.

Delete a User Completely

Eine vollständige Löschung eines Benutzers (inkl. Nachrichten, Medien, Events) ist mit Bordmitteln nicht vorgesehen.
Für DSGVO-konforme Löschungen gibt es eigene Tools und APIs, die individuell eingebunden werden müssen.


7. Reverse Proxy and HTTPS Setup (Nginx)

Synapse itself listens only on localhost:8008 by default.
To make your server accessible to the outside world via HTTPS (https://matrix.example.com), a reverse proxy is required — typically Nginx.

This setup also handles TLS termination (SSL certificates) and forwards traffic securely to Synapse.

7.1 Basic Nginx Configuration

Create a new config file under /etc/nginx/conf.d/matrix.conf:

server {
    listen 80;
    server_name matrix.example.com;

    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    listen 443 ssl;
    server_name matrix.example.com;

    # Placeholder – configure TLS properly
    ssl_certificate /etc/letsencrypt/live/matrix.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/matrix.example.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:8008;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Forwarded-Proto https;
    }
}

Replace matrix.example.com with your actual domain.

7.2 TLS Certificates

This guide assumes that a valid TLS certificate is already available.

  • Recommended: Use Let’s Encrypt with a tool like Certbot to generate and renew certificates automatically.
  • You can also use any other SSL certificate provider.

Note: TLS setup is not part of this guide. Only the placeholder paths are shown above.

7.3 Reload Nginx

Once configured:

nginx -t
systemctl reload nginx

Your Synapse server should now be reachable at:

https://matrix.example.com

8. Domain Setup and Optional Federation

Matrix works locally out of the box. But to make it usable for clients (and optionally other servers), a few domain-level configurations are recommended.

This step is optional if you want to use Matrix purely as a private messenger.
If you do want to interact with other servers (federation), the following setup is required.

8.1 Recommended Domain Structure

To keep things clean and flexible, we separate user-visible and server-visible domains:

PurposeExample DomainNotes
Matrix user IDs@user:example.comWhat users see and share
Homeserver base URLmatrix.example.comThe actual backend

This way, your server can live at matrix.example.com, while your users appear to be on example.com.
It also allows you to migrate or rename infrastructure later, without changing user IDs.

8.2 Client Discovery via .well-known

This lets Matrix clients automatically find your homeserver when a user types @neo:example.com.

Create this file:
https://example.com/.well-known/matrix/client

Content:

{
  "m.homeserver": {
    "base_url": "https://matrix.example.com"
  }
}

8.3 Server-to-Server Discovery (Federation)

Only required if you want to federate with the wider Matrix network.

Create this file:
https://example.com/.well-known/matrix/server

Content:

{
  "m.server": "matrix.example.com:443"
}

This tells other Matrix servers where to reach your homeserver.

8.4 DNS and Firewall Checklist

  • A DNS A or AAAA record for matrix.example.com must point to your server.
  • Ports 443/tcp must be open to the public internet if you want federation.
  • Port 8008 should not be exposed to the internet — it’s internal only.

8.5 Test Federation (optional)

You can test if your server is reachable from the outside using:


10. Running Synapse as a systemd Service

To ensure your Matrix Synapse server starts automatically on boot and is easy to manage, create a systemd service unit.

10.1 Create the Service File

Create a file at /etc/systemd/system/synapse.service with the following content:

[Unit]
Description=Matrix Synapse homeserver
After=network.target postgresql.service
Requires=postgresql.service

[Service]
Type=simple
User=root
WorkingDirectory=/opt/synapse
ExecStart=/opt/synapse/venv/bin/python -m synapse.app.homeserver --config-path /opt/synapse/homeserver.yaml
Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target

10.2 Explanation

  • User=root: runs Synapse as root by default (adjust if you want a dedicated user)
  • WorkingDirectory: points to your Synapse install folder
  • ExecStart: runs Synapse using Python from the virtual environment
  • Restart=on-failure: auto-restarts if the process crashes

10.3 Enable and Start the Service

Reload systemd to recognize the new service:

sudo systemctl daemon-reload

Enable Synapse to start on boot:

sudo systemctl enable synapse

Start the service now:

sudo systemctl start synapse

Check status and logs:

sudo systemctl status synapse
sudo journalctl -u synapse -f

10.4 Optional: Run as Dedicated User

For better security, create a dedicated user (e.g., matrix) and adjust the service:

  • Change User=root to User=matrix
  • Ensure file permissions for /opt/synapse are accessible to matrix

Example:

sudo useradd -r -d /opt/synapse -s /sbin/nologin matrix
sudo chown -R matrix:matrix /opt/synapse

11. Config Best Practices with Relevant Settings

  • Disable public user registration enable_registration: false
  • Use a strong shared secret for scripted user creation registration_shared_secret: "a-very-long-random-secret"
  • Bind only to localhost listeners: - port: 8008 bind_addresses: ['127.0.0.1'] tls: false type: http resources: - names: [client, federation]
  • Use PostgreSQL backend database: name: psycopg2 args: user: synapse password: YOUR_PASSWORD database: synapse host: 127.0.0.1 port: 5432
  • Configure admin contact for federation admin_contact: 'mailto:admin@example.com'
  • Limit logging verbosity (set to info or warning) log_level: INFO

12. Backup and Restore

Backup essentials

  • PostgreSQL database
    Regularly back up using pg_dump, for example: pg_dump -U synapse -F c synapse > /backup/synapse-$(date +%F).dump
  • Configuration files
    Backup homeserver.yaml and key files (TLS certificates, signing keys).
  • Media store
    Backup the media directory (e.g., /opt/synapse/media_store).

Restore basics

  • Restore database dump: pg_restore -U synapse -d synapse /backup/synapse-YYYY-MM-DD.dump
  • Restore config files and keys.
  • Verify file permissions.
  • Restart Synapse service.

Regularly test your backups to ensure reliable recovery.


13. Upgrading Synapse

Keeping your Synapse server up-to-date is important for security, bug fixes, and new features. Here’s a straightforward approach:

13.1 Preparation

  • Backup database, config files, media, and keys before upgrading.
  • Read the official Synapse release notes for any breaking changes or special upgrade instructions.

13.2 Upgrade Steps

  1. Activate your virtual environment: source /opt/synapse/venv/bin/activate
  2. Upgrade Synapse via pip: pip install --upgrade matrix-synapse
  3. Run any required database migrations: /opt/synapse/venv/bin/python -m synapse.app.homeserver \ --config-path /opt/synapse/homeserver.yaml \ --upgrade
  4. Restart the Synapse service: sudo systemctl restart synapse

13.3 Verify

  • Check service status: sudo systemctl status synapse
  • Monitor logs for errors: sudo journalctl -u synapse -f

Keep upgrade frequency reasonable; don’t skip major versions without reading upgrade notes.


14. Optional: SRV Record for Federation

In case your Matrix homeserver (matrix.example.com) differs from your user domain (example.com), an SRV record allows other servers to discover your actual endpoint – especially if you’re not using a .well-known configuration.

Example SRV Record for example.com

For DNS providers without dedicated SRV fields (e.g., SchlundTech), use a value format like this:

TypeName / HostValueTTL
SRV_matrix._tcp10 5 443 matrix.example.com.3600

✅ Note: Include the trailing dot after the hostname (matrix.example.com.)

Test Your SRV Record

ℹ️ If you already use a .well-known/matrix/server file, the SRV record is not required but can act as a fallback or future-proofing.


Choose the right pill and take control

You’re not just stepping out of the Matrix — you’re stepping into it, on your terms.

Forget the big messengers who trap you in their system and demand your trust. With your own Synapse server, you hold the keys, set the rules, and decide who you connect with.

So pick the red pill — the one that gives you freedom, control, and sovereignty over your communication.

Welcome to the real Matrix. It’s yours now.