Matrix Secure Messaging
- Setup
- Set up the Reverse Proxy
- Set up Synapse
- Adding PostgreSQL
- Test the Synapse Matrix Homeserver
- Setting up Federation in Synapse
- Deactivate Registrations
- Configuration
Setup
Create a work directory and set the necessary permissions:
mkdir -p /opt/matrix/synapse/{data,certs,vhost,html,db}
chown -R 991:991 /opt/matrix
chown -R 999:999 /opt/matrix/synapse/db
# chmod -R 777 /opt/matrix
Then we need an internal docker network for the services to communicate on:
docker network create matrix
Set up the Reverse Proxy
NGINX Proxy Service
jwilder/nginx-proxy nginx-proxy sets up a container running nginx and docker-gen. docker-gen generates reverse proxy configs for nginx and reloads nginx when containers are started and stopped.
nano /opt/matrix/docker-compose.yml
services:
proxy:
image: "jwilder/nginx-proxy"
container_name: "proxy"
volumes:
- "certs:/etc/nginx/certs"
- "vhost:/etc/nginx/vhost.d"
- "html:/usr/share/nginx/html"
- "/run/docker.sock:/tmp/docker.sock:ro"
- "/opt/matrix/synapse-federation:/etc/nginx/vhost.d/time.instar.com/synapse-federation"
networks: ["matrix"]
restart: "always"
ports:
- "80:80"
- "443:443"
networks:
matrix:
external: true
volumes:
certs:
driver: local # Define the driver and options under the volume name
driver_opts:
type: none
device: /opt/matrix/synapse/certs
o: bind
vhost:
driver: local # Define the driver and options under the volume name
driver_opts:
type: none
device: /opt/matrix/synapse/vhost
o: bind
html:
driver: local # Define the driver and options under the volume name
driver_opts:
type: none
device: /opt/matrix/synapse/html
o: bind
Letsencrypt Proxy Companion Service
letsencrypt-nginx-proxy-companion:
nano /opt/matrix/docker-compose.yml
services:
proxy:
...
letsencrypt:
image: "jrcs/letsencrypt-nginx-proxy-companion"
container_name: "letsencrypt"
volumes:
- "certs:/etc/nginx/certs"
- "vhost:/etc/nginx/vhost.d"
- "html:/usr/share/nginx/html"
- "/run/docker.sock:/var/run/docker.sock:ro"
environment:
NGINX_PROXY_CONTAINER: "proxy"
networks: ["matrix"]
restart: "always"
depends_on: ["proxy"]
...
Run the compose file with:
sudo apt-get update
sudo apt-get install docker-compose-plugin
docker compose version
Docker Compose version v2.24.7
docker compose -f /opt/matrix/docker-compose.yml up -d
curl localhost
<html>
<head><title>503 Service Temporarily Unavailable</title></head>
<body>
<center><h1>503 Service Temporarily Unavailable</h1></center>
<hr><center>nginx</center>
</body>
</html>
Set up Synapse
Configuration
nano /opt/matrix/synapse/docker-compose.yml
services:
synapse:
image: "matrixdotorg/synapse:latest"
restart: always
container_name: "synapse"
volumes:
- "/opt/matrix/synapse/data:/data"
environment:
# Replace this with your domain
VIRTUAL_HOST: "sub.domain.com"
VIRTUAL_PORT: 8008
# Replace this with your domain
LETSENCRYPT_HOST: "sub.domain.com"
# Replace this with your domain
SYNAPSE_SERVER_NAME: "sub.domain.com"
SYNAPSE_REPORT_STATS: "yes"
networks: ["matrix"]
networks:
matrix:
external: true
Generate the configuration file:
docker compose -f /opt/matrix/synapse/docker-compose.yml run --rm synapse generate
This will generate the config file inside ./data
, named homeserver.yaml
. We need to make the following changes:
- The
server_name
variable is set to the subdomain of your choice, as set in the environment variableSYNAPSE_SERVER_NAME
. TLS
is set tofalse
. You are using a reverse proxy, so TLS is handled through your web server. Leave the port be.- Make sure
enable_registration
is set totrue
, so that you can sign up and use your homeserver.
nano /opt/matrix/synapse/data/homeserver.yaml
# 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
## Server ##
server_name: "sub.domain.com"
pid_file: /data/homeserver.pid
listeners:
- port: 8008
tls: false
type: http
x_forwarded: true
resources:
- names: [client, federation]
compress: false
database:
name: sqlite3
args:
database: /data/homeserver.db
log_config: "/data/sub.domain.com.log.config"
media_store_path: /data/media_store
registration_shared_secret: secretkey
report_stats: true
macaroon_secret_key: secretkey
form_secret: secretkey
signing_key_path: "/data/sub.domain.com.signing.key"
trusted_key_servers:
- server_name: "matrix.org"
# vim:ft=yaml
And make sure that all necessary ports are open on your server - e.g. with ufw
:
# ufw allow 8448/tcp
ufw allow http
ufw allow https
Now that everything is in place, you can start synapse using a command as simple as:
docker compose -f /opt/matrix/synapse/docker-compose.yml up -d
If the container crashes immediately check the container logs:
docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS
5e5c18fcf2f8 matrixdotorg/synapse:latest "/start.py" 19 seconds ago Exited (1)
In this case the mounted in configuration file was not readable by the container due to a permission issue:
docker logs 5e5c18fcf2f8
Starting synapse with args -m synapse.app.homeserver --config-path /data/homeserver.yaml
Error in configuration at 'signing_key':
Error accessing file '/data/sub.domain.com.signing.key':
[Errno 13] Permission denied: '/data/sub.domain.com.signing.key'
Adding PostgreSQL
By default, synapse uses SQLite for its database. For a more important usecase, I recommend using PostgreSQL instead - if you need it, simply add it to your compose file:
nano /opt/matrix/synapse/docker-compose.yml
version: "3"
services:
synapse:
...
postgresql:
image: postgres:latest
container_name: synapsedb
restart: always
environment:
POSTGRES_PASSWORD: secretdbpassword
POSTGRES_USER: synapse
POSTGRES_DB: synapse
POSTGRES_INITDB_ARGS: "--encoding='UTF8' --lc-collate='C' --lc-ctype='C'"
volumes:
- "/opt/matrix/synapse/db:/var/lib/postgresql/data"
networks: ["matrix"]
...
networks:
matrix:
external: true
Configure Synapse
nano /opt/matrix/synapse/data/homeserver.yaml
And replace the SQLite configuration in:
nano /opt/matrix/synapse/data/homeserver.yaml
## Server ##
server_name: "sub.domain.com"
pid_file: /data/homeserver.pid
listeners:
- port: 8008
tls: false
type: http
x_forwarded: true
resources:
- names: [client, federation]
compress: false
database:
# name: sqlite3
# args:
# database: /data/homeserver.db
name: psycopg2
args:
user: synapse
password: secretdbpassword
database: synapse
host: synapsedb
cp_min: 5
cp_max: 10
log_config: "/data/sub.domain.com.log.config"
media_store_path: /data/media_store
registration_shared_secret: secretkey
report_stats: true
macaroon_secret_key: secretkey
form_secret: secretkey
signing_key_path: "/data/sub.domain.com.signing.key"
trusted_key_servers:
- server_name: "matrix.org"
# vim:ft=yaml
The name of the database is psycopg2, which is a PostgreSQL adapter for python. Now reload the compose file:
docker compose -f /opt/matrix/synapse/docker-compose.yml down
docker compose -f /opt/matrix/synapse/docker-compose.yml up -d
Verify that the container is now using the Postgres Database:
docker exec -ti synapsedb psql -d synapse -U synapse
psql (16.2 (Debian 16.2-1.pgdg120+2))
Type "help" for help.
synapse=# \l
List of databases
Name | Owner | Encoding | Access privileges
-----------+---------+----------+---------------------
postgres | synapse | UTF8 |
synapse | synapse | UTF8 |
template0 | synapse | UTF8 | =c/synapse +
| | | synapse=CTc/synapse
template1 | synapse | UTF8 | =c/synapse +
| | | synapse=CTc/synapse
synapse=# \c synapse
You are now connected to database "synapse" as user "synapse".
synapse=# \dt
List of relations
Schema | Name | Type | Owner
--------+------------------------------------------------+-------+---------
public | access_tokens | table | synapse
public | account_data | table | synapse
public | account_validity | table | synapse
public | application_services_state | table | synapse
...
Test the Synapse Matrix Homeserver
Our Synapse Server now allows us to use the Matrix protocol to exchange messages. But we still need to install a client to interact with our server. A list of optional clients can be found here.
Add your server address instead of the official server network and create an account - done!
Setting up Federation in Synapse
Federation is basically the ability to communicate with users on a different homeserver.
By default, each matrix server tries to reach another matrix server via port 8443
. The following process basically tells the other servers to use a different port. Because https is already working in port 443
, you're simply going to delegate the default matrix communication port to 443
.
Configuration File for NGINX
nano /opt/matrix/synapse-federation
location /.well-known/matrix/server {
return 200 '{"m.server": "sub.domain.com:443"}';
}
Change sub.domain.com
to your domain.
Edit the Docker Compose File
Open your docker-compose.yml
file and add another entry to the volumes array:
nano /opt/matrix/docker-compose.yml
services:
proxy:
image: "jwilder/nginx-proxy"
container_name: "proxy"
volumes:
- "/opt/matrix/synapse/certs:/etc/nginx/certs"
- "/opt/matrix/synapse/vhost:/etc/nginx/vhost.d"
- "/opt/matrix/synapse/html:/usr/share/nginx/html"
- "/run/docker.sock:/tmp/docker.sock:ro"
- /opt/matrix/synapse-federation:/etc/nginx/vhost.d/sub.domain.com/synapse-federation
networks: ["matrix"]
restart: "always"
ports:
- "80:80"
- "443:443"
Change sub.domain.com
to your domain. Now reload the compose file:
docker compose -f /opt/matrix/docker-compose.yml down
docker compose -f /opt/matrix/docker-compose.yml up -d
Testing
Run the curl command and you should receive a true
as response (make sure you have jq
installed):
curl https://federationtester.matrix.org/api/report?server_name=sub.domain.com --silent | jq -r '.FederationOK'
true
Change sub.domain.com
to your domain.
Deactivate Registrations
Once everyone joined shut of the registration option to your server:
nano /opt/matrix/synapse/data/homeserver.yaml
## Registration ##
enable_registration: false
And cycle the synapse service to take effect!
Configuration
The server starts up with user registration deactivated:
We can now use the admin API to create a user:
docker exec -ti synapse register_new_matrix_user
usage: register_new_matrix_user [-h] [-u USER] [-p PASSWORD] [-t USER_TYPE] [-a | --no-admin] (-c CONFIG | -k SHARED_SECRET) [server_url]
register_new_matrix_user: error: one of the arguments -c/--config -k/--shared-secret is required
The shared secret was generated for us in the home server configuration. Which is mounted to /data/homeserver.yaml
inside the container. Also the server URL is set to use port 8008
:
docker exec -ti synapse register_new_matrix_user -c /data/homeserver.yaml http://localhost:8008
New user localpart [root]: admin
Password: secretpassword
Confirm password: secretpassword
Make admin [no]: yes
Sending registration request...
Success!
The user can now be used to log into your server: