Setting up a Synapse + Element Matrix chat server
Firewall
The following ports have to be opened in your firewall:
80/tcp # HTTP 443/tcp # HTTPS
Domain setup
You will have to decide if you want Synapse to use your root domain or a subdomain, and on which subdomain you want to serve Element.
Examples are:
quietlife.nlas the homeserver domain andchat.quietlife.nlserving Elementmatrix.quietlife.nlas the homeserver domain andchat.quietlife.nlserving Elementchat.quietlife.nlboth as the homeserver domain and the domain serving Elementquietlife.nlas the homeserver domain without serving Element
It doesn't really matter which you choose, but configuration will depend on it slightly. In this case, I decided to use quietlife.nl as the homeserver domain and chat.quietlife.nl to serve Element.
Note that Element is merely a web application talking to Synapse. It is not required to build a Matrix server. You can also use Synapse without Element, using locally installed Matrix client applications instead. So you are free to leave out Element entirely if you wish.
Docker
Unfortunately, Synapse and Element are not packaged for Debian, so you can't simply apt install them. For now, Docker is likely the easiest way to deploy this stack.
It is probably a good idea to first read Setting up IPv6 NAT in Docker in order to have IPv6 working for these containers.
Compose file
First create /var/opt/matrix:
sudo mkdir /var/opt/matrix
Then create docker-compose.yml there:
- /var/opt/matrix/docker-compose.yml
services: postgres: image: postgres:17 restart: always environment: POSTGRES_USER: "synapse" POSTGRES_PASSWORD: "5yN@Ps3" POSTGRES_DB: "synapse" healthcheck: test: ["CMD", "pg_isready", "-U", "synapse"] networks: - network volumes: - /var/opt/matrix/postgres:/var/lib/postgresql/data synapse: image: matrixdotorg/synapse:latest restart: always depends_on: - postgres networks: - network ports: - "127.0.0.1:8008:8008" - "[::1]:8008:8008" volumes: - /var/opt/matrix/synapse:/data
Make sure to change POSTGRES_PASSWORD to something you generated yourself.
If you also want to serve Element, add this:
- /var/opt/matrix/docker-compose.yml
element: image: vectorim/element-web:latest restart: always networks: - network ports: - "127.0.0.1:8088:80" - "[::1]:8088:80" volumes: - /var/opt/matrix/element/config.json:/app/config.json
If you have an IPv6-enabled Docker setup, add this:
- /var/opt/matrix/docker-compose.yml
networks: network: driver: bridge enable_ipv6: true ipam: driver: default config: - subnet: 2001:db8:db8:8008::/64
PostgreSQL
Start PostgreSQL:
cd /var/opt/matrix sudo docker compose up -d postgres
The database created by PostgreSQL's init script upsets Synapse because of the collation.
So after starting PostgreSQL, drop the database and recreate it manually:
sudo docker exec -it matrix-postgres-1 dropdb -U synapse synapse sudo docker exec -it matrix-postgres-1 createdb -U synapse -E UTF8 -l C -T template0 synapse
Synapse
For the Synapse container, create its data directory:
sudo mkdir /var/opt/matrix/synapse
Create its configuration file there:
- /var/opt/matrix/synapse/homeserver.yaml
server_name: "quietlife.nl" public_baseurl: "https://quietlife.nl/" serve_server_wellknown: true report_stats: false media_store_path: "/data/media_store" signing_key_path: "/data/signing.key" form_secret: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" macaroon_secret_key: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" registration_shared_secret: "cccccccccccccccccccccccccccccccccccccccccccccccccc" listeners: - port: 8008 tls: false type: http x_forwarded: true resources: - names: [client, federation] compress: false database: name: psycopg2 args: host: "postgres" port: 5432 user: "synapse" password: "5yN@Ps3" database: "synapse" trusted_key_servers: - server_name: "matrix.org"
Make sure you change server_name, public_baseurl and password under database.
For form_secret, macaroon_secret_key and registration_shared_secret you have to generate random password strings.
For example:
apg -m50 -n3
If you want to add e-mail support to Synapse, append this to homeserver.yaml:
- /var/opt/matrix/synapse/homeserver.yaml
email: smtp_host: "quietlife.nl" smtp_port: 587 smtp_user: "synapse@quietlife.nl" smtp_pass: "PASSWORD" require_transport_security: true enable_notifs: true notif_from: "Quiet Chat <synapse@quietlife.nl>" app_name: "Quiet Chat" client_base_url: "https://chat.quietlife.nl"
You will, of course, have to change the majority of fields to match your own mail setup and whether or not you also host Element.
If you run your own TURN server, append this to homeserver.yaml:
- /var/opt/matrix/synapse/homeserver.yaml
turn_uris: ["turn:turn.quietlife.nl?transport=udp", "turn:turn.quietlife.nl?transport=tcp"] turn_shared_secret: "dddddddddddddddddddddddddddddddddddddddddddddddddd" turn_user_lifetime: 86400000 turn_allow_guests: true
Make sure you change turn_uris and turn_shared_secret.
Set the permissions to 991:991:
sudo chown -R 991:991 /var/opt/matrix/synapse
And start Synapse:
cd /var/opt/matrix sudo docker compose up -d synapse
Element
(Feel free to skip this part if you do not want to host Element. You can still use the Element desktop application or other Matrix clients if you don't.)
For the Element container, create its data directory:
sudo mkdir /var/opt/matrix/element
Create its configuration file there:
- /var/opt/matrix/element/config.json
{ "default_server_name": "quietlife.nl", "disable_custom_urls": true, "disable_3pid_login": true, "disable_guests": true, "brand": "Quiet Chat", "branding": { "auth_footer_links": [], "welcome_background_url": "https://quietlife.nl/resources/images/background.jpg" }, "embedded_pages": { "login_for_welcome": true }, "setting_defaults": { "UIFeature.registration": false } }
And start Element:
cd /var/opt/matrix sudo docker compose up -d element
nginx
A reverse proxy will be needed to do the TLS termination and to forward incoming traffic to the Docker containers.
This guide assumes that you use nginx with certificates generated by certbot.
Homeserver domain
For your homeserver domain, it's important to do two things:
- Route traffic on
/.well-known/matrix/(client|server)to Synapse - Route traffic on
/_matrixand/_synapse/clientto Synapse
There is no problem if you host another site on your root domain, as long as these subpaths can be used for Matrix.
All you need is this:
- /etc/nginx/sites-available/quietlife.nl.conf
location ~ ^/.well-known/matrix/(client|server) { include proxy_params; proxy_http_version 1.1; proxy_pass http://localhost:8008; } location ~ ^(/_matrix|/_synapse/client) { client_max_body_size 50M; include proxy_params; proxy_http_version 1.1; proxy_pass http://localhost:8008; }
Element domain
If you want to host Element, you'll most likely want that on a subdomain, such as chat.quietlife.nl.
Of course, skip this step if you don't run the Element container.
- /etc/nginx/sites-available/chat.quietlife.nl.conf
server { listen 80; listen [::]:80; listen 443 ssl; listen [::]:443 ssl; http2 on; server_name chat.quietlife.nl; access_log /var/log/nginx/chat.quietlife.nl-access.log; error_log /var/log/nginx/chat.quietlife.nl-error.log; ssl_certificate /etc/letsencrypt/live/chat.quietlife.nl/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/chat.quietlife.nl/privkey.pem; if ($scheme != "https") { return 301 https://$host$request_uri; } location / { add_header X-Content-Type-Options nosniff; add_header X-Frame-Options SAMEORIGIN; add_header X-XSS-Protection "1; mode=block"; include proxy_params; proxy_pass http://localhost:8088; } }
Database backups
It's probably a good idea to make regular backups of the Synapse database. You can use this script:
- /usr/local/bin/backup-synapse.sh
#!/bin/bash backup_directory="/var/backups/synapse_backups" timestamp=$(date +"%Y-%m-%d_%H:%M") # Create backup directory if not present if [ ! -d $backup_directory ]; then mkdir -p $backup_directory; fi # Create a database dump and compress it nice -n 19 docker exec matrix-postgres-1 pg_dump postgres://synapse@localhost:5432/synapse | nice -n 19 xz -T1 -1 > $backup_directory/synapse-postgres-$timestamp.sql.xz # Delete backups older than one week find $backup_directory -mtime +7 -exec rm {} \; exit 0
Make it executable:
sudo chmod +x /usr/local/bin/backup-synapse.sh
And run it as a cronjob for the root user:
30 * * * * /usr/local/bin/backup-synapse.sh
Automatic updates
Because Synapse and Element run in Docker, you can't update them with unattended-upgrades.
You can use Watchtower to automate container updates, with this line in root's crontab:
0 21 * * * /usr/bin/docker run --rm -v /var/run/docker.sock:/var/run/docker.sock containrrr/watchtower --run-once --cleanup
Creating user accounts
The quickest way to create user accounts is to talk to Synapse directly on http://localhost:8008.
Within the container, there is a register_new_matrix_user command:
docker exec -it matrix-synapse-1 register_new_matrix_user -c /data/homeserver.yaml http://localhost:8008