Signal is good. WhatsApp claims end-to-end encryption. Telegram is… complicated. But all of them share one architectural fact: someone else runs the server. Your contact list, your message metadata, your group memberships, your online status — all of it lives on infrastructure you don’t control, in a jurisdiction you didn’t choose, operated by a company whose business model may not align with your privacy interests forever.
Matrix is an open protocol for decentralised, encrypted communication. Think of it as email for messaging — anyone can run their own server, and servers can talk to each other (federation), but you own your data and your encryption keys. The reference server implementation is called Synapse, and the most polished client is Element.
Run Synapse on a Swiss VPS and you get something no commercial messaging service offers: end-to-end encrypted messaging on infrastructure you own, in a country whose data protection laws — the FADP — are among the strictest on Earth, with no advertising company in the loop and no backdoor mandate in the legal framework.
This guide builds a production Matrix homeserver from scratch on Debian 12. It’s written for someone who can SSH into a server and edit config files. Where we hit problems during the real build, we left them in.
Before we touch the keyboard, the jurisdiction question matters more for messaging than almost any other service.
What Swiss law gives you:
• The Federal Act on Data Protection (FADP) treats personal data — including communication metadata — as fundamentally protected. There’s no mass-surveillance mandate equivalent to the US FISA Section 702 or the UK’s Investigatory Powers Act.
• Switzerland is not in the EU, not in the Five/Nine/Fourteen Eyes intelligence-sharing alliances, and not subject to EU data-retention directives.
• Law enforcement access requires a Swiss court order. Foreign government requests go through Mutual Legal Assistance Treaties (MLAT), which adds layers of judicial review and takes months, not minutes.
• There’s no law requiring hosting providers to build decryption backdoors or weaken encryption. If your server runs end-to-end encryption (and Matrix does), even a lawful seizure of the hardware yields ciphertext.
None of this makes your server “above the law.” It means the law is written by a country that considers privacy a constitutional right, not a feature toggle. That’s the foundation you’re building on.
What hosting in Switzerland gives you technically:
• Swiss data centres are connected to major European internet exchanges with sub-10ms latency to Frankfurt, Amsterdam, and Milan.
• The power grid runs largely on hydroelectric — cheap, green, reliable.
• Physical security standards in Swiss facilities are among the highest globally, often exceeding Tier III requirements.
The finished system has four layers:
| Component | Job |
|---|---|
| Debian 12 | The operating system. Stable, minimal, predictable. |
| Synapse | The Matrix homeserver. Handles accounts, rooms, encryption key exchange, and federation. |
| PostgreSQL | The database. Synapse’s default SQLite works for testing but buckles under real use. |
| nginx + Certbot | Reverse proxy with HTTPS. Terminates TLS, handles .well-known delegation, proxies to Synapse. |
| Element Web | The browser-based client. Optional but useful — users can also use Element desktop/mobile apps. |
Placeholders — replace with your own:
| Placeholder | Meaning |
|---|---|
| matrix.example.com | The server hostname (where Synapse actually runs) |
| example.com | Your Matrix domain (the @user:example.com part) |
| chat.example.com | Where Element Web is served (optional) |
| 203.0.113.5 | Your server’s public IP |
Provision a Swiss VPS with at least:
• 2 vCPUs, 4 GB RAM (Synapse is memory-hungry during federation; 2 GB is the absolute floor for a personal server, 4 GB for a small team)
• 40 GB SSD minimum (media uploads and the database grow fast; plan 100 GB+ for anything beyond personal use)
• Debian 12 minimal install
apt update && apt full-upgrade -y
apt install -y curl wget gnupg lsb-release ca-certificates sudo ufw nano
timedatectl set-timezone Europe/Zurich
Lock down everything except what Matrix needs:
ufw default deny incoming
ufw default allow outgoing
ufw allow 22/tcp # SSH — restrict to your IPs in production
ufw allow 80/tcp # HTTP (certificate validation + redirect)
ufw allow 443/tcp # HTTPS (client connections)
ufw allow 8448/tcp # Matrix federation (server-to-server)
ufw enable
Port 8448 is critical. That’s how other Matrix homeservers reach yours for federation — the ability to message users on other servers. Without it, you’re an island.
Production hardening: replace
ufw allow 22/tcpwith rules for your specific management IPs only. An open SSH port invites brute-force noise.
Synapse ships with SQLite support, but the official documentation warns against it for any real deployment. SQLite locks the entire database on writes — a single busy room can stall everything. PostgreSQL handles concurrent access properly.
apt install -y postgresql postgresql-contrib
sudo -u postgres psql <<'SQL'
CREATE USER synapse_user WITH PASSWORD 'STRONG_DB_PASSWORD';
CREATE DATABASE synapse
ENCODING 'UTF8'
LC_COLLATE='C'
LC_CTYPE='C'
template=template0
OWNER synapse_user;
SQL
⚠️ Gotcha — the collation. The
LC_COLLATE='C'andLC_CTYPE='C'are not optional. Synapse requires the C locale for its database. If you create the database with your system’s default locale (often en_US.UTF-8), Synapse will refuse to start with a cryptic locale mismatch error. Usingtemplate=template0is what allows overriding the locale from your system default.
Generate your database password beforehand:
openssl rand -base64 24
Use the official Matrix.org Debian repository. The version in Debian’s own repos lags significantly:
wget -qO /usr/share/keyrings/matrix-org-archive-keyring.gpg \
https://packages.matrix.org/debian/matrix-org-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/matrix-org-archive-keyring.gpg] \
https://packages.matrix.org/debian/ $(lsb_release -cs) main" \
> /etc/apt/sources.list.d/matrix-org.list
apt update
apt install -y matrix-synapse-py3
During installation, the package asks for your server name. Enter example.com (your Matrix domain, not the machine hostname). This is permanent — changing it later means starting over with a fresh database.
⚠️ Gotcha — server_name is forever. The server_name you set during installation becomes part of every user ID (@user:example.com), every room ID, and every event your server signs. It’s cryptographically baked into the federation protocol. You cannot change it without losing all history and all user identities. Choose carefully. Use your main domain, not the machine hostname.
The main config file is /etc/matrix-synapse/homeserver.yaml. Open it and set the critical sections:
nano /etc/matrix-synapse/homeserver.yaml
Database — switch from SQLite to PostgreSQL:
database:
name: psycopg2
args:
user: synapse_user
password: "STRONG_DB_PASSWORD"
database: synapse
host: localhost
cp_min: 5
cp_max: 10
Listeners — set up the client and federation ports:
listeners:
- port: 8008
tls: false
type: http
x_forwarded: true
bind_addresses: ['127.0.0.1']
resources:
- names: [client, federation]
compress: false
Why bind to 127.0.0.1 only: Synapse listens on localhost, and nginx (the reverse proxy) handles all external connections with proper TLS. Synapse never touches the public network directly. The
x_forwarded: truetells Synapse to trust the X-Forwarded-For header from nginx for rate limiting and logging.
Registration and security settings:
# Disable open registration — you control who gets accounts
enable_registration: false
# Allow registration via shared secret (for admin account creation)
registration_shared_secret: "GENERATE_A_LONG_RANDOM_STRING"
# Media storage
media_store_path: /var/lib/matrix-synapse/media
max_upload_size: 100M
# URL previews (optional but nice)
url_preview_enabled: true
url_preview_ip_range_blacklist:
- '127.0.0.0/8'
- '10.0.0.0/8'
- '172.16.0.0/12'
- '192.168.0.0/16'
- '100.64.0.0/10'
- '192.0.0.0/24'
- '169.254.0.0/16'
- '198.18.0.0/15'
- '::1/128'
- 'fe80::/10'
- 'fc00::/7'
# Retention — auto-delete old messages (optional, privacy-first)
retention:
enabled: true
default_policy:
min_lifetime: 1d
max_lifetime: 365d
allowed_lifetime_min: 1d
allowed_lifetime_max: 365d
Generate the registration secret:
openssl rand -base64 32
⚠️ Gotcha — the URL preview blacklist. Without the IP blacklist above, the URL preview feature becomes a server-side request forgery (SSRF) vector. A malicious user could send a message containing
http://169.254.169.254/latest/meta-data/and your server would dutifully fetch it and preview the contents — potentially leaking cloud provider metadata. The blacklist blocks all private and link-local ranges. Don’t skip it.
nginx sits in front of Synapse, terminates TLS, and routes both client API traffic (port 443) and federation traffic (port 8448) to Synapse’s local listener.
apt install -y nginx certbot python3-certbot-nginx
Get the certificate first (nginx needs to serve port 80 for the challenge):
certbot certonly --nginx -d matrix.example.com \
--email admin@example.com --agree-tos --no-eff-email
If your Matrix domain (example.com) is hosted on the same server, get that certificate too:
certbot certonly --nginx -d example.com \
--email admin@example.com --agree-tos --no-eff-email
Now create the nginx configuration:
cat > /etc/nginx/sites-available/matrix.conf <<'NGINX'
# Redirect HTTP to HTTPS
server {
listen 80;
listen [::]:80;
server_name matrix.example.com;
return 301 https://$host$request_uri;
}
# Client API (port 443)
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name matrix.example.com;
ssl_certificate /etc/letsencrypt/live/matrix.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/matrix.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Content-Security-Policy "frame-ancestors 'none'" always;
client_max_body_size 100M;
location ~ ^(/_matrix|/_synapse/client) {
proxy_pass http://127.0.0.1:8008;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $host;
proxy_read_timeout 600s;
# Synapse responses can be large (media, sync)
proxy_buffering off;
}
}
# Federation (port 8448)
server {
listen 8448 ssl;
listen [::]:8448 ssl;
http2 on;
server_name matrix.example.com;
ssl_certificate /etc/letsencrypt/live/matrix.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/matrix.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
client_max_body_size 100M;
location / {
proxy_pass http://127.0.0.1:8008;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $host;
proxy_read_timeout 600s;
proxy_buffering off;
}
}
NGINX
ln -sf /etc/nginx/sites-available/matrix.conf /etc/nginx/sites-enabled/
rm -f /etc/nginx/sites-enabled/default
nginx -t && systemctl reload nginx
Here’s the part that trips up almost everyone. Your Matrix user IDs are @user:example.com, but Synapse runs on matrix.example.com. Other servers need to know where to find yours. This is called delegation, and it uses .well-known files served from your main domain.
You need to serve two JSON files from https://example.com:
# On the server hosting example.com, create:
mkdir -p /var/www/html/.well-known/matrix
cat > /var/www/html/.well-known/matrix/server <<'EOF'
{
"m.server": "matrix.example.com:443"
}
EOF
cat > /var/www/html/.well-known/matrix/client <<'EOF'
{
"m.homeserver": {
"base_url": "https://matrix.example.com"
}
}
EOF
Make sure your web server (on example.com) serves these with the right CORS headers. In the nginx config for example.com:
location /.well-known/matrix/ {
default_type application/json;
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods 'GET, HEAD, OPTIONS';
add_header Access-Control-Allow-Headers 'X-Requested-With, Content-Type, Authorization';
}
⚠️ Gotcha — the client file needs CORS. The
/.well-known/matrix/clientfile is fetched by web browsers (Element Web runs in the browser), so it must includeAccess-Control-Allow-Origin: *. Without it, Element shows “Can’t find your homeserver” even though the server file (fetched server-to-server, no CORS needed) works fine. This mismatch is the #1 cause of “federation works but login doesn’t” reports.
Test both endpoints:
curl -s https://example.com/.well-known/matrix/server | python3 -m json.tool
curl -sI https://example.com/.well-known/matrix/client | grep -i access-control
systemctl enable --now matrix-synapse
systemctl status matrix-synapse --no-pager
Check the logs for errors:
journalctl -u matrix-synapse -n 50 --no-pager
If it’s running, create your admin account using the registration secret:
register_new_matrix_user -c /etc/matrix-synapse/homeserver.yaml \
http://localhost:8008
It will ask for username, password, and whether to make the user an admin. Say yes to admin for your first account.
⚠️ Gotcha — registration_shared_secret must match. The
register_new_matrix_usertool reads the shared secret fromhomeserver.yaml. If you edited the file but didn’t restart Synapse, the running process has the old secret and registration fails with a generic HMAC error. Alwayssystemctl restart matrix-synapseafter config changes.
Verify the API responds:
curl -s https://matrix.example.com/_matrix/client/versions | python3 -m json.tool
You should see a JSON object listing supported Matrix spec versions.
Element is the most mature Matrix client. Installing the web version gives your users browser access without installing anything:
cd /tmp
ELEMENT_VERSION="1.11.85" # check latest at https://github.com/element-hq/element-web/releases
wget "https://github.com/element-hq/element-web/releases/download/v${ELEMENT_VERSION}/element-v${ELEMENT_VERSION}.tar.gz"
tar xzf "element-v${ELEMENT_VERSION}.tar.gz"
mv "element-v${ELEMENT_VERSION}" /var/www/element
cp /var/www/element/config.sample.json /var/www/element/config.json
Edit /var/www/element/config.json:
{
"default_server_config": {
"m.homeserver": {
"base_url": "https://matrix.example.com",
"server_name": "example.com"
}
},
"disable_custom_urls": true,
"disable_guests": true,
"brand": "Element",
"default_theme": "dark"
}
Add an nginx server block for Element (get its certificate first):
certbot certonly --nginx -d chat.example.com \
--email admin@example.com --agree-tos --no-eff-email
cat > /etc/nginx/sites-available/element.conf <<'NGINX'
server {
listen 80;
listen [::]:80;
server_name chat.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name chat.example.com;
ssl_certificate /etc/letsencrypt/live/chat.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/chat.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
root /var/www/element;
index index.html;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Content-Security-Policy "frame-ancestors 'self'" always;
add_header Strict-Transport-Security "max-age=63072000" always;
location / {
try_files $uri $uri/ /index.html;
}
}
NGINX
ln -sf /etc/nginx/sites-available/element.conf /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx
Federation is the whole point — the ability to message users on other Matrix servers. Test it:
# Check from the Matrix Federation Tester
curl -s "https://federationtester.matrix.org/api/report?server_name=example.com" \
| python3 -c "import sys,json; r=json.load(sys.stdin); print('OK' if r.get('FederationOK') else 'FAIL')"
Or visit https://federationtester.matrix.org/#example.com in a browser.
⚠️ Gotcha — federation fails but client works fine. The most common cause: port 8448 is blocked by the firewall (check
ufw status), or the .well-known/matrix/server file points to the wrong hostname or port. Federation uses a completely different discovery path than client login. Test them separately. Another common one: the SSL certificate on port 8448 doesn’t match the hostname in the server file. Use the same certificate.
A messaging server is a high-value target. Harden it beyond the defaults.
Rate limiting (already default in Synapse, but verify):
# In homeserver.yaml — these are the defaults, adjust if needed
rc_message:
per_second: 0.2
burst_count: 10
rc_login:
address:
per_second: 0.17
burst_count: 3
account:
per_second: 0.17
burst_count: 3
Fail2ban for Synapse:
apt install -y fail2ban
cat > /etc/fail2ban/filter.d/synapse.conf <<'EOF'
[Definition]
failregex = ^.* Received request: POST /_matrix/client/.*/login\b.*"request_ip": "".*$
^.* Failed password login.*remote_addr=.*$
ignoreregex =
EOF
cat > /etc/fail2ban/jail.d/synapse.conf <<'EOF'
[synapse]
enabled = true
port = 443,8448
filter = synapse
logpath = /var/log/matrix-synapse/homeserver.log
maxretry = 5
bantime = 3600
findtime = 600
EOF
systemctl restart fail2ban
Disable federation room directory (privacy measure):
By default, other servers can browse your public rooms. If you’re running a private server, disable this:
# In homeserver.yaml
allow_public_rooms_over_federation: false
Restrict room creation to admins (small team servers):
If you want tight control over what exists on your server:
# In homeserver.yaml — uncomment or add
# Only server admins can create rooms
# Regular users can still be invited to rooms
room_default_power_levels:
events_default: 0
state_default: 50
Media retention:
Left alone, Synapse stores every image, video, and file uploaded to any room you’re in — including federated media from other servers. This accumulates fast:
# In homeserver.yaml
media_retention:
remote_media_lifetime: 90d
local_media_lifetime: 365d
Your messaging server holds conversations, encryption keys, and uploaded media. Back it up properly.
What to back up:
• PostgreSQL database — the core. Contains all room state, user accounts, and message history.
• Synapse media store — /var/lib/matrix-synapse/media/ — uploaded files, images, avatars.
• Synapse config and signing key — /etc/matrix-synapse/ and especially homeserver.signing.key. Lose the signing key and your server’s federation identity is destroyed.
• Let’s Encrypt certificates — /etc/letsencrypt/ (these auto-renew, but backing them up avoids rate-limit issues during recovery).
#!/bin/bash
# /usr/local/bin/backup-matrix.sh
BACKUP_DIR="/var/backups/matrix"
DATE=$(date +%Y%m%d-%H%M)
mkdir -p "$BACKUP_DIR"
# Database
sudo -u postgres pg_dump synapse | gzip > "$BACKUP_DIR/synapse-db-${DATE}.sql.gz"
# Config and signing key
tar czf "$BACKUP_DIR/synapse-config-${DATE}.tar.gz" /etc/matrix-synapse/
# Media (incremental with rsync is better for large stores)
tar czf "$BACKUP_DIR/synapse-media-${DATE}.tar.gz" /var/lib/matrix-synapse/media/
# Retention — keep 14 days locally
find "$BACKUP_DIR" -name "*.gz" -mtime +14 -delete
Schedule it:
chmod +x /usr/local/bin/backup-matrix.sh
echo "0 3 * * * root /usr/local/bin/backup-matrix.sh" >> /etc/cron.d/matrix-backup
⚠️ Critical: local backups alone are not backups. If the server dies, the backups die with it. Copy them off-server — to a second Swiss dedicated server, to encrypted object storage, or to a BorgBackup repository. See our encrypted backup guide for a full off-server backup architecture.
Synapse on default settings works for a few users. Under real load — especially with federated rooms — it needs tuning.
PostgreSQL tuning:
cat > /etc/postgresql/15/main/conf.d/synapse.conf <<'EOF'
shared_buffers = 1GB
effective_cache_size = 3GB
work_mem = 16MB
maintenance_work_mem = 256MB
max_connections = 200
random_page_cost = 1.1
effective_io_concurrency = 200
wal_buffers = 64MB
checkpoint_completion_target = 0.9
EOF
systemctl restart postgresql
Synapse caching:
# In homeserver.yaml
caches:
global_factor: 1.0
per_cache_factors:
get_users_in_room: 2.0
get_current_state_event: 2.0
Synapse worker architecture (advanced):
For servers handling more than ~50 concurrent users or participating in large federated rooms, Synapse supports splitting work across multiple worker processes. The main bottleneck is the sync endpoint (what clients poll constantly). Moving it to a dedicated worker dramatically reduces load on the main process. This is documented in the Synapse workers documentation and is worth implementing once you hit performance limits on a single process.
A messaging server that goes down silently is worse than no server. Set up basic monitoring:
# Quick health check script
cat > /usr/local/bin/check-matrix.sh <<'BASH'
#!/bin/bash
HTTP_CODE=$(curl -s -o /dev/null -w '%{http_code}' \
https://matrix.example.com/_matrix/client/versions)
if [ "$HTTP_CODE" != "200" ]; then
echo "ALERT: Matrix homeserver returned HTTP $HTTP_CODE" | \
mail -s "Matrix Server Alert" admin@example.com
fi
BASH
chmod +x /usr/local/bin/check-matrix.sh
echo "*/5 * * * * root /usr/local/bin/check-matrix.sh" >> /etc/cron.d/matrix-monitor
For proper monitoring, expose Synapse’s Prometheus metrics (add to homeserver.yaml):
enable_metrics: true
metrics_port: 9100
# Bind metrics to localhost only — never expose externally
metrics_bind_host: 127.0.0.1
Then scrape http://127.0.0.1:9100/_synapse/metrics with Prometheus and build Grafana dashboards. The Synapse project provides a reference Grafana dashboard.
Matrix’s encryption deserves explanation because it’s fundamentally different from TLS (the padlock in your browser).
TLS encrypts data between your device and the server. The server sees everything in plaintext — it has to, to route and store messages.
Matrix E2EE (end-to-end encryption, based on the Olm/Megolm protocol — the same cryptographic ratchet that underpins Signal’s encryption) encrypts messages on the sender’s device and decrypts them only on the recipient’s device. The server handles routing, stores the ciphertext, but cannot read the content. Not the server admin, not a subpoena, not a hardware seizure.
In Matrix, E2EE is enabled per-room and is the default for direct messages. Here’s what that means practically:
• Messages are encrypted: content, attachments, images, reactions.
• Metadata is not: who is in the room, when messages are sent, room names (unless encrypted). The server needs this to function.
• Key verification matters: Element lets users verify each other’s devices using emoji comparison or QR codes. Without verification, you’re trusting that the server correctly distributes keys — which is where having your own server becomes powerful. On someone else’s server, key distribution is a trust point. On your server, you control it.
• Key backup: if a user loses all their devices without setting up key backup (Secure Key Backup in Element), encrypted message history is permanently unreadable. Configure key backup during onboarding.
One of Matrix’s most practical features: bridges let you use your Matrix client to talk to people on Signal, WhatsApp, Telegram, Discord, Slack, and IRC without installing those apps or giving them your phone number from your main device.
The most reliable bridges:
| Bridge | Network | Notes |
|---|---|---|
| mautrix-signal | Signal | Full double-puppeting, rich media, reactions. Requires a linked Signal device. |
| mautrix-whatsapp | Works via WhatsApp Web protocol. Link via QR code. | |
| mautrix-telegram | Telegram | Full-featured, including Telegram groups and channels. |
| mautrix-discord | Discord | DMs and server channels. |
Bridge installation is its own article — each has specific requirements — but the architecture is straightforward: the bridge runs as a separate service, registers as an “application service” with Synapse, and creates Matrix rooms that mirror conversations on the other network. Your messages go through your Matrix server (encrypted), the bridge decrypts only what it needs to forward, and delivers to the other network.
Privacy note on bridges: the bridge itself must decrypt messages to forward them to the other network. This is unavoidable — WhatsApp can’t read Megolm ciphertext. Run bridges only on your own server, and understand that the bridge process has access to the plaintext of bridged messages. Conversations that stay within Matrix remain fully E2EE.
Updating Synapse:
apt update && apt upgrade -y matrix-synapse-py3
systemctl restart matrix-synapse
Read the changelog before upgrading. Synapse occasionally introduces breaking config changes or database migrations that increase restart time.
Updating Element Web:
cd /tmp
ELEMENT_VERSION="x.x.x" # check latest release
wget "https://github.com/element-hq/element-web/releases/download/v${ELEMENT_VERSION}/element-v${ELEMENT_VERSION}.tar.gz"
tar xzf "element-v${ELEMENT_VERSION}.tar.gz"
cp /var/www/element/config.json "/tmp/element-v${ELEMENT_VERSION}/config.json"
rm -rf /var/www/element
mv "/tmp/element-v${ELEMENT_VERSION}" /var/www/element
Database maintenance:
# Synapse accumulates state resolution data. Periodic VACUUM keeps PostgreSQL healthy:
sudo -u postgres vacuumdb --analyze synapse
# Check database size:
sudo -u postgres psql -c "SELECT pg_size_pretty(pg_database_size('synapse'));"
Log rotation:
Synapse logs can grow large in federated environments. The default logrotate config should handle it, but verify:
ls -lh /var/log/matrix-synapse/
cat /etc/logrotate.d/matrix-synapse
You now run a private, encrypted messaging server on Swiss infrastructure:
• ✅ End-to-end encrypted messaging — messages are plaintext only on sender and recipient devices, never on the server
• ✅ Swiss jurisdiction — FADP protection, no mass-surveillance mandates, court-order-only access
• ✅ Federation — you can message anyone on any Matrix server worldwide, or keep it private
• ✅ No third-party dependencies — no app store account, no phone number requirement, no corporate intermediary
• ✅ Hardened — rate-limited, fail2ban-protected, HSTS-enforced, SSRF-mitigated
• ✅ Bridge-ready — can connect to Signal, WhatsApp, Telegram, Discord through your server
The honest trade-off: you’re now responsible for uptime, updates, and backups. A messaging server that goes down means people can’t communicate — that’s a higher-stakes service than a file sync. Plan for it.
Running a Matrix homeserver is satisfying and genuinely private. But it’s also ongoing work: Synapse updates, database maintenance, monitoring, backup verification, certificate renewals, security patches, and being on call when federation breaks at 2 AM.
For a personal server or a small team, it’s manageable. For an organisation that needs messaging uptime guarantees but doesn’t have a dedicated sysadmin, it’s a commitment that shouldn’t be underestimated.
If you want the privacy of Swiss-hosted messaging infrastructure without managing it yourself, SwissLayer runs dedicated servers in Switzerland with full management — we handle the uptime, the patches, and the 2 AM pages so you don’t have to.
Questions about deploying Matrix on Swiss infrastructure, or about managed hosting for messaging workloads? Get in touch.