Tired of corporate servers watching every chat?
Host your own Matrix on Tor, no public IP, zero tracking.
By running Matrix over Tor, you eliminate exposure to public servers and keep your chats private.
This isn’t just another guide – it’s a battle‑tested recipe for keeping your Matrix chats private on Tor.
This guide walks you through deploying a self-contained Matrix Synapse server accessible only via a Tor .onion address.
No public exposure. No certificates. Just encrypted chat through the hidden network.
What you’ll learn
- Spin up Synapse in minutes with Python‑venv
- Expose it only via a .onion address
- Lock down registration to invite‑only users
- Run Element Web on the same hidden service
What you’ll get
- Encrypted text chat (end-to-end)
- Hidden service access via Tor
- PostgreSQL backend
- Invite-only registration
- Element Web client
1. Install Synapse And Dependencies
Dependencies
sudo apt install -y python3 python3-pip python3-venv tor postgresql-17 libpq5 libpq-dev
Create Environment
mkdir -p ~/matrix-synapse
cd ~/matrix-synapse
python3 -m venv .venv
source .venv/bin/activate
pip install --upgrade pip
pip install "matrix-synapse[postgres]"
2. 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
sudo cat /var/lib/tor/matrix/hostname
The file contains your new .onion address – this will become your servername in Synapse.
3. Configure Synapse
Generate a new 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
4. Set Up PostgreSQL
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
5. Synapse Configuration for Tor
Minimal homeserver.yaml:
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
6. Start Synapse
synctl start
Register the first user:
register_new_matrix_user -c homeserver.yaml http://localhost:8008
7. 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 are not 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:
https://github.com/element-hq/element-web/releases
Install Nginx and download Element Web:
# on the same server as Synapse
sudo apt install nginx
cd /var/www/
sudo wget https://github.com/vector-im/element-web/releases/download/v1.11.83/element-v1.11.83.tar.gz
sudo tar xzf element-v1.11.83.tar.gz
sudo mv element-v1.11.83 element
Open the site 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
Reload the Nginx configuration:
sudo nginx -t && sudo systemctl reload nginx
Set up the Element Web via /var/www/element/config.json:
{
"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:
# 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
8. Possible Improvements
- SystemD synapse service unit (enable / auto start the service)
- SystemD backup service unit (automated, quick backups)
- Systemd backup timer (schedule the backups)
- Admin maintenance scripts (e.g. removing idle users, rooms)
- Fail2ban or ufw minimal ruleset (limit external noise)
9. Closing Thoughts
Running 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 lab for privacy-centric network design, and a reminder:
Visibility is a choice.
Privacy is not hiding.
It’s choosing when to be seen.
This is pretty much my setup, but I can’t get encrypted v3 notifications running… which 100% defeats the purose of this. Every single message, that’s not longer than 150 chars can be read in clear text bei apns and fcm, as soon as some mobile client is in use.
Unified push isn’t encrypted either afaik.
Each device gets a unique token assigned for push notifications, combined with fingerprinting etc and they can read every single message as well as profile you. Graphene phones with ntfy are the only exception… but then again, as long as I can’t use encrypted notification, or turn off notifications all together, all of this is basically a fools errand.
LikeLike
Thank you for the detailed comment – this is an important point that doesn’t get discussed enough.
You’re right about the current limitations: when a Matrix client on iOS or Android relies on APNS/FCM, the push payload must remain very small and cannot contain end-to-end encrypted content. That means metadata about a message (or in extreme cases a short preview) can end up visible to the push provider. It’s a structural limitation of the mobile OS push systems, not Synapse itself.
Encrypted v3 notifications were introduced to address this, but support is still incomplete across the ecosystem. Some clients have partial implementations, others not at all, and mobile OS constraints make it slow to roll out in a fully private form.
Today, the realistic options look like this:
1. Use a client + setup that supports encrypted notification attempts, understanding that the ecosystem is catching up.
2. Disable notifications on mobile and rely on polling. Less convenient, more private.
3. Use UnifiedPush where possible, but yes – the encryption story there is not perfect either.
4. GrapheneOS + local notification handling (for example with ntfy) is currently the most privacy-preserving setup on mobile, as you mentioned.
5. Desktop clients remain the most private since they don’t rely on third-party push frameworks.
You’re absolutely correct that push notification design on mainstream mobile platforms remains a weak link for strong end-to-end privacy. Synapse over Tor solves transport-level exposure, but push delivery is still limited by what Apple and Google allow clients to do.
I still think hosting your own Synapse – especially over Tor – gives you significant privacy and control over metadata, even if mobile notifications remain a compromise. But yes, it’s important to be transparent: for the strongest privacy, the only full solution today is disabling notifications or using a hardened Android environment.
Thanks again for raising the topic. It’s a critical part of understanding the real-world privacy model of Matrix outside the marketing claims.
LikeLike