How to Run Matrix On a Raspberry Pi Without Opening Ports
The current state of the messaging industry is a disaster - a fragmented assortment of platforms who all refuse to integrate with any other. Now the EU’s impending Digital Markets Act will come into effect on November 2, but, until then, what can we use now to force interoperability?
Our savior comes in the form of Matrix, a federated messaging protocol a unique “bridging” feature to other services, such as Instagram, Telegram, Discord, and more! In other words, you can consolidate all your messages on a single Matrix account. It also happens to allow E2EE, though most bridges currently are not made to use it unfortunately.
The de facto server implementation of Matrix is called Synapse and is written in Python 🤮. There is an alternative server implementation called Dendrite that runs on Go, but it does not have full parity with Synapse yet. You may try this version, but your mileage will certainly vary.
We need somewhere to host this server at home, so we will be using a Raspberry Pi 4 8 GB model. I’m sure 4 GB will work fine as well, though I haven’t tested it myself. You may follow along with this guide if you’re using something else, though it steps may vary a little when installing the Matrix components.
Because we don’t want to open our ports (perhaps because of security concerns, CGNAT, etc.), we will be using Cloudflare (🤮) Tunnels to forward traffic to our Matrix Synapse server.
Of course, we could install Synapse manually, or we can have someone else do all the hard work - which is where the amazing matrix-docker-ansible-deploy
project by spantaleev comes in!
Before we start, I will give you a small list of what we will be using:
- matrix-docker-ansible-deploy - To install all the necessary Matrix components as well as any service bridges that you want.
- Ansible - To run the
matrix-docker-ansible-deploy
playbook. - Synapse - The server implementation we will be using.
- Element - The Matrix client that we will be using.
- Cloudflare Tunnels - To forward traffic back and forth to our server without having to open any ports.
- Let’s Encrypt - To get that beautiful HTTPS lock icon on our site (which is also required for Synapse to run).
- Raspberry Pi 4B 8 GB - To run the Matrix homeserver on.
- rpi-imager - To write Raspbian 64-bit OS to a microSD.
Prerequisites
- A Cloudflare account to manage your DNS and create tunnels.
- A domain to host your Matrix homeserver on.
- A working server in your home to physically run Matrix on - we will be using the ARM64-based Raspberry Pi 4 for this guide.
- A 32 GB or larger microSD card
- A microSD card reader
- A mouse and keyboard (optional if accessing solely through SSH)
- A monitor (optional if accessing solely through SSH)
Step 0: Prepare your Raspberry Pi 4
We will need to get our Pi ready to install Matrix on it, let alone use it. If you already have a working Pi with a 64-bit OS running on it (e.g. Ubuntu Server - the default on the Pi 4 is 32-bit Raspbian), then skip to Step 1.
Step 0.1: Obtain a working Raspi 4
This is the most difficult step. Raspberry Pis are hard to come by during these trying times. But if you do happen to get one, keep reading on.
Step 0.2: Download 64-bit Raspbian
(OPTIONAL IF YOU ARE USING rpi-imager
)
The default operating system on the Raspberry Pi 4 is 32-bit Raspbian. Synapse no longer builds Docker images for 32-bit systems, so we’re going to need to use the 64-bit image, which can be found on the Raspberry Pi site if you scroll down. The latest version as of writing is 2022-09-22-raspios-bullseye-arm64.img.xz
. Extract the 64-bit Raspbian image from the .xz
archive.
0.3: Install 64-bit Raspbian
At this point, you should plug in your microSD card reader with your >=32 GB microSD card.
Next, you will need some kind of disk imaging software, such as Balena Etcher or Rufus, however I will be using Raspberry Pi’s official imager.
In the official imager, we will select “Raspberry Pi OS (64-bit)”. If you don’t need the desktop, I recommend using the Lite version to reduce the load on your Pi. If you downloaded the image despite using the official imager, you can install it by scrolling down to the bottom and selecting “Use custom”.
Be VERY CAREFUL during this part: select your microSD card (it may show up as the reader) from the list of Storage options. Do not click on your computer’s hard drives unless you intend on wiping out your data with Raspbian.
Turing on SSH is necessary to use the Ansible playbook. If you would like to turn on SSH for your Pi without having to attach a monitor to your Raspberry Pi, please see Step 0.3.1.
Finally, select “Write” and wait for the imager to complete writing Raspbian to your microSD. Eject the card when finished and stick it in your Pi. Then, move on to Step 0.4.
0.3.1: Image Customization Options
(FOR rpi-imager
USERS)
Before writing, we can set some settings for the Raspberry Pi image. We’ll want to enable SSH access so we .
For more security, I suggest using public-key authentication. If you already generated an SSH key, it will automatically fill the field with whatever was in ~/.ssh/id_rsa.pub
. To generate a new key, you can do it on Linux using ssh-keygen
and following the instructions. Copy public key in the file it generates (the one that ends with .pub
).
For security, I recommend also changing the username and password for the Pi. If you are using Wi-Fi, you will want to configure Wireless LAN.
0.4: Turn on the Pi and update packages
Let the Pi boot up. If you are using SSH, please see Step 0.4.1, then return here.
After booting with the desktop environment, it may prompt you to add information. Plug in a mouse and keyboard and fill it out as asked. After this, it may install packages. This may take a hot minute. It will ask to restart when done.
Once the server has restarted or you have gained access, some packages in the image may not be updated to the latest version. In the interest of security, we’ll want to bring them all up to date by running the classic duo on the Raspberry Pi:
sudo apt update && sudo apt upgrade -y
Let it do its thing, then proceed to Step 1.
0.4.1: SSH into your Pi
(FOR THOSE WHO ENABLED SSH IN STEP 0.3.1)
To SSH into your Pi, you will need to find its IP address. This can be done with nmap
using the command:
nmap 192.168.1.0/24
In the output, search for the device which has port 22 open. There may be multiple devices with this port open, so be careful. To check, you can try doing:
ssh [email protected]
where “pi” is the username you set for the Raspberry Pi (“pi” is the default) and 192.168.X.X is whatever IP address you think the Pi might be on.
It may warn you that the authenticity of the device cannot be verified. Type ‘yes’. Once it successfully logins, you may return to Step 0.4.
Step 1: Prepare matrix-docker-ansible-deploy
Now that we have the Pi finished, we will want to clone the matrix-docker-ansible-deploy
repository to our computer.
You may also follow along with the official documentation.
Step 1.1: Clone matrix-docker-ansible-deploy
Open up terminal on your computer (not the Pi) and do:
git clone https://github.com/spantaleev/matrix-docker-ansible-deploy.git && cd matrix-docker-ansible-deploy
If you do not have git
installed yet, install it now with:
sudo apt install git
Step 1.2: Configure your Matrix components
Within the matrix-docker-ansible-deploy
directory, we want to create a new directory to hold all of our Ansible playbook configuration files.
mkdir inventory/host_vars/matrix.<your domain>
Then we want to grab a sample config file provided with the playbook.
cp examples/vars.yml inventory/host_vars/matrix.<your domain>/vars.yml
Open the file you just copied. Make sure to randomly generate secrets and keys with something like:
pwgen 64
Afterward, you can add whatever services you want to install. Please see the GitHub repository to see what you can install and how.
For the Raspberry Pi, though, there is one step that we MUST do: add the line:
matrix_architecture: arm64
to our config so we build and pull Docker images belonging to our current architecture. Please be aware that some of the bridges and tools in the Ansible playbook may be for x86_64
only! Check with each project to make sure it can run on arm64
systems.
Step 1.3: Configure your Matrix hosts file
Return to the matrix-docker-ansible-deploy
directory. Now we want to configure the what host this playbook to install its components on. Copy the example hosts file provided by the playbook.
cp examples/hosts inventory/hosts
Now edit inventory/hosts
. At the bottom, it should look like:
[matrix_servers]
matrix.<your-domain> ansible_host=<your-server's external IP address> ansible_ssh_user=root
- Replace
<your-domain>
with the domain you are hosting Matrix on. - Replace
<your-server's external IP address>
with the IP address of your Raspberry Pi. If you do not know your Raspberry Pi’s IP address, you can find it using eitherifconfig
on the Pi itself or using the instructions in Step 0.4.1. - Replace
root
with the username of your Pi user and appendbecome=true become_user=root
. - Also append the text below to the end. We setting up Cloudflare Tunnel in a bit, just hang on and come back when you’re done.
ansible_ssh_pipelining=False matrix_coturn_turn_external_ip_address=<cloudflare tunnel address>
Step 2: Configuring Cloudflare with Tunnels
Step 2.1: Setup Cloudflare DNS
If you haven’t already, go to Cloudflare, register an account, and add your domain on their site. Go to your domain registrar and replace the nameservers for your domain with the ones provided by Cloudflare.
Step 2.2: Install cloudflared
Go back to your Raspberry Pi. In its terminal, we need to get cloudflared
. Unfortunately, it is not found in the apt repository, so we will have to download it ourselves using the command:
wget https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-arm64.deb
If you are using and x86_64
/amd64
system, replace arm64
with amd64
.
With the package downloaded on our Raspberry Pi, we can install it by writing:
dpkg cloudflared-linux-arm.deb
Step 2.3: Create a Cloudflare Tunnel
To create a persistent tunnel, we need to log into Cloudflare.
cloudflared tunnel login
Click the link after providing your credentials. You may need to CTRL+Left click on some terminals. After following the instructions provided, it will generate a cert.pem
in the /etc/cloudflared
directory.
Now we create our tunnel with:
cloudflared tunnel create <whatever name you want>
It will output a Cloudflare Tunnel UUID. Remember it.
Step 2.4: Configure the tunnel
Create and edit the tunnel config with:
sudo nano /etc/cloudflared/config.yml
You will want to create a configuration like this for now:
tunnel: <TUNNEL UUID>
credentials-file: /home/<USERNAME>/.cloudflared/<TUNNEL UUID>.json
originRequest:
originServerName: <YOUR DOMAIN>
ingress:
- hostname: "*.<YOUR DOMAIN>"
service: http://localhost:80
- service: http_status:404
We are setting http://localhost:80
for now so Let’s Encrypt can verify that we own the domains and give us SSL.
Step 2.5: Create CNAME records for your Matrix domains
You may use the Cloudflare dashboard or CLI to create CNAME records.
In the CLI, we can run this command:
cloudflared tunnel route dns <UUID or NAME> <DOMAIN>
In <DOMAIN>
, you will want to put the following:
<YOUR DOMAIN>
matrix.<YOUR DOMAIN>
element.<YOUR DOMAIN>
Lastly, we can use this command to start our tunnel:
cloudflared tunnel run <TUNNEL UUID or NAME>
Make sure to go back to Step 1.3 and add your tunnel UUID.
Step 2.6: Set up cloudflared
as a service
Using systemctl
, we can run cloudflared
as a service so we can run it in the background.
cloudflared service install && systemctl start cloudflared
Step 3: Run the Ansible playbook
Now we can get around to generating our Matrix services. Return to your computer.
Step 3.1: Install Ansible
If you haven’t already, you will need to install Ansible on your computer (again, not the Pi). You will need an ansible-core
version higher than 2.7.1 according to the playbook, but I would install the latest. You will not be able to install the latest from the default app respositories, so we will add Ansible’s PPA.
sudo apt-add-repository ppa:ansible/ansible && sudo apt update
Then we install Ansible.
sudo apt install ansible
Step 3.2: Set up the playbook
cd
back into the matrix-docker-ansible-deploy
directory and run the playbook command:
ansible-playbook -i inventory/hosts setup.yml --tags=setup-all
And now wait another hot minute for everything to get set up for you.
Step 4: Configure Cloudflare tunnels (again) for SSL
Unfortunately, you will have to change the Cloudflare Tunnel port from 80 to 443 and back every time your SSL certificates are up for renewal. I have not found a way within the playbook to automatically renew SSL with Cloudflare Tunnels beyond just manually using certbot.
Change service in:
- hostname: "*.<YOUR DOMAIN>"
service: http://localhost:80
to:
- hostname: "*.<YOUR DOMAIN>"
service: https://localhost:443
Save then restart the tunnel by running:
sudo systemctl restart cloudflared
Step 5: Start Matrix services with the playbook
And now, the moment of truth!
ansible-playbook -i inventory/hosts setup.yml --tags=start
Once this command successfully finishes, we can finally access our Matrix homeserver using the Element client at element.<your domain>
. Woo hoo!
But how do we make an account?
Step 6: Post-Installation
Creating an account
Using the Ansible playbook, we can create our first account.
ansible-playbook -i inventory/hosts setup.yml --extra-vars='username=<your-username> password=<your-password> admin=<yes|no>' --tags=register-user
By default, you are not allowed to create an account on your homeserver. This is clearly to prevent random people from creating accounts on your homeserver. If you want randos joining your homeserver, then you can enable it in your vars.yml
that you created earlier in Step 1.2 by adding:
matrix_synapse_enable_registration: true
Token-based authentication
If you want to allow people to register themselves, but still maintain control on who can register, you can use registration tokens.
In vars.yml
, insert these config options:
matrix_synapse_enable_registration_without_verification: true
matrix_synapse_registration_requires_token: true
To create these tokens, we’re going to need either Synapse Admin or matrix-registration-bot. This guide will use the bot.
Add these to your vars.yml
.
matrix_bot_matrix_registration_bot_enabled: true
matrix_bot_matrix_registration_bot_bot_access_token: <create a new user and get their access token>
You will need to create a new user to serve as a bot. In Element, the access token can be found by going to “All Settings”, “Help & About”, then clicking “Access Token” under “Advanced” to reveal the currently-logged-in user’s access token.
Federation
I have not tried federating with another server yet, but I have gotten the Federation Tester to work.
You will need to create another CNAME in your DNS for this to work. This site uses the CNAME matrix-federation.wenkdth.org
for federation.
In your vars.yml
, write:
matrix_server_fqn_matrix_federation: "matrix-federation.{{ matrix_domain }}"
matrix_federation_public_port: 8448
Do not touch {{ matrix_domain }}
, that is a variable that will automatically put whatever you set matrix_domain
to there.
Since Synapse will be listening to port 8448 for federation traffic, we will need to edit our Cloudflare Tunnel to forward traffic there.
Under ingress:
, append:
- hostname: matrix-federation.<YOUR DOMAIN>
service: https://localhost:8448
originRequest:
originServerName: matrix.<YOUR DOMAIN>
I’m not sure why originServerName
works, but it does.
Make sure to save then restart the Cloudflare Tunnel.
Now we need to do something a little messy. Go into /home/locke/matrix-docker-ansible-deploy/roles/matrix-base/templates/static-files/well-known
and edit matrix-server.j2
to:
#jinja2: lstrip_blocks: "True"
{
"m.server": "{{ matrix_server_fqn_matrix_federation }}:443"
}
This allows traffic to point to the right area. Simply setting the federation port in vars.yml
to 443 will not work since that port is taken by Synapse.
If you would like to test your domain’s federation, use the try out the Matrix Federation Tester. Make sure you use your base domain (not matrix.<your domain>
).
If you want to set a whitelist of federated serves (so you don’t have people joining massive Matrix.org rooms and killing your poor Raspberry Pi), you can use something along the lines of:
matrix_synapse_federation_domain_whitelist:
- mozilla.modular.im
- halogen.city
- kde.org
- matrix.tchncs.de
Add and remove domains as you wish.
You may also limit joining federated rooms by complexity with:
matrix_synapse_configuration_extension_yaml: |
limit_remote_rooms:
enabled: true
complexity: 1.0
Higher complexity means you’re going to have to deal with bigger rooms, thus more RAM.
Step 7: Invite people to join your server!
So now you’ve got a working Matrix server! Tell your friends about this article, maybe challenge them to set up their own Matrix server to federate with! The fediverse is your oyster!
Expect this article to be updated now and then for any big errors I might have made :).