Matrix Homeserver As Hidden Service Over Tor (Synapse)

Matrix as a hidden service over Tor can increase privacy.
You already learned during our “hidden service” series:

that Tor is not an invisible cape. It’s not full-anonymity.
Using Tor you can maintain better privacy and OPSEC.
It’s a better protection against metadata leaks if you use it carefully.

With this solution you may expect:

  • Encrypted group and one-to-one text chat (end-to-end)
  • Hidden service access via Tor (.onion only)
  • PostgreSQL backend
  • Invite-only registration
  • Element Web client

Install Synapse And Its Dependencies

Synapse is written mainly in Python. You will install it in a virtual environment.
The Onion Router (tor) routes the traffic through the hidden services.
PostgreSQL is a great choice for database backend.

sudo apt install -y python3 python3-pip python3-venv tor postgresql libpq5 libpq-dev

Create a Python virtual environment for Synapse.
Do it as an ordinary user (non-root):

mkdir -p ~/matrix-synapse
cd ~/matrix-synapse
python3 -m venv .venv

Activate the virtual environment and install Synapse.

source .venv/bin/activate
pip install --upgrade pip
pip install "matrix-synapse[postgres]"

Configure The Tor Hidden Service

Edit Tor’s config:

sudo nano /etc/tor/torrc

Add:

HiddenServiceDir /var/lib/tor/matrix/
HiddenServicePort 80 127.0.0.1:8008

Restart Tor:

sudo systemctl restart tor

Check your new .onion address:

sudo cat /var/lib/tor/matrix/hostname

The file contains your .onion address – this will become your server_name in Synapse.

Generate A Synapse Initial Configuration

Synapse generates unique keys with running the initial configuration.
Replace with your .onion address from /var/lib/tor/matrix/hostname.

python3 -m synapse.app.homeserver \
--server-name yourhiddenchat.onion \
--config-path homeserver.yaml \
--generate-config \
--report-stats=no

The homeserver configuration is in the directory where you ran the command.

Configure The PostgreSQL Server For Synapse

Change to postgres user and create the necessary

  • user
  • database
sudo -u postgres bash
createuser --pwprompt synapse_user
createdb --encoding=UTF8 --locale=C --template=template0 --owner=synapse_user synapse
exit

Edit the database section in homeserver.yaml:

database:
name: psycopg2
args:
user: synapse_user
password: "your_password"
dbname: synapse
host: localhost

Configure Your Synapse Homeserver

The minimal homeserver.yaml can look like:

server_name: "yourhiddenchat.onion"
public_baseurl: "http://yourhiddenchat.onion"
listeners:
- port: 8008
tls: false
type: http
x_forwarded: false
bind_addresses: ['127.0.0.1']
resources:
- names: [client]
compress: false
report_stats: false
registration_shared_secret: "long_random_secret"
federation_domain_whitelist: []
federation_ip_range_blacklist: ["0.0.0.0/0"]
enable_registration: false
registration_requires_token: true

Don’t forget to change your .onion address in the URLs.

Start Your Synapse Server

Still in the Python virtual environment the synctl tool is for basic operations.

To start the Synapse server use the start command:

synctl start

Register your first user interactively:

register_new_matrix_user -c homeserver.yaml http://localhost:8008

Install Element Web

Element Web is a lightweight matrix client.

Limitations through the Tor browser:

  • No WebRTC = no media service.
  • No audio.
  • No video.

On The Onion Network, these are not real limitations.

  1. On Tor Browser, avatars may not be displayed, but text chat remains fully functional.
  2. Brave can handle media fully, but only use it for your private .onion server to maintain privacy.
  3. Element Web requires JavaScript – only load your own .onion instance to avoid exposure.

Check the current release on:

Install Nginx and download Element Web:

# on the same server as Synapse
sudo apt install -y nginx
cd /var/www/
sudo wget https://github.com/element-hq/element-web/releases/download/v1.12.18/element-v1.12.18.tar.gz
sudo tar xzf element-v1.12.18.tar.gz
sudo mv element-v1.12.18 element

Open the site’s configuration file:

sudo nano /etc/nginx/sites-available/element-web.conf

Add the site config:

server {
listen 127.0.0.1:8080;
server_name your-element.onion;
root /var/www/element;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
}

Enable the site:

sudo ln -s /etc/nginx/sites-available/element-web.conf /etc/nginx/sites-enabled/
sudo systemctl reload nginx

Verify and reload the Nginx configuration:

sudo nginx -t && sudo systemctl reload nginx

Set up the Element Web via /var/www/element/config.json.
Do not forget to replace the example with your .onion address.

{
"default_server_config": {
"m.homeserver": {
"base_url": "http://yourhiddenchatxxxxxxxx.onion",
"server_name": "yourhiddenchatxxxxxxxx.onion"
}
},
"disable_custom_urls": true,
"disable_guests": true,
"brand": "Private Matrix over Tor"
}

Enable the Element Web site in the torrc file:

# Synapse onion
HiddenServiceDir /var/lib/tor/matrix/
HiddenServicePort 80 127.0.0.1:8008
# Element onion
HiddenServiceDir /var/lib/tor/element/
HiddenServicePort 80 127.0.0.1:8080

Possible Improvements

This system is just scratching the surface of a private communication platform.
With creativity and discipline it can be a whole framework.

  1. You can add a Systemd Synapse service unit (enable / auto start the service)
  2. Automated, quick backup service
  3. Admin maintenance scripts (e.g. removing idle users, rooms)
  4. Fail2ban and UFW minimal ruleset (limit external noise)

Don’t stop at the basics.
Default configuration doesn’t survive in the wild.

Closing Thoughts

Running your chat over Tor isn’t about going dark – it’s about reducing exposure.
You control who can register, who can connect, and how data flows.

It’s a place for privacy-centric network design, and a reminder:

Visibility is a personal choice.


Discover more from Tom's IT Cafe

Subscribe to get the latest posts sent to your email.

Leave a comment