Matrix setup with Synapse, Postgres, Maubot, and matrix-registration

Posted on

This is how I set up my own Matrix server on a Raspberry Pi with Docker. Unfortunately, the Matrix community has stopped releasing ARM images, so the latest version that will work on ARM is v1.26.0. The instructions will work the same for x86_64 systems, except you’ll be able to use the default x86_64 images in the docker-compose file.

This installation comes with Maubot and matrix-registration containers too. If you don’t want to use those features, leave out those sections of the docker-compose config and don’t follow the instructions in the corresponding sections.

First, switch the Raspberry Pi to 64-bit kernel mode for performance:

echo 'arm_64bit=1' | sudo tee -a /boot/config.txt
sudo systemctl reboot

Create a directory for the services we’re going to provide:

mkdir -p ~/services/data/{certbot,maubot,nginx,postgres,registration,synapse,www}
cd ~/services

The rest of the commands below will assume the working directory is ~/services.


The homeserver is going to be hosted on, but the server name is (That way usernames can be instead of, which would be sort of like having an email address For federation to work correctly, other servers need to know that uses as its server. To do that, create the file with the contents { "m.server": "" }. We should also add the following line to /etc/hosts so that any outgoing requests to are routed right back to the machine:

Another note: you may want to change the DNS server to something better. I added the following line to /etc/dhcpcd.conf:

static domain_name_servers=

And then do sudo service dhcpcd restart.


Now install docker and docker-compose following the instructions here. Then create docker-compose.yml:

wget \
    -O docker-compose.yml

You’ll note that in several places I set the user to 1000:1000. I did this so that data files are owned by my user, plus it’s generally safer not to run containers as root. You can see which numbers you need to use by running id -u and id -g.

SSL certificate

We’re going to set up certbot with a new SSL certificate from LetsEncrypt. Since I host other things on this server besides, I do a wildcard certificate (*, which requires DNS verification by creating a TXT record in the DNS settings for my domain. You can use host -t txt to make sure the TXT records have been updated before having LetsEncrypt check them.

mkdir -p data/certbot/conf
curl -s \
    > data/certbot/conf/ssl-dhparams.pem
curl -s \
    > data/certbot/conf/options-ssl-nginx.conf
docker-compose run --rm certbot certonly --manual --preferred-challenges=dns \
    -w /var/www/certbot --email -d '*' \
    --rsa-key-size 4096 --agree-tos --force-renewal

If you don’t need a wildcard certificate, replace that last line with this:

docker-compose run --rm certbot certonly --webroot \
    -w /var/www/certbot --email -d '' \
    --rsa-key-size 4096 --agree-tos --force-renewal

To renew the wildcard certificate, you’ll have to run that last docker-compose run certbot line again. I’d suggest creating a scripts/ directory for things like this:

mkdir -p scripts
wget \
    -O scripts/
chmod +x scripts/

To set the certbot container to renew a non-wildcard certificate, remove the profiles section from the certbot container in the docker-compose.yml so that the container runs with the rest of the services, and add the following instead:

entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"

Note that changing the entrypoint this way will break the previous docker-compose run command that you ran to get the certificate if you were to run it again.


To set up NGINX, download the app.conf I’ve got here (and modify it to your liking):

wget \
    -O data/nginx/app.conf


Before doing anything else, make sure you have the version you want in the docker-compose config for the Synapse container. The last version to support ARM was v1.26.0, so if you’re on ARM you’ll need to use that. Otherwise, update the docker-compose config to use whatever version is latest. It’s not a good idea to use the tag latest, because version upgrades often require changes to be made. You can see the upgrade instructions here.

Now generate the initial configuration for Synapse:

docker-compose run --rm \
    synapse generate

Now edit data/synapse/homeserver.yaml to your liking. Here are some things I did:

  • change max_upload_size to "50M" or something
  • enable URL previews

Run docker-compose run -v /etc/passwd:/etc/passwd:ro postgres to give Postgres the chance to initialize the database before Synapse looks for it.

The next thing you should do is create the first user for yourself, and make that user an admin on the server. Run docker-compose up synapse to start the container temporarily, then open another terminal and run docker-compose exec synapse register_new_matrix_user -c /data/homeserver.yaml http://localhost:8008. Don’t forget to make yourself an admin! Once you finish with that command, quit the up process with Ctrl+C, and then run docker-compose down to destroy all the containers.


Generate a config file by running docker-compose run --rm maubot and then update the config to your liking, making sure to update the following objects:

    secret: <registration secret from data/synapse/homeserver.yaml>
  root: ''  # disable login
  <username>: <password>  # password will be replaced with bcrypted hash on startup


Copy the sample config from the repo:

wget \
    -O data/registration/config.yaml

Update the config to your liking, making sure to update the following objects:

server_location: 'http://synapse'
server_name: ''
registration_shared_secret: <registration secret from data/synapse/homeserver.yaml>
admin_api_shared_secret: <invent a secret string here>
base_url: ''
db: 'sqlite:////data/db.sqlite3'
host: ''  # super important
      filename: /data/m_reg.log

I’ve got a script that can be used to create a one-time-use token that expires after 7 days:

mkdir -p scripts
wget \
    -O scripts/
chmod +x scripts/

Read about your options for managing tokens here.

enabling the services with Systemd

Now we’re going to use Systemd to make this set of containers a background service that starts at boot.

wget \
chmod +x
wget -O
chmod +x
wget -O services.service
sed -i "s|kyle|$USER|g" services.service
sudo systemctl enable /home/$USER/services/services.service

Now we’re finally ready to get everything started.

sudo systemctl start services

Message me and tell me how it went!