negative zero

Bridging Signal to Matrix

2021 November 4

[matrix] [messaging] [privacy] [signal] [tech] [tutorial]

Update: Since writing this post, I have changed my approach to using Signal to using the official desktop client, first with Axolotl as the primary client, and then using signal-cli when I encountered issues with Axolotl.

If you don't care about my personal relationship with Signal and just want to set up the bridge, jump down to Preparing the PostgreSQL Database.

Why I left Signal

I dislike Signal for a number of reasons, including...

This last reason is what finally pushed me to stop using Signal a couple years ago when I got rid of my phone. No phone, no Signal. So saith Moxie.

Why use Signal again?

While I'm trying to promote Matrix as the general-purpose private messenger we should all be using, the truth of the matter is that Signal is (relatively) popular among normal people, and Matrix lives in obscurity.

The other truth of the matter is that Signal is very easy to use, as long as you use it how Moxie wants you to use it.

My primary method of communication is unencrypted email with people's Gmail accounts. Obviously, this is not by my choice.

Some of these people I email have Signal installed on their phones. They actually do use encryption sometimes. But they don't use it with me because I don't use the same encrypted platform they use.

Even if Google has the power to collect a lot of Signal metadata and potentially compromise users entirely, I'd still prefer it over unencrypted Gmail where Google certainly has access to all the data and metadata in all cases.

Also, there's a Matrix bridge!


One of the goals of Matrix is bridging: You should be able to use Matrix to get all your conversations in one place.

This suits me better than Signal-Desktop anyway, since (as mentioned above) Signal Desktop is not a proper standalone client, and I generally try to avoid adding more Electron to my life.

Plus, I've started using Matrix a lot, so it's convenient to just add more things to it.

Thus, I set up the mautrix-signal bridge.


Matrix bridges necessarily break end-to-end encryption, since the bridge needs to be able to read the message content to translate it to the other protocol. I'd love it if we could have fully E2EE Matrix bridges, but these would require also bridging Matrix's Megolm+Olm hybrid encryption protocol with whatever's on the other side, and without rewriting the Matrix spec, that seems like a hard problem if not impossible. (As an aside, I'd love to see an XEP for Megolm+Olm on the XMPP side so Matrix and XMPP could be bridged with proper E2EE. I don't know how possible or difficult that would be.)

Obviously, this is third-party software which is unapproved by the Signal developers. I don't really expect that Signal will take legal action against the project or put in the resources to identify and ban users of the software, but it's very possible at any time that Signal will make changes to break this interoperability.

Fortunately, I don't need the bridge to be super stable, since I don't rely on Signal for anything important, and if I run the bridge on my own server (which is a computer I physically own and control), it doesn't really matter if my server can read the messages, as long as the Signal server can't.

Preparing the PostgreSQL Database

I'll assume you already have PostgreSQL installed and running (since you're hopefully using it for Synapse).

sudo -u postgres psql

(Use the name of the postgres user. In my case, it is "postgres".)

Now, in the psql prompt, create a new user for the mautrix-signal bot:

CREATE USER mautrix_signal_user WITH PASSWORD 'PutAStrongPasswordHere';

This password will need to be put into a config file later, so I recommend making it just alphanumeric (but long) without any symbols to avoid issues later on.

CREATE DATABASE mautrix_signal;

ALTER DATABASE mautrix_signal OWNER TO mautrix_signal_user;

(I'm not very experienced with database management. There's probably a better way to do this.)

Now, you can quit with \q.

Setting up mautrix-signal


mautrix-signal uses signald as the Signal backend. Since I'm running Debian Testing on my server, I just used the Debian package provided by signald.


First, download the key used to sign the signald packages:

curl | sudo tee /usr/share/keyrings/signald.asc

Then, add the signald Debian repo to /etc/apt/sources.list.d/:

echo "deb [signed-by=/usr/share/keyrings/signald.asc] unstable main" | sudo tee /etc/apt/sources.list.d/signald.list

Now, check for updates and install signald:

sudo apt update && sudo apt install signald


If you're not on Debian, signald provides instructions for installing from source or installing with Docker (if you're into that sort of thing).

Run signald

If you have a systemd service, you can start signald with

sudo systemctl start signald

You can also enable it to start on boot.

sudo systemctl enable signald


Now, we can install and configure the bridge.

You can install it with Docker if you want.

I don't, so let's talk about how to install it with pip.

We're going to be setting it up with systemd.


We need Python 3.8+, pip, and virtualenv.

On Debian...

sudo apt install python3 python3-pip python3-virtualenv

On Fedora...

sudo dnf install python3 python3-pip python3-virtualenv

E2BE Dependencies

Even though my Matrix homeserver is running on the same device as the bridge, and the bridge has to break the E2EE, I still wanted to enable end-to-bridge encryption on the Matrix side. mautrix-signal supports this, but it requires some extra dependencies, including python-olm, which (when installed via pip) needs to be built on your device. In order to do this, we need some extra dependencies installed.

On Debian...

sudo apt install build-essential libolm-dev python3-dev

On Fedora...

sudo dnf install gcc libolm-devel python3-devel

Install the Bridge

First, create a user for the bridge. (I'm using the --shell option here so that we can login and set things up as the user.)

sudo adduser --system mautrix-signal --home /opt/mautrix-signal --shell /bin/bash

Now, switch to the new mautrix-signal user.

sudo su mautrix-signal

You should now be logged in as the mautrix-signal user. Go to your home directory (which should be /opt/mautrix-signal). This is where we'll set up the bridge bot.

cd ~

Now, let's set up the pip environment and install the bridge software.

virtualenv -p /usr/bin/python3 .

(Make sure to include the dot at the end.)

source ./bin/activate

Now, we'll actually install the bridge and its dependencies. Here's the command at time of writing, including E2BE support:

pip install --upgrade mautrix-signal phonenumbers qrcode Pillow signalstickers-client asyncpg python-olm pycryptodome unpaddedbase64

(You can also use pip install --upgrade mautrix-$bridge[all] to install all optional dependencies. I chose not to do this because one of the optional dependencies is prometheus_client, which I don't want installed on my system.)

Configure the Bridge

cp example-config.yaml config.yaml

Now, modify config.yaml to fill in the settings you want. Make sure to change the following...

When you're done with the config file, run

python -m mautrix_signal -g

This will create a registration.yaml file.

Now, modify your Synapse homeserver.yaml file to tell Synapse where to find this this registration file.

(If you installed Synapse through the Debian package, this is probably located at /etc/matrix-synapse/homeserver.yaml.)

app_service_config_files: [ "/opt/mautrix-signal/registration.yaml" ]

Setting up the systemd Service

Create a systemd service file at /etc/systemd/system/mautrix-signal.service

Description=mautrix-signal bridge

ExecStart=/opt/mautrix-signal/bin/python -m mautrix_signal


Reload systemd.

sudo systemctl daemon-reload

Start mautrix-signal when you're ready.

sudo systemctl start mautrix-signal

Enable mautrix-signal if you want it to start at boot.

sudo systemctl enable mautrix-signal


By default with this configuration, signald and mautrix-signal run as different users. This can cause issues. If you run into issues because of this, try running signald as the mautrix-signal user. If you're using systemd, you can

sudo systemctl edit signald

### Editing /etc/systemd/system/signald.service.d/override.conf
### Anything between here and the comment below will become the new contents of the file

ExecStartPre=/usr/bin/chown -R mautrix-signal /var/lib/signald

This will run signald as the mautrix-signal user, and ensure the signald files are assigned to the mautrix-signal user.

Using the Signal Bridge

(I'll assume this part is done on a different computer than your server.)

Start a new room on Matrix and invite @signalbot:<your domain>.

Register for a Signal Account

For this, you must be able to receive an SMS message or a call on a phone number. (I have a number through which I can access via XMPP.)

(It's not important for your phone number to be in your own country.)

In the chat with the signalbot, issue the command:

register <your phone number>


The Signal bot may respond "Unhandled error while handling command:" with an error log. If the final line of this error log is...

mausignald.errors.ResponseError: CaptchaRequiredError: a captcha token is required to register

...then it means you are required to fulfill a Google reCAPTCHA. (See the signald docs for more information.)

In this case, go to for the CAPTCHA. If you want to avoid non-free software, you can use librecaptcha:

pip install --user librecaptcha

Get the source from by either opening it in a browser and viewing the source (Ctrl+U) or downloading the page, e.g., with curl:


Find the line that says data-sitekey="<some string>" and copy the string in the quotation marks.

Now, run librecaptcha:

librecaptcha <the data-sitekey value> --gui

This still has the ethical issues of dependence on Google, forcing users to provide free labor to Google, and enabling Google's tracking of users (particularly punishing users who are not logged into Google accounts or who try to use the web anonymously)... but hey, at least we can use free software.

When Google finally accepts your CAPTCHA, you should get a link like "signalcaptcha://<some long CAPTCHA token>". Copy that CAPTCHA token (but not the signalcaptcha:// part).

Try again to register, but this time provide the CAPTCHA token before the phone number:

register --captcha <the CAPTCHA token> <your phone number>

SMS Code

You should get an SMS message (or a call) at the phone number provided containing a 6-digit code. The Signal bot will say "Register SMS requested, please enter the code here." Reply to that message with the code.


If all goes well, the bot will reply "Successfully logged in as <your phone number>."

Adding a Signal Contact

In the room with the Signal bridge bot, send a message:

pm <phone number of your contact>

The Signal bot will invite you to a new Matrix room with that contact.

Having Someone Else Add You

If autocreate_contact_portal is set to true in your bot's config, other Signal users should be able to message you first. In this case, the Matrix bot will create a new room with that person and invite you to join it.

For other commands (such as linking the bridge as a secondary device or verifying safety numbers, type "help" in the Signal bridge bot room.)