In this article we will cover how to setup a minimal installation of a Matrix homeserver using the Synapse implementation. This homeserver will be able to federate to other homeserver to completely participate in the public Matrix network.

TLDR: Look at the bottom of this article, there is a summary if you just want the configuration files.

Introduction

All software in this article is open source and you can follow this article on any linux installation, here we will use a fresh install of Debian 12 running on [Redpill Linpro’s Nordic Cloud (RLNC)}(https://www.redpill-linpro.com/cloud/cloud/platforms/rlnc.html). Synapse’s system requirements specify at least 1GB of ram for large rooms like #matrix:matrix.org, for reference the RLNC virtual machine has 2 VCPUs, 4GB of ram and 50GB of storage.

Method

The Synapse homeserver will store data on the file system and in a Postgres database, traffic will be reversed proxied using Caddy.

Architecture
Architecture

Setup Postgres

First of all we need Postgres.

Execute this in bash.

# Install Postgres
$ sudo apt install postgresql postgresql-client
# Verify installation and that the service is both running and enabled
# Look for "Loaded: loaded (/lib/systemd/system/postgresql.service; enabled; preset: enabled)"
$ sudo systemctl status postgresql
# Verify that you can login with psql as the postgres user
# Enter \q to quit psql
$ sudo -u postgres psql

Setup Synapse

Choose a server name

Choose a server name for your Matrix homeserver, mine will be e012.se, this cannot be changed at a later date. The server name determines the user ids for your users on your homeserver, as an example my user will be @oscar:e012.se. The Synapse documentation recommends not using a specific subdomain, rather opting for delegation. My server is located at matrix.e012.se so I will need to setup delegation, this will be covered further in this article, so don’t worry.

Install Synapse
# Install dependencies
$ sudo apt install -y lsb-release wget apt-transport-https
# Add Matrix's APT repository
$ sudo wget -O /usr/share/keyrings/matrix-org-archive-keyring.gpg https://packages.matrix.org/debian/matrix-org-archive-keyring.gpg
# Verify the fingerprint AAF9AE843A7584B5A3E4CD2BCF45A512DE2DA058
$ gpg /usr/share/keyrings/matrix-org-archive-keyring.gpg
# Add APT repository
$ 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
# Update APT
$ sudo apt update
# Install synapse
# APT will suggest sqlite3, this is not needed since we are using postgres
# Python 3 among other packages will also be installed as dependencies for matrix-synapse-py3
$ sudo apt install matrix-synapse-py3

When installing matrix-synapse-py3 you will receive this prompt, enter your server name.

First installation prompt
First installation prompt

You will then receive this prompt, if installing in a production environment please share usage statistics with the developers. I am running it in a test environment, and I do not want to confuse anyone, so I will opt out. We all help Matrix grow by spreading knowledge as well as statistics… well enough rambling, let’s continue.

Second installation prompt
Second installation prompt

If you’d like, backup your homeserver configuration, now would be a good time.

# Backup your homeserver configuration
$ cp /etc/matrix-synapse/homeserver.yaml ~/homeserver.yaml.bak
Setup database
  1. Set up user synapse with password ee94cb132a2432c6e95d505334736186.
  2. Create database synapse with locale C and encoding UTF8.
  3. Configure Synapse to use Postgres.

Execute this in bash.

# Login as user postgres
$ sudo -u postgres -i
# Create postgres user synapse
# Enter password ee94cb132a2432c6e95d505334736186 (if you'd like)
$ createuser --pwprompt synapse
# Create database synapse for user synapse
# The locale and encoding is important
$ createdb --encoding=UTF8 --locale=C --template=template0 --owner=synapse synapse
# Verify that the databse has been created with the above mentioned locale and encoding
$ psql -l
# Log out of user postgres
$ exit

The database listing should look something like this.

                                             List of databases
   Name    |  Owner   | Encoding | Collate |  Ctype  | ICU Locale | Locale Provider |   Access privileges   
-----------+----------+----------+---------+---------+------------+-----------------+-----------------------
 postgres  | postgres | UTF8     | C.UTF-8 | C.UTF-8 |            | libc            | 
 synapse   | synapse  | UTF8     | C       | C       |            | libc            | 
 template0 | postgres | UTF8     | C.UTF-8 | C.UTF-8 |            | libc            | =c/postgres          +
           |          |          |         |         |            |                 | postgres=CTc/postgres
 template1 | postgres | UTF8     | C.UTF-8 | C.UTF-8 |            | libc            | =c/postgres          +
           |          |          |         |         |            |                 | postgres=CTc/postgres
(4 rows)

Now modify the database block in /etc/matrix-synapse/homeserver.yaml to use Postgres.

database:
  name: psycopg2
  args:
    user: synapse
    password: ee94cb132a2432c6e95d505334736186
    dbname: synapse
    host: localhost
    cp_min: 5
    cp_max: 10
Delegation

Note: You can skip this step if you hostname is the same as your server name.

Delegation is required when serving traffic from another host than the one set as the server name, in my case matrix.e012.se is serving Matrix for server name e012.se. Note that these two domains are located on different servers. By default other homeservers will wrongfully look for my account @oscar:e012.se at e012.se:8448, we cant blame them, although we need to direct them to matrix.e012.se where Synapse is listening for incoming connections. This can be achieved using .well-known delegation, in practise pointing other homeservers to matrix.e012.se:443.

To use .well-known delegation you need to serve two json files, one at /.well-known/matrix/server and one at /.well-known/matrix/client. The server file directs federation.

{
    "m.server": "matrix.e012.se:443"
}

The client file directs users authenticating.

{
    "m.homeserver":
    {
        "base_url": "https://matrix.e012.se"
    }
}

Add the following lines to your homeserver config serve_server_wellknown: true and public_baseurl: "https://matrix.e012.se" located at /etc/matrix-synapse/homeserver.yaml. For reference, the 4 bottom lines in my config now looks like this.

trusted_key_servers:
  - server_name: "matrix.org"
serve_server_wellknown: true
public_baseurl: "https://matrix.e012.se"
Serving .well-known

I am using the Caddy reverse proxy on e012.se, so I will add the delegation to my Caddyfile and restart caddy.

e012.se {
    # Add this
        handle /.well-known/matrix/server {
        respond `{ "m.server": "matrix.e012.se:443"}` 200
        header Content-Type application/json
        header Access-Control-Allow-Origin *
    }

    # Add this
    handle /.well-known/matrix/client {
        respond `{ "m.homeserver": { "base_url": "https://matrix.e012.se" }}` 200
        header Content-Type application/json
        header Access-Control-Allow-Origin *
    }

    # This is an unrelated service
    reverse_proxy localhost:49167
    encode gzip
}
# Verify your .well-known
$ curl https://e012.se/.well-known/matrix/server -v

Does it work?.. Can you see access-control-allow-origin: * and content-type: application/json in the headers? Great, well done!

Secrets

You need to set macaroon_secret_key in /etc/matrix-synapse/homeserver.yaml, first generate a random string (pwgen 32 1) and then set it. This key will be used as a secret in certain encryption algorithms.

You also need to set form_secret, do this as with macaroon_secret_key.

At the end of you homeserver config you should now have this.

macaroon_secret_key: 2d6cb04e7ed21a3275c104c66143169155eca864
form_secret: fae8c35cd966d3067a0fc4dbcbd7fa8f26616c42
User authentication and registration

Users can authenticate in a number of ways, using OpenID Connect or JWT among others. In this article we will opt for a simple user registration without captchas or any security mechanisms. Any internet user can simply create an account and start messaging, you will likely want to limit this in some way, but that’s beyond the scope of this article.

Add the following to your homeserver config.

enable_registration: true
enable_registration_without_verification: true
registration_shared_secret: b10aa181d025852ce5616b950c1b7342f12beaf2

Note that registration_shared_secret is secret, hence it’s name. This secret is used to generate admin accounts, even when enable_registration is false. You should generate a random string for this value, (pwgen 32 1) and never share it with anyone, certainly not put it in a blog post on the internet… oops!

Manually adding a user

You can add users manually using the admin API, we will now utilize this by creating out first admin user. There is a nice script (thanks Matrix team) that does this for you, you can view it here.

# Only python 3 minimal is installed alongside Synapse
# I am installing the python dependencies using APT
$ sudo apt install python3 python3-requests python3-yaml
# Download the script
$ curl -o register_new_matrix_user.py "https://raw.githubusercontent.com/element-hq/synapse/refs/heads/develop/synapse/_scripts/register_new_matrix_user.py"
# Make it executable
$ sudo chmod +x register_new_matrix_user.py
# Run the script
# Enter yes for Make admin
$ python3 register_new_matrix_user.py -c "/etc/matrix-synapse/homeserver.yaml"
Register admin user
Register admin user

Setup Caddy

  1. Install Caddy
  2. Reverse proxy connections to Synapse

Install Caddy using APT.

$ sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
$ curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
$ curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
$ sudo apt update
$ sudo apt install caddy
# Enable caddy to autostart
$ sudo systemctl enable caddy

View your homeserver config at /etc/matrix-synapse/homeserver.yaml, the listener block should look like this.

listeners:
  - port: 8008
    tls: false
    type: http
    x_forwarded: true
    bind_addresses: ['::1', '127.0.0.1']
    resources:
      - names: [client, federation]
        compress: false

Edit your Caddyfile located at /etc/caddy/Caddyfile, we need to reverse proxy the listening connection from Synapse. Your Caddyfile should look like this.

matrix.e012.se {
    reverse_proxy /_matrix/* localhost:8008
    reverse_proxy /_synapse/client/* localhost:8008
}
Optional endpoints

You can also expose:

  • /health which always return 200 OK and does not get logged.
  • /_synapse/admin, for admin access with a token, although the admin endpoint does not need to be exposed so I will leave it as is, for security.

A fully exposed Synapse homeserver will look like this.

matrix.e012.se {
    reverse_proxy /health localhost:8008
    reverse_proxy /_matrix/* localhost:8008
    reverse_proxy /_synapse/client/* localhost:8008
    reverse_proxy /_synapse/admin/* localhost:8008
}

Final setup

Start Caddy and Synapse.

# Restart (or start) Caddy
$ sudo systemctl restart caddy
# Verify that Caddy is running without errors or warnings
$ sudo systemctl status caddy
# Restart (or start) Synapse
$ sudo systemctl restart matrix-synapse.service
# Verify that Synapse is running without errors or warnings
$ sudo systemctl status matrix-synapse.service

You will most likely see that Synapse emits this warning, you can supress this by setting suppress_key_server_warning: true in /etc/matrix-synapse/homeserver.yaml.

Mar 20 13:10:27 matrix.e012.se matrix-synapse[5772]: This server is configured to use 'matrix.org' as its trusted key server via the
Mar 20 13:10:27 matrix.e012.se matrix-synapse[5772]: 'trusted_key_servers' config option. 'matrix.org' is a good choice for a key
Mar 20 13:10:27 matrix.e012.se matrix-synapse[5772]: server since it is long-lived, stable and trusted. However, some admins may
Mar 20 13:10:27 matrix.e012.se matrix-synapse[5772]: wish to use another server for this purpose.
Mar 20 13:10:27 matrix.e012.se matrix-synapse[5772]: To suppress this warning and continue using 'matrix.org', admins should set
Mar 20 13:10:27 matrix.e012.se matrix-synapse[5772]: 'suppress_key_server_warning' to 'true' in homeserver.yaml.

Test

Test Federation

Go to The Matrix Federation Tester and enter your server name, in my case e012.se.

Federation tester results
Federation tester results

If you want to compare, you can look at my json report here.

{
  "WellKnownResult": {
    "m.server": "matrix.e012.se:443",
    "CacheExpiresAt": 0
  },
  "DNSResult": {
    "SRVSkipped": true,
    "SRVCName": "",
    "SRVRecords": null,
    "SRVError": null,
    "Hosts": {
      "matrix.e012.se": {
        "CName": "matrix.e012.se.",
        "Addrs": [
          "87.238.55.105"
        ],
        "Error": null
      }
    },
    "Addrs": [
      "87.238.55.105:443"
    ]
  },
  "ConnectionReports": {
    "87.238.55.105:443": {
      "Certificates": [
        {
          "SubjectCommonName": "matrix.e012.se",
          "IssuerCommonName": "E6",
          "SHA256Fingerprint": "O30FA5vrwAV4SkanmElnsy56gnvNotqjFNAQ2yitxGI",
          "DNSNames": [
            "matrix.e012.se"
          ]
        },
        {
          "SubjectCommonName": "E6",
          "IssuerCommonName": "ISRG Root X1",
          "SHA256Fingerprint": "duniiKr8Djf0OQy/lGqtmX1cHJAbPOUT09j626viq4U",
          "DNSNames": null
        }
      ],
      "Cipher": {
        "Version": "TLS 1.3",
        "CipherSuite": "TLS_AES_128_GCM_SHA256"
      },
      "Checks": {
        "AllChecksOK": true,
        "MatchingServerName": true,
        "FutureValidUntilTS": true,
        "HasEd25519Key": true,
        "AllEd25519ChecksOK": true,
        "Ed25519Checks": {
          "ed25519:a_iIQQ": {
            "ValidEd25519": true,
            "MatchingSignature": true
          }
        },
        "ValidCertificates": true
      },
      "Errors": [],
      "Ed25519VerifyKeys": {
        "ed25519:a_iIQQ": "rgzS2r2YL1/2mMvnZOF/rVA8dX7ueV+l6Ayr8mi8msg"
      },
      "Info": {},
      "Keys": {
        "old_verify_keys": {},
        "server_name": "e012.se",
        "signatures": {
          "e012.se": {
            "ed25519:a_iIQQ": "QyVvx2iHlVax6peGKJmlCB5rJWUS0Vcq8UHtGMsVLnufsrokDO4tsruLCQSfg1jYq4XfzpgDo5cWxsoc8mPkDg"
          }
        },
        "valid_until_ts": 1742564141137,
        "verify_keys": {
          "ed25519:a_iIQQ": {
            "key": "rgzS2r2YL1/2mMvnZOF/rVA8dX7ueV+l6Ayr8mi8msg"
          }
        }
      }
    }
  },
  "ConnectionErrors": {},
  "Version": {
    "name": "Synapse",
    "version": "1.126.0"
  },
  "FederationOK": true
}
Test as a user

I am using the client Cinny but the client does not really matter, the great thing with Matrix is that you can use whatever you like, the standard allows you to be yourself… sorry I am rambling, again, let’s get to it!

  1. Enter your server name e012.se.
  2. Login as @oscar:e012.se
  3. Create another user @test:e012.se with the script mention above in this article.
  4. Login as @test:e012.se (have two clients/web browsers open)
  5. Start a conversation from one account to the other.
Open a converation
Open a converation
Start chatting
Start chatting
Troubleshooting federation

I experienced problems searching for accounts on matrix.org in the client Fractal, but after a night of having Synapse enabled it worked. In Fractal I could not add the user but in Cinny I could. It’s worth noting here that Cinny is a stable client while Fractal is still in beta. My guess is that matrix.org restricts some features until the newly created server is deemed trustworthy.

Search for a user
Search for a user

To troubleshoot you federation, please use The Matrix Federation Tester and start of with sending messages to users on your server, to make sure that the problems are in fact related to federating.

Results

Awesome! We have a homeserver setup and we are participating in the next generation of instant messaging. Matrix is a lot of thing, you can control IoT, make video conference calls, coordinate emergency services and so much more. Since Matrix is an open standard that is extendable you can send any data using it and create whatever you can imagine.

If this article was of any help to you, please send me a message @oscarandersson:matrix.org, I would love to hear about your experience with Synapse.

If you require an enterprise solution or wonder if Matrix is the right choice for you, please contact us at Redpill Linpro. We have offices in Karlstad, Gothenburg, Oslo, Stockholm, Copenhagen, Karlskrona, Aarhus and Trondheim.

TLDR

Alright, I get it, there’s a lot of information, so here are the config files! If you encounter problems scroll to the top and read the article in full. Good luck.

homeserver.yaml

pid_file: "/var/run/matrix-synapse.pid"
listeners:
  - port: 8008
    tls: false
    type: http
    x_forwarded: true
    bind_addresses: ['::1', '127.0.0.1']
    resources:
      - names: [client, federation]
        compress: false
database:
  name: psycopg2
  args:
    user: synapse
    password: ee94cb132a2432c6e95d505334736186
    dbname: synapse
    host: localhost
    cp_min: 5
    cp_max: 10
log_config: "/etc/matrix-synapse/log.yaml"
media_store_path: /var/lib/matrix-synapse/media
signing_key_path: "/etc/matrix-synapse/homeserver.signing.key"
trusted_key_servers:
  - server_name: "matrix.org"
serve_server_wellknown: true
public_baseurl: https://matrix.e012.se/
suppress_key_server_warning: true
macaroon_secret_key: 2d6cb04e7ed21a3275c104c66143169155eca864
form_secret: fae8c35cd966d3067a0fc4dbcbd7fa8f26616c42
enable_registration: true
enable_registration_without_verification: true
registration_shared_secret: b10aa181d025852ce5616b950c1b7342f12beaf2

Caddyfile for Synapse

matrix.e012.se {
    reverse_proxy /_matrix/* localhost:8008
    reverse_proxy /_synapse/client/* localhost:8008
}

Caddyfule for delegation

e012.se {
    handle /.well-known/matrix/server {
        respond `{ "m.server": "matrix.e012.se:443"}` 200
        header Content-Type application/json
        header Access-Control-Allow-Origin *
    }

    handle /.well-known/matrix/client {
        respond `{ "m.homeserver": { "base_url": "https://matrix.e012.se" }}` 200
        header Content-Type application/json
        header Access-Control-Allow-Origin *
    }

    # This is an unrelated service
    reverse_proxy localhost:49167
    encode gzip
}

Sources

Oscar Andersson

Software Development Consultant at Redpill Linpro

Oscar is a software development consultant with broad expertise in development and integration, specializing in C#, MuleSoft, and BizTalk, with a proven ability to quickly adapt to new technologies. He has a personal deep intrest in Open Source, Tor (The Onion Router), privacy and Matrix (both the movies and the message protocol).

Kom igång med Matrix, skapa en Synapse server

I den här artikeln kommer vi att gå igenom hur man sätter upp en minimal installation av en Matrix homeserver med Synapse-implementationen. Denna server kommer att kunna federera med andra servrar, för att fullt ut delta i det offentliga Matrix-nätverket.

TLDR: Ifall du vill köra på snabbspåret, kika längst ner i artikeln, där finns det en sammanfattning som endast innehåller konfigurationsfilerna.

Introduktion

All mjukvara i denna ... [continue reading]

Why automate Ansible

Published on January 14, 2025