Matrix - Synapse

Matrix is a project to create a decentralized chat / messaging world. Probably much more than that, but i think it's the definitive and only future-proof messaging solution.

It looks amazing and it already has so many features that can replace other proprietary solutions. Unfortunately, it is still quite difficult not only to setup, but also to use. One of the big selling point, in my opinion, are the bridges to the other messaging solutions, but the lack of a really viable whatsapp bridge is a bummer. The reason is due to whatsapp limitation, but i am not ready to give up my main whatsapp account on my phone to move it to a VM running on my server.

The Telegram integration works pretty well, even if the initial setup proved to be a bit shaky, it's solid after that phase. I didnt explore any other bridge at the moment.

Overall i am not yet sure if the effort is worthwile, but since i believe in the need for an open internet, i will give Matrix more time.

Tools and Architecture

Matrix is the high-level protocol. To join, or host, a Matrix network you need a Matrix server implementation. There are quite a few but i choose Synapse because it's solid and well-proven.

In addition to the server itself, you need the bridges if you want to connect your matrix instance to other messaging platforms. I only use Telegram and Whatsapp (well, discord maybe, but it's more of a forum for me than an actual messaging tool), and of these only Telegram has a viable bridge.

More info about the bridges can be found here.

To use Matrix properly you do need one dedicated subdomain. It is teorically possible to host on sub-paths, but i do not recomend it as it adds another layer of uncertainity and complexity that you don't want. I will assume you have for your Matrix service. Also note that https is mandatory.


It is possible to install Synapse and the Telegram bridge on bare-metal leveraging Python Virtual Envs, but unless you want to use a SQLite (which won't scale easily) database, you will also need a PostgresSQL installation.

Overall, i prefer to go the container route which proved to be easy enough. Setting up and running properly your Matrix instance is already tricky that adding a bare-metal installation hurdle didnt seems needed.

As usual create one dedicated user, and create a data folder where all the data will be stored:

useradd -d /data/daemons/synapse -m synapse
mkdir /data/synapse
mkdir /data/synapse/data
mkdir /data/synapse/database
mkdir /data/synapse/bridges
mkdir /data/synapse/bridges/telegram
chown synapse:synapse -R /data/synapse

The data folder will contain Synapse configuration and uploaded files. The database folder will contain the PostgreSQL database. The bridges folders will contain each one bridge various files.

Now, take the following docker-compose.yml file and adapt to your needs:

version: '3.7'
    restart: unless-stopped
      - SYNAPSE_CONFIG_PATH=/data/homeserver.yaml
      - /data/synapse/data:/data
      - db
      - 8008:8008/tcp
      - synapse-net

    container_name: mautrix-telegram
    restart: unless-stopped
      - db
    - /data/synapse/bridges/telegram:/data
      - synapse-net
    # Change that password, of course!
      - POSTGRES_USER=synapse
      - POSTGRES_PASSWORD=<<< my db password >>>
      - POSTGRES_INITDB_ARGS=--encoding=UTF-8 --lc-collate=C --lc-ctype=C
      - /data/synapse/database:/var/lib/postgresql/data
      - synapse-net

  synapse-net: {}

Now, as usual, pull the images:

podman compose pull

Generate initial configuration file:

podman compose run --rm -e -e SYNAPSE_REPORT_STATS=yes synapse generate

Edit your /data/synapse/data/homeserver.yaml so that you use PostgresSQL, and double check the paths and server name:

server_name: ""
pid_file: /data/
# NOTE: enable the following two lines ONLY to create users, then REMOVE them!
#enable_registration: true
#enable_registration_without_verification: true
  - port: 8008
    tls: false
    type: http
    x_forwarded: true
      - names: [client, federation]
        compress: false
  name: psycopg2
    user: synapse
    password: <<< my db password >>>
    dbname: synapse
    host: db
    cp_min: 5
    cp_max: 10
log_config: "/data/"
media_store_path: /data/media_store
registration_shared_secret: "<<< random secret >>>"
report_stats: true
macaroon_secret_key: "<<< random key >>>"
form_secret: "<<< random secret >>>"
signing_key_path: "/data/"
  - server_name: ""

At this point, you are ready to run the Matrix.

Set up Telegram Bridge

The main docker-compose.yml above already contains the bridge image, so just run it once to have it create all the files under */data/synapse/bridges/telegram, and then edit the main file: …

Edit the file /data/synapse/bridges/telegram/config.yaml and will in all the required details.

Go to| and generate both an api_id and api_hash. Optionally you can go to the BotFather in Telegram and create a specific bot for you (here)

Some hints on config.yaml:

  • homeserver - address: the URL of your instance (
  • homeserver - domain: the URL of your instance, cleaned (, yes, this is not a typo)
  • appservice - address: the container name of the bridge (so, http://mautrix-telegram:29317)
  • appservice - database: i had to switch to SQLite, as i couldn't create a PostgresSQL new database (so, database: sqlite:/data/mautrix-telegram.db)
  • permissions: change to match your admin user and instance name (ex: full \n '': admin)
  • telegram - api_id the API_ID generated on telegram
  • telegram - api_hash: API_HASH generated on telegram
  • telegram - bot_token: the bot token created on telegram (optional)

Copy the /data/synapse/bridges/telegram/registration.yaml to /data/synapse/data/mautrix-telegram-registration.yaml. If the file is missing, restart the containers to have it generated. If you change anything inside the config.yaml, delete the registration.yaml and restart the containers.

It is very important to note that the registration.yaml file must be correct and copied to the synapse data folder. This is mandatory to let the TelegramBot authenticate in Synapse, and if the bridge does not work, you need to double check that the contents of this file looks correct and match you actual configuration.

Reverse Proxy

You need to run your Matrix behind a reverse proxy so that you can easily add SSL and protect your server. See The Reverse Proxy concept for more details.

This is a simple and effective chat.conf for NGINX:

server {
        listen 8443 ssl; 
        listen 443 ssl; 

        access_log /var/log/nginx/chat.mydomain.com_access_log main;
        error_log /var/log/nginx/chat.mydomain.com_error_log info;

        location ~ ^(/_matrix|/_synapse/client) {
                # note: do not add a path (even a single /) after the port in `proxy_pass`,
                # otherwise nginx will canonicalise the URI and cause signature verification
                # errors.
                proxy_set_header X-Forwarded-For $remote_addr;
                proxy_set_header X-Forwarded-Proto $scheme;
                proxy_set_header Host $host;

                # Nginx by default only allows file uploads up to 1M in size
                # Increase client_max_body_size to match max_upload_size defined in homeserver.yaml
                client_max_body_size 500M;

        # Synapse responses may be chunked, which is an HTTP/1.1 feature.
        proxy_http_version 1.1;
        include com.mydomain/certbot.conf;

Autostart & Running

If you are following my Custom User Services approach, it's pretty easy:

cd /etc/local.d
ln -s 63-synapse--podman.start
ln -s 63-synapse--podman.stop

User Creation

One of the not so intuitive things about Matrix is user creation. I didnt waste too much time on this because i only needed two users (and in general, only a limited numnber of family members), so i went the manual way.

After starting the containers, as user synapse, run:

podman compose run --rm --entrypoint /bin/bash synapse
/usr/local/bin/register_new_matrix_user  -c /data/homeserver.yaml