Hosting a Bare Metal Matrix Synapse Server
In this article we cover how to set up your own matrix server based on synapse. We will discuss some concepts and potential pitfalls. While this article will not cover all the details - that's what the official documentation is for - it should serve as a guide to get you to a working setup and knowing your way around decisions.
There are various guides floating around the internet, so why another one? Simply put, because the ones we found while setting up our server were often either not very complete or did not explain why some things were configured the way they were. As we were not really familiar with matrix except having used the client once or twice with a matrix.org account, some more background information would have been helpful. This is why we compiled this document - which is a living document that might get updated in the future.
Last update: 12.06.2026
Assumptions
For this article we will assume the following:
- Root domain:
example.com - Homeserver domain:
matrix.example.com - Both domains hosted on same server named
serverusing IPv4192.168.1.10and IPv6fe80::/10. - Operating System: Debian based Linux (Debian, Ubuntu, Kali, ...).
- Network: The server is reachable on port
443, port80and port8448for TCP connections from the internet for both the IPv4 and the IPv6 addresses.
Key Considerations
Server Software
First, you need to decide which software for your matrix server you are going to use. In this article, we are using Synapse, a matrix homeserver developed in python and maintained by Element under the AGPL-3.0 license. Because Synapse is the reference implementation, it is feature-complete but can be resource-intensive. If you want to use a different one, then this article is likely not for you (but you might write one and publish here on CH405 Labs?).
Make sure to not confuse Synapse with the Element Server Suite which is a bundle of matrix services offered by Element including other useful modules. Setup for the Element Server Suite is not part of this article.
Server Name
This is the most critical decision. The server name you choose (e.g., example.com or matrix.example.com) becomes a permanent part of every user ID (@user:example.com) and room ID created on your server.
Most people want their IDs to look like @me:example.com but host the server at matrix.example.com. This can be achieved via delegation.
Resources
Matrix is a federated protocol. Because Matrix is decentralized, joining a room with 50,000 members (like #matrix:matrix.org) means your server has to process every single message, state change, and profile update for all those users, even if you are the only person from your server in that room. This means that not only the data of users with an account on your server and rooms on your server, but also rooms on other servers that your users join will be held on your server. This is how matrix guarantees, that the room remains available even if the homeserver where the account was initially created on goes down. The consequence of this is that if one of your user joins a high traffic room, you are also hit by this traffic and you will need the storage accordingly. There are configuration settings to limit this, for example by denying users with an account on your server to join so called "complex rooms". Also, you could decide to just host rooms on your server and not serve user accounts but rather have your community host their accounts on a different, public server.
The benefit of allowing the creation of new users on your server is that they will have your domain as part of their user handle, similar to an e-mail address. So for example, @jane_doe:example.com. They are also more likely to search for public rooms on your homeserver as they feel part of this community and you could even go as far as make new users autojoin certain rooms on your server. The downside is that they will increase the traffic and storage requirements on your server depending on how active they are on the matrix network and how many rooms on any server they join. Also, they depend on your homeserver being online to be able to use matrix and thus your server should have very little downtime to not frustrate users.
The benefit of not allowing users on your homeserver is that storage requirements on your server will mostly depend on the room complexity of rooms created on your server. Also, uptime is less critical, as due to federation every user that joined a room will be able to continue talking in this room even if your homeserver is down. Only new users will not be able to search for the room on your homeserver and join it while your server is down (though this can even be circumvented by publishing the room to other homeservers as it then can be found and joined there). The benefit of having this setup is that while you can have rooms and spaces with your domain, for example #lobby:example.com and thus create your community around them, your server has less responsibility. On the downside, as users from different homeservers participate the community feel among them might be less. Also while you can create private, encrypted rooms, the encrypted messages are copied to all homeservers of any participating user. So if you fear that they might be decrypted in the future as breaking ciphers becomes easier, a closed setup with only users on your homeserver might be preferable.
Of course you can mix and match, so for example have your closest community members have an account on your server and have the broader community make accounts with other homeservers and join your rooms. Some estimations on resource usage can be found in the following table.
| Feature | Small Server | Large Server |
|---|---|---|
| Purpose | Personal / Private | Community / Public |
| Local Users | 1 - 50 | 500+ |
| Active Rooms | 10 - 50 | 500+ |
| RAM Usage | 1 GB - 2 GB | 8 GB - 32 GB |
| Database Size | Grows MBs per month | Grows GBs per month |
| CPU cores | 1 vCPU | 2 - 4 vCPU |
Synapse is written in python and it is rather resource hungry, especially when it comes to RAM. However, as long as you are not hosting hundreds of users or hundreds of very active channels you should be all good with 1 or 2 GB of RAM, a CPU with 1 or two cores and some 20GB of storage. Note however, that the storage requirement heavily depends on how many channels you host and what channels your homeusers join, as we discussed above.
When setting up a large server, you also want to be able to use multiple CPUs / CPU cores and you will need to configure workers to distribute load.
Preparations
If you do not have a static ip and / or you are behind a router that does network address translation (NAT) you will need to configure your domains and router such that requests are properly forwarded to your server. We will just cover this here for the level of completeness but not discuss details to set it up, as setups and steps differ significantly depending on your domain registrar and the router setup.
DNS Configuration
Configure your DNS to point towards your router if you use NAT or directly to your server. In our example, let's assume that the server's IPv4 is behind a NAT and the server's IPv6 address is directly reachable from the internet. In that case you would set up your A record to point example.com and matrix.example.com to the router's public IPv4 address and the AAAA record to the server's IPv6 address. Note, that if you use dyndns your provider might not support setting a static IPv6 address, though sometimes if you set it first and then switch to dyndns and only update the IPv4 address this works.
Last but not least, if you intend to allow users to self register on your server you will need to have a mail server configured and set your MX record accordingly. Ideally you can also set your PTR record to avoid your mails going to spam.
Router Configuration
Make sure to forward any incoming traffic on your public interface for example.com and matrix.example.com to your server. In our example where we use dyndns for IPv4 and a fixed IPv6 address pointing to our server we would ensure that the IPv6 address assigned to the server is properly routed and that the IPv4 port forward for ports 80, 443 and 8448 is set. Also we need to ensure that if there is a firewall active on the server we need to make sure that those are not filtered for tcp connections.
Mailserver Configuration
Setting up a mailserver is beyond the purpose of this tutorial. Just be aware that if you want users to be able to self register you will need a mailserver that is able to send out verification e-mails. In theory you could do without, but then you will get many spam accounts registered and your server will more than likely be used to share illegal content.
Initial Setup
When starting the initial setup, we assume that our server is reachable for traffic on TCP ports 80 and 443 on example.com and TCP ports 443 and 8448 on matrix.example.com.
example.com. Also use your own username instead of username. (and for hostnames, etc...)Log in to your server with your standard user account. The user needs to be in the sudoers file so it can assume root privileges. If it isn't, reach out to your server administrator or - if you are the server administrator - either add the user to the sudoers file by running sudo usermod -aG sudo username, replacing username with the target user. Log out and back in for changes to take effect.
Adding the Synapse Repository to Our System and Install Synapse
In order to install the Synapse, we need to set up the proper apt sources to get the latest stable Synapse version. For this we add packages.matrix.org/debian/ to our sources.list and then update apt.
First we need to make sure that we have dependencies installed. We will use the lsb_release command to identify the version of linux we are running. wget will be used to download the keyring with the public keys of the packages.matrix.org/debian/ repository. apt-transport-https enables apt to download via https. You will likely already have those dependencies installed, but it doesn't hurt to make sure, as if they are the latest version this command will simply do nothing.
sudo apt install -y lsb-release wget apt-transport-httpsInstall required packages to add matrix repository
Now we need to download the keyring to verify the packages.matrix.org/debian/ source in the future to our system. We do this through wget directly in the /usr/share/keyrings/ directory.
sudo wget -O /usr/share/keyrings/matrix-org-archive-keyring.gpg https://packages.matrix.org/debian/matrix-org-archive-keyring.gpgDownload the keyring
Next we add the entry for packages.matrix.org/debian/ to our configuration.
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.listAdd the new repository to apt
And last but not least we refresh our system's package index.
sudo apt updateRefresh the system's package index
Now it's time to install Synapse.
sudo apt install matrix-synapseInstall matrix Synapse
During the installation you will be asked for the server name. Enter example.com as the server name. You will also be asked to share anonymous usage statistics. Feel free to do as you please.
Now try to start Synapse.
service matrix-synapse startCheck if the service could be properly started by issuing the following command:
systemctl status matrix-synapseYou should get something like the output below.
● matrix-synapse.service - Synapse Matrix homeserver
Loaded: loaded (/usr/lib/systemd/system/matrix-synapse.service; enabled; preset: enabled)
Active: active (running) since Tue 2026-03-10 06:56:14 UTC; 4 days ago
Main PID: 114296 (python)
Tasks: 44 (limit: 18984)
Memory: 488.6M (peak: 520.8M)
CPU: 3h 14min 3.058s
CGroup: /system.slice/matrix-synapse.service
└─114296 /opt/venvs/matrix-synapse/bin/python -m synapse.app.homeserver --config-path=/etc/matrix-synapse/>
Mar 10 06:56:11 examplehost systemd[1]: Starting matrix-synapse.service - Synapse Matrix homeserver...
Mar 10 06:56:11 examplehost matrix-synapse[1923]: This server is configured to use 'matrix.org' as its trusted key server via the
Mar 10 06:56:11 examplehost matrix-synapse[1923]: 'trusted_key_servers' config option. 'matrix.org' is a good choice for a key
Mar 10 06:56:11 examplehost matrix-synapse[1923]: server since it is long-lived, stable and trusted. However, some admins may
Mar 10 06:56:11 examplehost matrix-synapse[1923]: wish to use another server for this purpose.
Mar 10 06:56:11 examplehost matrix-synapse[1923]: To suppress this warning and continue using 'matrix.org', admins should set
Mar 10 06:56:11 examplehost matrix-synapse[1923]: 'suppress_key_server_warning' to 'true' in homeserver.yaml.
Mar 10 06:56:11 examplehost matrix-synapse[1923]: --------------------------------------------------------------------------------
Mar 10 06:56:12 examplehost matrix-synapse[1923]: Config is missing macaroon_secret_key
Mar 10 06:56:14 examplehost systemd[1]: Started matrix-synapse.service - - Synapse Matrix homeserver.systemctl output for successfully started Synapse server
The server should be started, so you can also check what ports it is listening on using the following command.
sudo ss -antpl | grep pythonThis should produce an output similar to the one below.
LISTEN 0 50 127.0.0.1:8008 0.0.0.0:* users:(("python",pid=114296,fd=17))
LISTEN 0 50 [::1]:8008 [::]:* users:(("python",pid=114296,fd=16))This output means, that the process is listening for connections on the local host (IPv4 and IPv6) on port 8008.
Now enable the service to be also started after a reboot of the system.
systemctl enable matrix-synapseAs we will need to configure Synapse properly, we can now stop the service again.
service matrix-synapse stopConfigure NGINX as Reverse Proxy
Our initial configuration above lets Synapse listen for connections on the loopback interface for the localhost for unencrypted connections on port 8008. It does not listen to external interfaces. This is the way we like to keep it. External connections shall be handled by a reverse proxy that among other things lets our reverse proxy handle and terminate encryption and forward communication to Synapse listening on the loopback interface. This gives us more control about incoming connections.
As a reverse proxy we will use NGINX. If you are using another reverse proxy you can still read this chapter to understand what you need to set in order to make it work. Also, if you already host a website at example.com you will need to adjust the existing configuration rather than start with a new one. However, in this case you will likely be knowledgeable enough to adapt the following as needed.
If you haven't installed nginx yet, install it using apt.
sudo apt install nginxQuickly test nginx by starting it and checking its status:
sudo systemctl start nginx
sudo systemctl status nginxThe output should look something like this:
● nginx.service - nginx - high performance web server
Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; preset: enabled)
Active: active (running) since Tue 2026-04-07 09:01:00 UTC; 3h 25min ago
Docs: https://nginx.org/en/docs/
Process: 222537 ExecStart=/usr/sbin/nginx -c /etc/nginx/nginx.conf (code=exited, status=0/SUCCESS)
Main PID: 222538 (nginx)
Tasks: 5 (limit: 18982)
Memory: 11.1M (peak: 15.9M)
CPU: 1min 19.014s
CGroup: /system.slice/nginx.service
├─222538 "nginx: master process /usr/sbin/nginx -c /etc/nginx/nginx.conf"
├─222539 "nginx: worker process"
├─222540 "nginx: worker process"
├─222541 "nginx: worker process"
└─222542 "nginx: worker process"
Apr 07 09:01:00 example systemd[1]: Starting nginx.service - nginx - high performance web server...
Apr 07 09:01:00 example systemd[1]: Started nginx.service - nginx - high performance web server.Stop nginx for now so we can configure it properly.
sudo systemctl stop nginxWe need two site configurations. One for example.com and one for matrix.example.com. For both of them we will use one configuration file that covers http and https configuration for the respective (sub-) domain.
Lets start with the configuration for example.com by creating the file /etc/nginx/sites-available/example.com.conf. We need to listen on http port 80 and https port 443. For http, we will simply forward requests to https and be done with it. For the https connection we need to serve two locations, example.com/.well-known/matrix/client and example.com/.well-known/matrix/server. While not needed for a minimal setup, it is also good practice to serve a third location example.com/.well-known/matrix/support which provides information on how to get support from the administrators.
At this point we trust you to be capable of doing this on your own. Just make sure to include the
location statements and adjust the client_max_body_size. If you only operate matrix at your main domain, your configuration should look like the one below:
server {
listen 443 ssl default_server;
listen [::]:443 ssl default_server;
http2 on;
server_name example.com;
root /var/www/example.com/system/nginx-root; # Used for acme.sh SSL verification (https://acme.sh)
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
include /etc/nginx/snippets/ssl-params.conf;
# These sections are required for client and federation discovery
# (AKA: Client Well-Known URI)
location /.well-known/matrix/client {
return 200 '{"m.homeserver": {"base_url": "https://matrix.example.com"}}';
default_type application/json;
add_header Access-Control-Allow-Origin *;
}
location /.well-known/matrix/server {
return 200 '{"m.server": "matrix.example.com:443"}';
default_type application/json;
add_header Access-Control-Allow-Origin *;
}
# The Support location is optional, but good practice.
location /.well-known/matrix/support {
return 200 '
{
"contacts": [
{
"email_address": "admin@example.com",
"matrix_id": "@admin:example.com",
"role": "m.role.admin"
},
{
"email_address": "security@example.com",
"matrix_id": "@security:example.com",
"role": "m.role.security"
}
]
}';
}
}
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name example.com;
return 301 https://$host$request_uri;
}NGINX configuration for example.com
Next we set up a configuration for our matrix.example.com subdomain. For this we first generate the file /etc/nginx/sites-available/matrix.example.com.conf.
We will add two server sections. One for http traffic on port 80 and one for https traffic on port 443. As before, the port 80 is merely for completeness and just forwards to port 443:
server {
server_name matrix.example.com;
listen 80;
listen [::]:80;
return 301 https://$host$request_uri;
}In matrix, usually client traffic goes to port 443 and server traffic to port 8448. We therefore will listen to those two ports in our server section to handle ssl traffic. We need to do this for our IPv4 and IPv6 traffic. This is achieved by adding four listen directives:
server {
server_name matrix.example.com;
listen 8448 ssl;
listen [::]:8448 ssl ipv6only=on;
listen 443 ssl; # managed by Certbot
listen [::]:443 ssl ipv6only=on;
}Note, that the option ipv6only=on will only allow IPv6 traffic to be handled, which is recommended for a dual stack setup and having separate listen directives make the dual stack setup explicit. Newer versions of nginx make this the default, so you could also omit it.
Lets also enable http2 while we are at it:
http2 on;Next, we need to forward any requests from matrix clients and servers to our synapse server that listens on our local port 8008. For this we add a location directive.
location ~* ^(\/_matrix|\/_synapse|\/_client) {
proxy_pass http://localhost: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;
}The location matches any request to a path that contains _matrix, _synapse or _client. Any request by a matrix server or client will meet this requirement. The proxy_pass directive forwards those requests to our localhost on port 8008. To make sure that our local host does see the origin of the request and not our local server, we also need to set the X-Forwarded-For header to the $remote_addr. We also need to set the used protocol using the de-facto standard header X-Forwarded-Proto. Last but not least we need to set the name of the host that received the request (matrix.example.com in our case) by setting the Host header to $host.
One setting that is important, is to set maximal body size a request can have. As we will allow users to upload media, the max body size must match the size of the media that we will allow for upload. The size of the media we allow will be set in the synapse configuration, however, if the reverse proxy has a smaller setting, the reverse proxy will not forward the request (good luck troubleshooting this). Synapse sets a default of 50 megabytes, so we will go with this by setting client_max_body_size 50M.
Currently, your configuration should look like this:
server {
server_name matrix.example.com;
listen 8448 ssl;
listen [::]:8448 ssl;
listen 443 ssl; # managed by Certbot
listen [::]:443 ssl ipv6only=on;
http2 on;
location ~* ^(\/_matrix|\/_synapse|\/_client) {
proxy_pass http://localhost: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;
}
}
server {
server_name matrix.example.com;
listen 80;
listen [::]:80;
return 301 https://$host$request_uri;
}It's time to add our ssl certificates. We will use certbot through snapd (you can find the installation instructions on the certbot.eff.org site if you get stuck in the next steps). To continue make sure the package certbot is NOT installed using apt packet manager.
sudo apt remove certbotOn newer versions of ubuntu, snapd is already installed. If snapd is not yet installed, then install it and make sure its up to date:
sudo apt install snapd -y
sudo snap install core
sudo snap refresh coreNow let's install certbot:
sudo snap install --classic certbotLink certbot to /usr/local/bin to ensure we can run certbot from our command line without giving the full path:
sudo ln -s /snap/bin/certbot /usr/local/bin/certbotNow obtain the certificates for both domains. Certbot needs to answer the ACME challenge over HTTP, so before our sites are enabled the easiest way is the standalone mode, in which certbot briefly runs its own web server (make sure nothing else is listening on port 80):
sudo certbot certonly --standalone -d example.com
sudo certbot certonly --standalone -d matrix.example.comGenerate the certificates with certbot
The certificates and keys are placed under /etc/letsencrypt/live/example.com/ and /etc/letsencrypt/live/matrix.example.com/ respectively, which is exactly where our nginx configuration expects them. Certbot also installs a systemd timer that takes care of renewing the certificates automatically. You can verify that the renewal works with a dry run:
sudo certbot renew --dry-runIn the end you should end up with a configuration similar to the one below:
server {
server_name matrix.example.com;
listen 8448 ssl;
listen [::]:8448 ssl;
listen 443 ssl; # managed by Certbot
listen [::]:443 ssl ipv6only=on; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/matrix.example.com/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/matrix.example.com/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
http2 on;
location ~* ^(\/_matrix|\/_synapse|\/_client) {
proxy_pass http://localhost: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;
}
}
server {
server_name matrix.example.com;
listen 80;
listen [::]:80;
return 301 https://$host$request_uri;
}NGINX configuration for matrix.example.com
Configuring Synapse
The configuration files reside in /etc/matrix-synapse. Something important is that when installing with APT, configurations in /etc/matrix-synapse/conf.d/ override your configuration without editing the main configuration file at /etc/matrix-synapse/homeserver.yaml. If this does not make sense to you right now, don't worry. It will only become important later, as for example the server_name is set in /etc/matrix-synapse/conf.d/server_name.yaml and thus the setting in the main configuration file will be overwritten.
Now we will start with setting up a minimal configuration for Synapse. The main configuration can be found in the file /etc/matrix-synapse/homeserver.yaml.
At first, we will deactivate registration for matrix clients and set a shared secret for user registration. Lets start by creating a unique shared secret by issuing the following command:
cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1This should output something like the value below.
ZRGz8JGZaIbL0m3MvI8RjsQo2RL0Wt5PMake sure to generate your own and never reveal it to anyone. Next, we make sure to set the following parameters in the homeserver.yaml configuration (you find the complete configuration below):
# Registration
#
enable_registration: false
registration_shared_secret: "ZRGz8JGZaIbL0m3MvI8RjsQo2RL0Wt5P"The public_baseurl is the address at which your homeserver's client-server API is reachable from the outside world. Because we are using delegation, the server_name (example.com) and the host that actually serves Matrix traffic (matrix.example.com) are not the same, so we have to tell Synapse explicitly where it can be reached. Synapse uses this value whenever it needs to build an absolute URL – for example in account-management and password-reset links, SSO redirects or identity-server requests – so if it does not match the URL exposed by your reverse proxy those features will silently break. Set it to the HTTPS URL of your Matrix subdomain, matching what nginx terminates TLS for.
public_baseurl: "https://matrix.example.com"The listeners block defines the network sockets Synapse opens. We configure a single HTTP listener on port 8008 that is bound only to the loopback addresses 127.0.0.1 and ::1. This is deliberate: nginx terminates TLS and reverse-proxies requests to Synapse, so Synapse itself never needs to be reachable directly from the network and does not have to handle certificates (tls: false). The same listener serves both the client and federation resources, which is why a single port is enough for our setup. Finally, x_forwarded: true tells Synapse to trust the X-Forwarded-For header set by nginx, so that logging and rate-limiting see the real client IP rather than that of the proxy.
listeners:
- bind_addresses:
- ::1
- 127.0.0.1
port: 8008
resources:
- compress: false
names:
- client
- federation
tls: false
type: http
x_forwarded: trueSynapse can keep its data either in SQLite or in PostgreSQL. SQLite (name: sqlite3) is convenient to get started with because it needs no separate database server and lives in a single file, which is exactly what we want for a first test. It does not cope well with concurrent access, however, and cannot be used together with worker processes, so it is not suitable for anything beyond experimentation. For a production server the official recommendation is PostgreSQL (the psycopg2 driver), where you point Synapse at a database with its own user, password and host and tune the connection pool through cp_min and cp_max. We therefore start on SQLite here and migrate to PostgreSQL later in this guide.
# Storage
#
# Make sure if you use a reverse proxy to also set the according body size in its configuration.
database:
name: sqlite3
args:
database: /var/lib/matrix-synapse/homeserver.db
media_store_path: /var/lib/matrix-synapse/media
max_upload_size: 50MRather than configuring logging inline, Synapse keeps its logging setup in a separate file referenced by log_config – a standard Python logging configuration that defines log levels, handlers and rotation. By default it writes to /var/log/matrix-synapse/homeserver.log at INFO level. When you are chasing a problem you can temporarily raise the root logger to DEBUG to get far more detail, but remember to lower it again afterwards: debug logs are verbose, grow quickly and can contain sensitive information, so INFO or WARNING is the right level for normal operation.
# Logging
#
log_config: "/etc/matrix-synapse/log.yaml"The crypto settings concern the cryptographic identity of your server. signing_key_path points to the ed25519 key that Synapse generates on first start and uses to sign every event and federation request – it is effectively your server's identity towards the rest of the federation, so keep it secret and back it up. If it is lost or changed, other servers will no longer trust your events and you would, from the network's point of view, become a brand-new server. trusted_key_servers lists the servers Synapse may query to look up the signing keys of other homeservers; we use matrix.org as a key notary, which is the common default. Because trusting a key server in principle allows it to vouch for other servers' keys, Synapse normally prints a warning about this – suppress_key_server_warning: true simply acknowledges that we are knowingly trusting matrix.org and silences that message.
# Crypto
#
signing_key_path: "/etc/matrix-synapse/homeserver.signing.key"
trusted_key_servers:
- server_name: "matrix.org"
suppress_key_server_warning: trueFinally, with enable_registration: false we switch off open registration, so visitors cannot create accounts on your homeserver on their own. This keeps the server closed and protects it against spam and abuse, which is what you want for a private or small community server. As the administrator you can still create accounts from the command line with the register_new_matrix_user tool, which authenticates using the registration_shared_secret we generated above. Should you later decide to open the server to the public, you can revisit this setting – ideally together with email verification or a CAPTCHA to keep automated sign-ups out.
# Configuration file for Synapse.
#
# This is a YAML file: see [1] for a quick introduction. Note in particular
# that *indentation is important*: all the elements of a list or dictionary
# should have the same indentation.
#
# [1] https://docs.ansible.com/ansible/latest/reference_appendices/YAMLSyntax.html
#
# For more information on how to configure Synapse, including a complete accounting of
# each option, go to docs/usage/configuration/config_documentation.md or
# https://element-hq.github.io/synapse/latest/usage/configuration/config_documentation.html
#
# This is set in /etc/matrix-synapse/conf.d/server_name.yaml for Debian installations.
# server_name: "SERVERNAME"
#
pid_file: "/var/run/matrix-synapse.pid"
public_baseurl: "https://matrix.example.com"
listeners:
- bind_addresses:
- ::1
- 127.0.0.1
port: 8008
resources:
- compress: false
names:
- client
- federation
tls: false
type: http
x_forwarded: true
# Storage
#
# Make sure if you use a reverse proxy to also set the according body size in its configuration.
database:
name: sqlite3
args:
database: /var/lib/matrix-synapse/homeserver.db
media_store_path: /var/lib/matrix-synapse/media
max_upload_size: 50M
# Logging
#
log_config: "/etc/matrix-synapse/log.yaml"
# Crypto
#
signing_key_path: "/etc/matrix-synapse/homeserver.signing.key"
trusted_key_servers:
- server_name: "matrix.org"
suppress_key_server_warning: true
# Registration
#
enable_registration: false
registration_shared_secret: "ZRGz8JGZaIbL0m3MvI8RjsQo2RL0Wt5P"homeserver.yaml configuration for first tests
Testing the Configuration
Now everything is in place to fire up the whole stack for the first time. If a firewall is active on your server, first make sure it does not block our traffic. With ufw you would allow HTTP, HTTPS and the federation port 8448:
sudo ufw allow 8448
sudo ufw allow http
sudo ufw allow httpsNext, enable the two nginx sites by linking them to sites-enabled and let nginx validate the configuration files:
sudo ln -s /etc/nginx/sites-available/example.com.conf /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/matrix.example.com.conf /etc/nginx/sites-enabled/
sudo nginx -tEnable the sites and test the nginx configuration
If the configuration test passes, start nginx and Synapse:
sudo systemctl start nginx
sudo systemctl start matrix-synapseFirst we verify that the well-known files for delegation are served correctly on the root domain:
curl https://example.com/.well-known/matrix/client
curl https://example.com/.well-known/matrix/serverTest the well-known delegation
Both requests should return the JSON documents we configured in nginx. Next, check that the client API is reachable through the reverse proxy on port 443 and that the federation API answers on port 8448:
curl https://matrix.example.com/_matrix/client/versions
curl https://matrix.example.com:8448/_matrix/federation/v1/versionTest the client and federation APIs
If both calls return JSON, the reverse proxy is forwarding requests to Synapse correctly. To verify that federation also works from the outside, point your browser to the Matrix Federation Tester at https://federationtester.matrix.org and enter your server name (example.com). The tester checks DNS, certificates, delegation and the federation API, and tells you exactly which part fails if something is misconfigured.
Time to create your first user. As we disabled open registration, we use the registration shared secret we configured earlier. The register_new_matrix_user tool that ships with Synapse reads the secret from the configuration file. The -a flag makes the new user a server administrator:
register_new_matrix_user -u admin -a -c /etc/matrix-synapse/homeserver.yaml http://localhost:8008Register the first (admin) user
Finally, do an end to end test with a real client. Open https://app.element.io (or any other Matrix client), choose sign in, set the homeserver to example.com and log in with the user you just created. Thanks to the well-known delegation, the client discovers https://matrix.example.com automatically and your user ID is @admin:example.com. If you can send a message to yourself and join a federated room such as #matrix:matrix.org, your setup works.
Making the Setup Production Ready
In order to make the setup production ready you want to migrate the SQLite database to PostgreSQL. Optionally, if you want to enable users to register on your server you want to enable user registration. If you are setting up a large server, you also want to configure workers to share the load across multiple cores of your CPUs (worker configuration is beyond the scope of this article; see the official Synapse documentation). Also, you might want to enable video and voice calls.
Migrating SQLite to PostgreSQL
Synapse's default SQLite database is perfectly fine for testing, but it does not cope well with the write load of a federated homeserver. For production use, the official documentation strongly recommends PostgreSQL. The following steps port the existing SQLite database to PostgreSQL. If you are starting from scratch, you can also configure PostgreSQL right away and skip the porting. Keep in mind that the PostgreSQL database has to be created with the C locale (LC_COLLATE and LC_CTYPE set to "C") for that migration to work
First, install PostgreSQL:
sudo apt install postgresqlThen we create a dedicated database user and a database for Synapse. Switch to the postgres system user:
su - postgres
# Or, if your system uses sudo to get administrative rights
sudo -u postgres bashCreate the user - you will be prompted for a password, which we will need again in the configuration below - and then the database. Synapse requires the C locale and UTF8 encoding, so the database must be created from template0 with exactly these options:
# this will prompt for a password for the new user
createuser --pwprompt synapse_user
createdb --encoding=UTF8 --locale=C --template=template0 --owner=synapse_user synapseType exit to return to your normal user. Now we prepare a configuration for the porting script. Copy the current configuration to a new file:
sudo cp /etc/matrix-synapse/homeserver.yaml /etc/matrix-synapse/homeserver-postgres.yamlIn the copy, replace the database section with the PostgreSQL settings, filling in the user, password, database name and host (localhost in our setup) from above:
database:
name: psycopg2
args:
user: <user>
password: <pass>
dbname: <db>
host: <host>
cp_min: 5
cp_max: 10Optionally, you can add TCP keepalive settings to the database arguments:
database:
args:
# ... as above
# seconds of inactivity after which TCP should send a keepalive message to the server
keepalives_idle: 10
# the number of seconds after which a TCP keepalive message that is not
# acknowledged by the server should be retransmitted
keepalives_interval: 10
# the number of TCP keepalives that can be lost before the client's connection
# to the server is considered dead
keepalives_count: 3There is one Debian specific pitfall: as discussed earlier, the server_name is set in /etc/matrix-synapse/conf.d/server_name.yaml and not in the main configuration file. The porting script only reads the file we pass to it, so we have to add the server_name to homeserver-postgres.yaml temporarily. It is only needed for the porting and the file can be removed once the migration is done.
server_name: example.comNow stop Synapse and take a snapshot of the SQLite database so we port from a consistent state. You can start Synapse again afterwards while the port runs against the snapshot - but be aware that everything that happens between the snapshot and the final switch will be lost. For a small server it is simpler to just keep Synapse stopped during the migration.
service matrix-synapse stop
cp homeserver.db homeserver.db.snapshot
service matrix-synapse start
Run the porting script as the matrix-synapse user. Depending on the size of your database this can take a while:
sudo -u matrix-synapse synapse_port_db \
--sqlite-database /var/lib/matrix-synapse/homeserver.db.snapshot \
--postgres-config /etc/matrix-synapse/homeserver-postgres.yamlPort the SQLite database to PostgreSQL
Once the script finishes, stop Synapse if it is still running and replace the database section in the live configuration /etc/matrix-synapse/homeserver.yaml with the same PostgreSQL settings as in homeserver-postgres.yaml. Then start Synapse again and check its status as before. Also make sure that all files and directories under /var/lib/matrix-synapse are owned by the matrix-synapse user - a directory accidentally created as root will cause hard to interpret errors:
root@example:/var/lib/matrix-synapse# ls -al
total 526968
drwxr-xr-x 4 matrix-synapse matrix-synapse 4096 Mar 5 00:13 .
drwxr-xr-x 56 root root 4096 Feb 27 21:45 ..
-rw-r--r-- 1 matrix-synapse matrix-synapse 269901824 Mar 5 00:13 homeserver.db
-rw-r--r-- 1 root root 269688832 Mar 4 23:52 homeserver.db.snapshot
drwxr-xr-x 6 matrix-synapse matrix-synapse 4096 Feb 26 18:45 media
drwxr-xr-x 2 root root 4096 Mar 4 23:40 media_store
root@example:/var/lib/matrix-synapse# chown matrix-synapse:matrix-synapse media_store
root@example:/var/lib/matrix-synapse# ls -al
total 526968
drwxr-xr-x 4 matrix-synapse matrix-synapse 4096 Mar 5 00:13 .
drwxr-xr-x 56 root root 4096 Feb 27 21:45 ..
-rw-r--r-- 1 matrix-synapse matrix-synapse 269901824 Mar 5 00:13 homeserver.db
-rw-r--r-- 1 root root 269688832 Mar 4 23:52 homeserver.db.snapshot
drwxr-xr-x 6 matrix-synapse matrix-synapse 4096 Feb 26 18:45 media
drwxr-xr-x 2 matrix-synapse matrix-synapse 4096 Mar 4 23:40 media_storeTroubleshooting: Inconsistent Stream Sequences
Depending on the Synapse version, the porting script may abort with an IncorrectDatabaseSetup error like the following:
2026-03-05 00:14:00,012 - synapse_port_db - 991 - ERROR -
Traceback (most recent call last):
File "/opt/venvs/matrix-synapse/lib/python3.12/site-packages/synapse/_scripts/synapse_port_db.py", line 776, in run
self.postgres_store = self.build_db_store(
^^^^^^^^^^^^^^^^^^^^
File "/opt/venvs/matrix-synapse/lib/python3.12/site-packages/synapse/_scripts/synapse_port_db.py", line 672, in build_db_store
store = Store(
^^^^^^
File "/opt/venvs/matrix-synapse/lib/python3.12/site-packages/synapse/storage/databases/main/event_push_actions.py", line 1890, in __init__
super().__init__(database, db_conn, hs)
File "/opt/venvs/matrix-synapse/lib/python3.12/site-packages/synapse/storage/databases/main/event_push_actions.py", line 261, in __init__
super().__init__(database, db_conn, hs)
File "/opt/venvs/matrix-synapse/lib/python3.12/site-packages/synapse/storage/databases/main/client_ips.py", line 89, in __init__
super().__init__(database, db_conn, hs)
File "/opt/venvs/matrix-synapse/lib/python3.12/site-packages/synapse/storage/databases/main/deviceinbox.py", line 1080, in __init__
super().__init__(database, db_conn, hs)
File "/opt/venvs/matrix-synapse/lib/python3.12/site-packages/synapse/storage/databases/main/devices.py", line 2414, in __init__
super().__init__(database, db_conn, hs)
File "/opt/venvs/matrix-synapse/lib/python3.12/site-packages/synapse/storage/databases/main/events_bg_updates.py", line 122, in __init__
super().__init__(database, db_conn, hs)
File "/opt/venvs/matrix-synapse/lib/python3.12/site-packages/synapse/storage/databases/main/media_repository.py", line 115, in __init__
super().__init__(database, db_conn, hs)
File "/opt/venvs/matrix-synapse/lib/python3.12/site-packages/synapse/storage/databases/main/registration.py", line 2566, in __init__
super().__init__(database, db_conn, hs)
File "/opt/venvs/matrix-synapse/lib/python3.12/site-packages/synapse/storage/databases/main/registration.py", line 184, in __init__
super().__init__(database, db_conn, hs)
File "/opt/venvs/matrix-synapse/lib/python3.12/site-packages/synapse/storage/databases/main/room.py", line 2022, in __init__
super().__init__(database, db_conn, hs)
File "/opt/venvs/matrix-synapse/lib/python3.12/site-packages/synapse/storage/databases/main/room.py", line 152, in __init__
self._un_partial_stated_rooms_stream_id_gen = MultiWriterIdGenerator(
^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/venvs/matrix-synapse/lib/python3.12/site-packages/synapse/storage/util/id_generators.py", line 307, in __init__
self._sequence_gen.check_consistency(
File "/opt/venvs/matrix-synapse/lib/python3.12/site-packages/synapse/storage/util/sequence.py", line 192, in check_consistency
raise IncorrectDatabaseSetup(
synapse.storage.engines._base.IncorrectDatabaseSetup:
Postgres sequence 'un_partial_stated_room_stream_sequence' is inconsistent with associated stream position
of 'un_partial_stated_room_stream' in the 'stream_positions' table.
This is likely a programming error and should be reported at
https://github.com/matrix-org/synapse.
A temporary workaround to fix this error is to shut down Synapse (including
any and all workers) and run the following SQL:
DELETE FROM stream_positions WHERE stream_name = 'un_partial_stated_room_stream';
This will need to be done every time the server is restarted.Or Synapse refuses to start after the migration with a similar error in the journal, just for a different stream:
root@example:/etc/matrix-synapse# service matrix-synapse start
Job for matrix-synapse.service failed because the control process exited with error code.
See "systemctl status matrix-synapse.service" and "journalctl -xeu matrix-synapse.service" for details.
root@example:/etc/matrix-synapse# journalctl -xeu matrix-synapse.service
Mar 05 00:15:35 example matrix-synapse[59337]: super().__init__(database, db_conn, hs)
Mar 05 00:15:35 example matrix-synapse[59337]: File "/opt/venvs/matrix-synapse/lib/python3.12/site-packages/synapse/storage/databases/main/thread_subscriptions.py", line 64, in __init__
Mar 05 00:15:35 example matrix-synapse[59337]: super().__init__(database, db_conn, hs)
Mar 05 00:15:35 example matrix-synapse[59337]: File "/opt/venvs/matrix-synapse/lib/python3.12/site-packages/synapse/storage/databases/main/sticky_events.py", line 68, in __init__
Mar 05 00:15:35 example matrix-synapse[59337]: super().__init__(database, db_conn, hs)
Mar 05 00:15:35 example matrix-synapse[59337]: File "/opt/venvs/matrix-synapse/lib/python3.12/site-packages/synapse/storage/databases/main/state.py", line 111, in __init__
Mar 05 00:15:35 example matrix-synapse[59337]: super().__init__(database, db_conn, hs)
Mar 05 00:15:35 example matrix-synapse[59337]: File "/opt/venvs/matrix-synapse/lib/python3.12/site-packages/synapse/storage/databases/main/state.py", line 758, in __init__
Mar 05 00:15:35 example matrix-synapse[59337]: super().__init__(database, db_conn, hs)
Mar 05 00:15:35 example matrix-synapse[59337]: File "/opt/venvs/matrix-synapse/lib/python3.12/site-packages/synapse/storage/databases/main/push_rule.py", line 135, in __init__
Mar 05 00:15:35 example matrix-synapse[59337]: super().__init__(database, db_conn, hs)
Mar 05 00:15:35 example matrix-synapse[59337]: File "/opt/venvs/matrix-synapse/lib/python3.12/site-packages/synapse/storage/databases/main/appservice.py", line 107, in __init__
Mar 05 00:15:35 example matrix-synapse[59337]: super().__init__(database, db_conn, hs)
Mar 05 00:15:35 example matrix-synapse[59337]: File "/opt/venvs/matrix-synapse/lib/python3.12/site-packages/synapse/storage/databases/main/pusher.py", line 78, in __init__
Mar 05 00:15:35 example matrix-synapse[59337]: super().__init__(database, db_conn, hs)
Mar 05 00:15:35 example matrix-synapse[59337]: File "/opt/venvs/matrix-synapse/lib/python3.12/site-packages/synapse/storage/databases/main/roommember.py", line 103, in __init__
Mar 05 00:15:35 example matrix-synapse[59337]: super().__init__(database, db_conn, hs)
Mar 05 00:15:35 example matrix-synapse[59337]: File "/opt/venvs/matrix-synapse/lib/python3.12/site-packages/synapse/storage/databases/main/pusher.py", line 575, in __init__
Mar 05 00:15:35 example matrix-synapse[59337]: super().__init__(database, db_conn, hs)
Mar 05 00:15:35 example matrix-synapse[59337]: File "/opt/venvs/matrix-synapse/lib/python3.12/site-packages/synapse/storage/databases/main/receipts.py", line 122, in __init__
Mar 05 00:15:35 example matrix-synapse[59337]: super().__init__(database, db_conn, hs)
Mar 05 00:15:35 example matrix-synapse[59337]: File "/opt/venvs/matrix-synapse/lib/python3.12/site-packages/synapse/storage/databases/main/stream.py", line 591, in __init__
Mar 05 00:15:35 example matrix-synapse[59337]: super().__init__(database, db_conn, hs)
Mar 05 00:15:35 example matrix-synapse[59337]: File "/opt/venvs/matrix-synapse/lib/python3.12/site-packages/synapse/storage/databases/main/receipts.py", line 1141, in __init__
Mar 05 00:15:35 example matrix-synapse[59337]: super().__init__(database, db_conn, hs)
Mar 05 00:15:35 example matrix-synapse[59337]: File "/opt/venvs/matrix-synapse/lib/python3.12/site-packages/synapse/storage/databases/main/client_ips.py", line 419, in __init__
Mar 05 00:15:35 example matrix-synapse[59337]: super().__init__(database, db_conn, hs)
Mar 05 00:15:35 example matrix-synapse[59337]: File "/opt/venvs/matrix-synapse/lib/python3.12/site-packages/synapse/storage/databases/main/client_ips.py", line 89, in __init__
Mar 05 00:15:35 example matrix-synapse[59337]: super().__init__(database, db_conn, hs)
Mar 05 00:15:35 example matrix-synapse[59337]: File "/opt/venvs/matrix-synapse/lib/python3.12/site-packages/synapse/storage/databases/main/deviceinbox.py", line 104, in __init__
Mar 05 00:15:35 example matrix-synapse[59337]: self._to_device_msg_id_gen: MultiWriterIdGenerator = MultiWriterIdGenerator(
Mar 05 00:15:35 example matrix-synapse[59337]: ^^^^^^^^^^^^^^^^^^^^^^^
Mar 05 00:15:35 example matrix-synapse[59337]: File "/opt/venvs/matrix-synapse/lib/python3.12/site-packages/synapse/storage/util/id_generators.py", line 307, in __init__
Mar 05 00:15:35 example matrix-synapse[59337]: self._sequence_gen.check_consistency(
Mar 05 00:15:35 example matrix-synapse[59337]: File "/opt/venvs/matrix-synapse/lib/python3.12/site-packages/synapse/storage/util/sequence.py", line 192, in check_consistency
Mar 05 00:15:35 example matrix-synapse[59337]: raise IncorrectDatabaseSetup(
Mar 05 00:15:35 example matrix-synapse[59337]: synapse.storage.engines._base.IncorrectDatabaseSetup:
Mar 05 00:15:35 example matrix-synapse[59337]: Postgres sequence 'device_inbox_sequence' is inconsistent with associated stream position
Mar 05 00:15:35 example matrix-synapse[59337]: of 'to_device' in the 'stream_positions' table.
Mar 05 00:15:35 example matrix-synapse[59337]:
Mar 05 00:15:35 example matrix-synapse[59337]: This is likely a programming error and should be reported at
Mar 05 00:15:35 example matrix-synapse[59337]: https://github.com/matrix-org/synapse.
Mar 05 00:15:35 example matrix-synapse[59337]:
Mar 05 00:15:35 example matrix-synapse[59337]: A temporary workaround to fix this error is to shut down Synapse (including
Mar 05 00:15:35 example matrix-synapse[59337]: any and all workers) and run the following SQL:
Mar 05 00:15:35 example matrix-synapse[59337]:
Mar 05 00:15:35 example matrix-synapse[59337]: DELETE FROM stream_positions WHERE stream_name = 'to_device';
Mar 05 00:15:35 example matrix-synapse[59337]:
Mar 05 00:15:35 example matrix-synapse[59337]: This will need to be done every time the server is restarted.
Mar 05 00:15:35 example matrix-synapse[59337]:
Mar 05 00:15:35 example matrix-synapse[59337]:
Mar 05 00:15:35 example matrix-synapse[59337]: There may be more information in the logs.
Mar 05 00:15:35 example matrix-synapse[59337]: **********************************************************************************The error message itself suggests a workaround, namely deleting the affected row from the stream_positions table:
DELETE FROM stream_positions WHERE stream_name = 'to_device';This works, but as the message says, it has to be repeated on every restart of the server. The cleaner fix is to make the PostgreSQL sequence consistent with the stream position again, using the values from the SQLite snapshot. First, look up the last stream position of the affected stream in the snapshot (shown here for to_device and un_partial_stated_room_stream, the two streams from the errors above):
root@example:~/matrix-db-issue# sqlite3 homeserver.db
SQLite version 3.45.1 2024-01-30 16:01:20
Enter ".help" for usage hints.
sqlite> select stream_id from stream_positions WHERE stream_name = 'to_device';
65
sqlite> .quitsqlite> select stream_id from stream_positions WHERE stream_name = 'un_partial_stated_room_stream';
13Then connect to the PostgreSQL database, set the stream position back to the value from SQLite and reset the associated sequence so that it continues one past the stream position:
postgres@example:~$ psql -U synapse_user -h 127.0.0.1 -d synapse
Password for user synapse_user:
psql (16.13 (Ubuntu 16.13-0ubuntu0.24.04.1))
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, compression: off)
Type "help" for help.
synapse=> update stream_positions set stream_id = 65 where stream_name = 'to_dev
ice';
UPDATE 1
synapse=> select * from stream_positions where stream_name = 'to_device';
stream_name | instance_name | stream_id
-------------+---------------+-----------
to_device | master | 65
(1 row)
synapse=> SELECT setval('device_inbox_sequence', (
SELECT stream_id + 1 FROM stream_positions WHERE stream_name = 'to_device'
));
setval
--------
66
(1 row)
\qRepeat this for every stream that is reported as inconsistent. Afterwards, Synapse starts cleanly and no manual intervention is needed on subsequent restarts.
Enabling User Registration
So far, the only way to create accounts on our server is the registration shared secret. If you want to allow users to register themselves, you need to enable registration in homeserver.yaml. As discussed in the beginning, you should never run fully open registration without any verification - your server will fill up with spam accounts in no time. The recommended way is to require a verified e-mail address, which in turn requires the working mail setup mentioned in the preparations:
enable_registration: true
registrations_require_3pid:
- email
email:
smtp_host: mail.example.com
smtp_port: 587
smtp_user: "synapse@example.com"
smtp_pass: "yourmailpassword"
force_tls: true
notif_from: "Your %(app)s homeserver <noreply@example.com>"Registration settings in homeserver.yaml
A good middle ground between closed and open registration are registration tokens: registration stays closed to the general public, but you can hand out tokens that allow the bearer to create an account. For this, set registration_requires_token: true and create tokens through the Admin API - the details are described in the official Synapse documentation. After any change to the configuration, restart Synapse for the settings to take effect.
Enabling Voice and Video Calls
Matrix supports two kinds of calls. Legacy one to one calls are negotiated directly between the two clients via WebRTC. They mostly work out of the box, but as soon as one of the participants is behind NAT - which is almost always the case - you need a TURN server to relay the media traffic. Group calls on the other hand are based on MatrixRTC and Element Call and require additional backend components.
For one to one calls, install the coturn TURN server:
sudo apt install coturnGenerate a shared secret (for example with the same command we used for the registration secret) and configure /etc/turnserver.conf:
use-auth-secret
static-auth-secret=YOURTURNSECRET
realm=turn.example.com
# Security: do not allow relaying to private networks
no-tcp-relay
denied-peer-ip=10.0.0.0-10.255.255.255
denied-peer-ip=192.168.0.0-192.168.255.255
denied-peer-ip=172.16.0.0-172.31.255.255
allow-loopback-peers=false
# Limit the load
user-quota=12
total-quota=1200Minimal coturn configuration in /etc/turnserver.conf
Then point Synapse to the TURN server by adding the following to homeserver.yaml and restart both services. Note that coturn listens on port 3478 and uses a UDP port range (49152 to 65535 by default) for the relayed media, so these ports need to be reachable from the internet as well:
turn_uris:
- "turn:turn.example.com?transport=udp"
- "turn:turn.example.com?transport=tcp"
turn_shared_secret: "YOURTURNSECRET"
turn_user_lifetime: 86400000
turn_allow_guests: trueTURN settings in homeserver.yaml
Group calls and video conferences are provided by Element Call. Element Call is not a Synapse module but a separate stack consisting of the Element Call widget (the user interface), a LiveKit SFU that distributes the media streams, and a JWT service that issues access tokens for the SFU. On the homeserver side, you announce the SFU to clients through an additional .well-known entry (org.matrix.msc4143.rtc_foci) on your root domain. Setting up the full Element Call stack deserves an article of its own - until then, the self-hosting documentation in the Element Call repository at https://github.com/element-hq/element-call describes the necessary steps in detail.
Comments ()