Synapse [1] the chat server recently turned 1.0, and I've got some spare CPU time and bandwidth on a tiiiiny VPS, so the urge to set up a home chat server and play God on it got hold of me.

After a glimpse at Synapse's documentation, I thought its configuration process was simple enough (or was it?) and decided to do it the old-school way, i.e. no Docker involved.

[1]The reference homeserver for Matrix.org: https://matrix.org/

Installation

Synapse is written mainly in Python and C, and the devs made it available on PyPI. Just create a virtualenv or venv, and pip install matrix-synapse. The installation process needs a working C compiler and Python header files though. In my case, the default GCC and Python packages came with my Fedora installation was used.

Synapse Configuration

The server itself is actually a web server exposing a RESTy API, so there are many web-server-like configuration options, such as what's the domain name, where to bind, where are the TLS certificates, etc. One can run the commands below to generate a default config file named homeserver.yaml, as described in the documentation:

1
2
3
4
5
6
cd synapse_runtime
python -m synapse.app.homeserver \
       --server-name my.domain.name \
       --config-path homeserver.yaml \
       --generate-config \
       --report-stats=[yes|no]

And of course you need to activate your Synapse virtualenv (or venv) before running these commands. synapse_runtime should be an empty dir. It's intended for the running Synapse process to store keys, data files, temporary files, etc.

The mose important options in homeserver.yaml may be these:

1
2
3
server_name    # The actual domain name used to point to Synapse.
listeners      # Tell Synapse where to bind the listening server sockets.
database       # What database to use, and where to connect.

To get the server up and running, you must get these options right. All other things can be tuned later. The generated config file uses the server name you specified in the command line, tells Synapse to bind to localhost:8008 and localhost6:8008, waits for plain HTTP connections, and creates an Sqlite database stored in synapse_runtime.

These default settings are ideal for my small-time usage, so I didn't even modify the generated config file at all. Wait what? Plain HTTP on localhost? How on earth will this combination be actually useful?

Reverse Proxying Behind Cloudflare

Yeah I already had an instance of Nginx running on my VPS, so I was thinking of setting up a reverse proxy. Problem was, the whole Nginx set-up was behind Cloudflare, and Cloudflare only do proxying for some fixed TCP ports. Synapse federation needs port 8448, and it's not in the Cloudflare proxied ports list. Luckily the federation documentation gave a way to remap the federation port. I just needed to carefully craft a slightly elaborated Nginx config.

The Nginx config came out like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
server
{
    listen         443 ssl;
    listen         [::]:443 ssl;
    server_name    my.domain.name;
    ssl_certificate        /path/to/certificate;
    ssl_certificate_key    /path/to/key;

    location = /.well-known/matrix/server {
        alias /path/to/synapse_delegation;
        types { }
        default_type application/json;
    }

    location /_matrix {
        proxy_pass http://localhost:8008;
        proxy_set_header X-Forwarded-For $remote_addr;
        client_max_body_size 0;
    }

    location / {
        proxy_pass http://localhost:8008;
        proxy_set_header X-Forwarded-For $remote_addr;
        client_max_body_size 0;
    }
}

I gave the Synapse server a dedicated domain name, so it wouldn't interfere with the stuff my Nginx was already serving. The /.well-known/matrix/server file is a simple JSON file with these content:

1
{ "m.server": "my.domain.name:443" }

This file will direct other homeservers to talk to port 443, as described in the federation doc, thus no need to expose port 8448 anymore.

And, to make it possible to upload big media files (photos, videos, etc.) during a chat, the client_max_body_size instruction is used. Synapse has it's own media size limit in homeserver.yaml, which defaults to 10M bytes. One can modify that if she feels insecure.

SELinux Permissions

Well, all the config files and software are in place, things should just work, right? Right?

Not really. All decent people should use a decent Linux distribution like Fedora, and Fedora comes with some decent security rules enforced by SELinux. One of the decent rules is to forbid making TCP connections from the Nginx process.

This rule is enabled by default. With it enabled, reverse proxying will never work, you'll just see 502 error pages and some permission denied messages in Nginx error logs.

Behold the magical spell to disperse the rule and make things run properly:

1
setsebool -P httpd_can_network_connect 1

Creating a User

The auto-generated Synapse config disables user registration, so you can't create accounts via any client. This is fine by me, since it's intended to be a private server.

In this case, one can invoke a command line tool to register a user:

1
register_new_matrix_user -c path/to/homeserver.yaml http://localhost:8008

And of course the command needs to be run inside the Synapse virtualenv.

If you want to enable user registration, just change the config, and use any client to create accounts.

Conclusion

The Synapse instance can be tested with the web client hosted at https://riot.im/app/ . Or, you can test the federation mechanisms via https://federationtester.matrix.org/ .

To save my own future ass, I wrote down all these steps as an Ansible playbook. And I strongly recommend you to do so as well.