So our good friends at matrix.org released a v0.99.1.1 of Synapse as a precursor for a much anticipated 1.0 release. A big change in the upcoming release is that federation between servers will now require a proper TLS certificate and the current self signed cert that Synapse provides won't work. Homeserver owners are advised to make that change as soon as possible, as v1 is supposed to be released in March.

I've been running my Synapse behind Traefik for quite some time now but never bothered to replace the certificate for federation, although I have proper TLS on the client side. Once I figured out the different options it was pretty straight forward to find one that worked with Traefik.

In this guide we are going to put both the client side and federation of Synapse on a non-subdomain - ie example.com - and use port 443 for both, so no 8448 for federation. If you have another setup - for example matrix.example.com - it should be possible with some adjustments.

We'll achive this by using both a SRV record and a .well-known/matrix/server file to delegate federation to example.com. Synapse itself will be available on synapse.example.com.

I'm also using postgres as the database for Synapse.

Prerequisites

If you already have a server with Traefik running then continue. Otherwise I recommend you read my previous post and you'll be back here soon.

Setup

Let's make a directory for your data to live in, as sudo if needed.

mkdir /opt/matrix
# If you had to use sudo, set permissons
sudo chown -R $USER:$GROUP /opt/matrix

Then we need an internal docker network for the services to communicate on

docker network create matrix

docker-compose.yml

We'll put our services in a docker-compose file to be able to easily manage things. We'll start with Synapse. I'm using the avhost/docker-matrix image but it should be possible to use the official one too I'm sure.

nano /opt/matrix/docker-compose.yml
version: "2"
services:
  postgres:
    image: postgres:9.6.4
    restart: unless-stopped
    networks:
      - default
    volumes:
     - /opt/matrix/pgdata:/var/lib/postgresql/data
    environment:
     - POSTGRES_PASSWORD=SECRET
     - POSTGRES_USER=synapse
    labels:
      - "traefik.enable=false"

  synapse:
    image: avhost/docker-matrix:v0.99.1.1
    restart: unless-stopped
    depends_on:
      - postgres
    networks:
      - web
      - default
    ports:
     # Coturn
     - "3478:3478"
     - "5349:5349"
    volumes:
     - /opt/matrix/synapse:/data
    labels:
      - "traefik.backend=synapse"
      - "traefik.enable=true"
      - "traefik.frontend=true"
      - "traefik.port=8008"
      - "traefik.frontend.rule=Host:synapse.example.com"
      - "traefik.docker.network=web"

networks:
  default:
    external:
      name: matrix
  # Traefiks network
  web:
    external: true

☝️ Remember to replace example.com in traefik.frontend.rule. That is where we do the reverse proxy for servers. Also change the POSTGRES_PASSWORD env.

Nginx

To be able to serve the .well-known path on our domain we need Nginx. We're also going to use nginx to proxy clients to Synapse. With this setup you have the options to also serve static files on your domain, like example.com/some-site.

Let's make a directory for nginx with a subdirectory for www.

mkdir -p /opt/matrix/nginx/www

Then we need a .well-known directory to put files in, for both our server and for clients.

mkdir -p /opt/matrix/nginx/www/.well-known/matrix

We start with the server file. This will take care of proxying federation to your subdomain so you'll be able to have usernames in Matrix like @me:example.com, while actually hosting Synapse at synapse.example.com.

nano /opt/matrix/nginx/www/.well-known/matrix/server

Here you place a JSON formatted file.

{
  "m.server": "synapse.example.com:443"
}

☝️ Replace example.com above and make sure to NOT include https or anything like that.

Next up is a client file, which makes it possible to sign in to your Riot with custom domain configured by only using your username and not full MXID (@username:example.com).

nano /opt/matrix/nginx/www/.well-known/matrix/client

Another JSON formatted file.

{
  "m.homeserver": {
    "base_url": "https://example.com"
  }
}

☝️ Here you include the full URL. You can also add m.identity_server if you have your own.

Now we need a config file for nginx!

nano /opt/matrix/nginx/matrix.conf
server {
  listen         80 default_server;
  server_name    example.com;

 # Traefik -> nginx -> synapse
 location /_matrix {
    proxy_pass http://synapse:8008;
    proxy_set_header X-Forwarded-For $remote_addr;
    client_max_body_size 128m;
  }

  location /.well-known/matrix/ {
    root /var/www/;
    default_type application/json;
    add_header Access-Control-Allow-Origin  *;
  }
}

Now we can add nginx to our docker-compose.yml.

version: "2"
services:
  postgres:
    # ...
  synapse:
    # ...
  # Add this
  nginx:
    image: nginx:1.12-alpine
    restart: unless-stopped
    networks:
      - web
      - default
    labels:
      - "traefik.enable=true"
      - "traefik.frontend.rule=Host:example.com"
      - "traefik.frontend.passHostHeader=true"
      - "traefik.port=80"
      - "traefik.docker.network=web"
    volumes:
      - ./nginx/matrix.conf:/etc/nginx/conf.d/matrix.conf
      - ./nginx/www:/var/www/

networks:
    # ...

Synapse config

Now we are ready to configure our Synapse so we can get it up and running.

This will create a homeserver.yaml in our synapse directory.

docker run -v /opt/matrix/synapse:/data --rm \
  -e SERVER_NAME=example.com \
  -e REPORT_STATS=yes \
  avhost/docker-matrix:v0.99.1.1 \
  generate

☝️ Replace example.com.

We then need to edit some stuff.

sudo nano /opt/matrix/synapse/homeserver.yaml

This is not a complete configuration file, just the parts that needs editing.

listeners:
  - port: 8008
    tls: false
    # Since it's running in a container we need to listen to 0.0.0.0
    # The port is only exposed on the host and put behind reverse proxy
    bind_addresses: ['0.0.0.0']
    # Previous
    # bind_addresses: ['::1', '127.0.0.1']
    type: http
    x_forwarded: true

    resources:
      - names: [client, federation]
        compress: false

We change from sqlite to postgres.

# Database configuration
database:
  name: psycopg2
  args:
    user: synapse
    # From your docker-compose.yml
    password: YOUR_PASSWORD_HERE
    database: synapse

    # This hostname is accessible through the docker network and is set 
    # by docker-compose. If you change the name of the service it will be different
    host: postgres

We'll enable registration to be able to test. You can change this later on.

# Enable registration for new users.
enable_registration: True

That should be it. I do recommend reading through the config and perhaps another guide to checkout all the options but for now let's try and start up our services...

cd /opt/matrix
docker-compose up -d
docker-compose ps

... and you should get something like this.

      Name                     Command              State                            Ports
--------------------------------------------------------------------------------------------------------------------
matrix_nginx_1      nginx -g daemon off;            Up      80/tcp
matrix_postgres_1   docker-entrypoint.sh postgres   Up      5432/tcp
matrix_synapse_1    /start.sh autostart             Up      0.0.0.0:3478->3478/tcp, 0.0.0.0:5349->5349/tcp, 8448/tcp

If the services are not up have a look at the logs with docker-compose logs (start with docker-compose logs synapse if the wall of text is too much).

Register account and login

If everything looks good, you can now use Riot to create an account and login. You need to select "Advanced" and then change Homeserver URL to your own.

create-matrix-account-1

Final notes

SRV Record

You should also add a SRV record to support older homeservers.

_matrix._tcp.example.com. IN SRV 10 5 443 synapse.example.com.

Troubleshooting

If it seems like people on other homeservers can't see your messages, or the other way around, your federation might be broken. Matrid Federation Tester is a good first place to check. If you want to see the actual data from the tester, use this link instead but replace the hostname.

Hosting Riot

If you want your own Riot you can use this guide I've written. It should also possible be to add Riot to the setup above with some modifications to your docker-compose.yml (add riot.example.com to traefik.frontend.rule) and your nginx.conf (add a new location and directory to nginx/www/).

Thanks!

If you have any questions or feedback please comment below or hit me up on Twitter 🙌