Enter the Matrix

After Element’s recent funding round for $30M generated a bit of buzz, I thought it was time that I took more notice of the Matrix protocol and what it can offer.

I’ve been keeping an eye on it for the last year or so as the general principles of a modern, secure, federated messaging protocol sit well with me. Like most, I’d never put much time into learning what was going on and how to get involved. The good news: the learning curve isn’t steep. The better news: the implementation curve is even shallower!

In a couple of hours or so you can get the basics down and have a home server running in a space to call your own.

The basics

I learnt all I needed for the basics from the Matrix homepage. They have a great animation that explains the big picture, as well as links to other, more detailed information about how to learn more.

Go there and come back when you’re comfortable.


To send and receive messages and other media, users need client(s). Since we’re starting with Element, we might as well go there and try it out. A free account is an easy way to check out the landscape. TL;DR - it looks just like Slack.

I’m yet to explore any other clients; Element was good enough for me!


Synapse is the server as implemented by the Matrix team. You can’t wrong with the original! It also happens that the docs aren’t bad - this helps a lot later.

By far the easiest way to get things running is using Docker. They have a pre-baked image that can be used. The docs are good but not amazing. There’s a bit of digging to be done and comments to read but it doesn’t take long to get to grips with it.

Since I’ve already got a server running some bits and bobs behind Traefik, that was the perfect route for me to take here.

Here’s what my docker-compose file looks like


    image: matrixdotorg/synapse:latest
    restart: unless-stopped
      - SYNAPSE_SERVER_NAME=neverstew.com
      - SYNAPSE_CONFIG_PATH=/data/homeserver.yaml
      - synapse-data:/data
      - "traefik.http.routers.matrix.rule=Host(`matrix.neverstew.com`)"
      - "traefik.http.routers.matrix.entrypoints=websecure"
      - "traefik.http.routers.matrix.tls.certresolver=myresolver"
      - "traefik.http.middlewares.matrix.headers.accesscontrolalloworiginlist=*"
      - "traefik.http.middlewares.matrix.headers.accesscontrolmaxage=100"
      - "traefik.http.middlewares.matrix.headers.addvaryheader=true"
      - "traefik.http.services.matrix.loadbalancer.server.port=8008"


Before running that, a config file needs to be generated. Do this with

docker-compose run matrix generate

Launch the server

docker-compose up -d matrix

And finally, generate an admin user so that you can sign in.

docker-compose exec matrix register_new_matrix_user -c /data/homeserver.yaml --admin http://localhost:8008

This is the server fully set up! What a breeze.


In order to use this in the wild, there are a few things that we need to tidy up.

Remove the registration secret

There’s a secret in the generated config file that allows you to use the script above to generate a new user. We want to get rid of it to make sure others can’t somehow register admin users (this is unlikely behind our reverse proxy, but it’s worth doing).

First, find your config file in the docker volume using docker volume inspect <volume_id>. The file will be at /data/homeserver.yaml.

Edit this file and comment out the registration secret (search for ‘secret’).

Restart the docker container with docker-compose restart matrix for the changes to take effect.

Set up delegation

If your Synapse server is the same as the domain that you’d like to be identified by, then you can skip this bit. If, like me, you’re going to run your server at a different DNS record to the server name you’ll need to set up some information that allows matrix clients and servers to understand where to route requests.

On my main site there are now two files:

  • /.well-known/matrix/server
  • /.well-known/matrix/client

They both return simple json payloads that point to matrix.neverstew.com.

See the docs for more info on what goes in the server file and what goes in the client file.

Et Voilà

That’s it. Sign into your users with any client (I’m using Element for now).

I wouldn’t recommend running a server like this in production because we’ve skipped a key step of moving away from SQLite and towards another database that will scale much better with concurrent writes (postgres). There are docs for this but I’m not going for it until I’ve played around with the first version here for a while.

Times like these make me marvel at how easy it can be to set up federated services when they’re designed well.