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-synapsecd ~/matrix-synapsepython3 -m venv .venv
Activate the virtual environment and install Synapse.
source .venv/bin/activatepip install --upgrade pippip 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 bashcreateuser --pwprompt synapse_usercreatedb --encoding=UTF8 --locale=C --template=template0 --owner=synapse_user synapseexit
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: falsereport_stats: falseregistration_shared_secret: "long_random_secret"federation_domain_whitelist: []federation_ip_range_blacklist: ["0.0.0.0/0"]enable_registration: falseregistration_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.
- On Tor Browser, avatars may not be displayed, but text chat remains fully functional.
- Brave can handle media fully, but only use it for your private .onion server to maintain privacy.
- 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 Synapsesudo apt install -y nginxcd /var/www/sudo wget https://github.com/element-hq/element-web/releases/download/v1.12.18/element-v1.12.18.tar.gzsudo tar xzf element-v1.12.18.tar.gzsudo 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 onionHiddenServiceDir /var/lib/tor/matrix/HiddenServicePort 80 127.0.0.1:8008# Element onionHiddenServiceDir /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.
- You can add a Systemd Synapse service unit (enable / auto start the service)
- Automated, quick backup service
- Admin maintenance scripts (e.g. removing idle users, rooms)
- 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.