Matrix Synapse Server with Element Web
Matrix is an open standard for decentralized, end-to-end encrypted real-time communication, and Synapse is the reference server implementation. Element Web is the primary Matrix client. This guide covers installing Synapse with Element Web for self-hosted secure messaging with federation, E2EE, bridges to Slack/IRC/Telegram, and user management.
Prerequisites
- Ubuntu 20.04+ or Debian 11+
- 2 GB RAM minimum (4 GB recommended with bridges)
- A domain name (e.g.,
example.com) with DNS control - Nginx for reverse proxy
- PostgreSQL 12+ (strongly recommended over SQLite)
- A TLS certificate (Let's Encrypt)
Installing Synapse
# Add the Matrix.org package repository
sudo apt-get install -y lsb-release wget apt-transport-https
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" \
| sudo tee /etc/apt/sources.list.d/matrix-org.list
sudo apt-get update
sudo apt-get install -y matrix-synapse-py3
# During installation, enter your server name when prompted
# This is your Matrix domain (e.g., example.com) - cannot be changed later!
# Verify
synapse_homeserver --version
sudo systemctl status matrix-synapse
Configuring Synapse
The main config is /etc/matrix-synapse/homeserver.yaml:
# /etc/matrix-synapse/homeserver.yaml (key settings)
server_name: "example.com" # Your Matrix domain - NEVER change this
# Public URL where Synapse is reachable (can differ from server_name)
public_baseurl: "https://matrix.example.com"
# Database (configure PostgreSQL below first, then update this)
database:
name: psycopg2
args:
user: synapse_user
password: synapse_password
database: synapse
host: localhost
cp_min: 5
cp_max: 10
# Listeners
listeners:
- port: 8008
tls: false
type: http
x_forwarded: true
bind_addresses: ["127.0.0.1"]
resources:
- names: [client, federation]
compress: false
# Media storage
media_store_path: /var/lib/matrix-synapse/media
# Registration
enable_registration: false # Disable open registration by default
registration_shared_secret: "$(openssl rand -hex 32)" # For admin user creation
# Log config
log_config: "/etc/matrix-synapse/log.yaml"
# Signing key (auto-generated on first run)
signing_key_path: "/etc/matrix-synapse/homeserver.signing.key"
# Federation
federation_domain_whitelist: null # Allow all federation (remove to disable)
# Rate limiting
rc_message:
per_second: 0.2
burst_count: 10
# Experimental features
experimental_features:
# MSC3861: OIDC/OAuth2 authorization
# msc3861:
# enabled: true
Setting Up the Database
# Install PostgreSQL
sudo apt-get install -y postgresql
# Create database and user
sudo -u postgres psql << 'SQL'
CREATE USER synapse_user WITH PASSWORD 'synapse_password';
CREATE DATABASE synapse
ENCODING 'UTF8'
LC_COLLATE='C'
LC_CTYPE='C'
template=template0
OWNER synapse_user;
GRANT ALL PRIVILEGES ON DATABASE synapse TO synapse_user;
SQL
# Install the psycopg2 Python adapter
sudo apt-get install -y matrix-synapse-py3 python3-psycopg2
# Run database migration
sudo -u matrix-synapse synapse_homeserver --config-path /etc/matrix-synapse/homeserver.yaml --generate-keys
# Start Synapse
sudo systemctl enable --now matrix-synapse
sudo systemctl status matrix-synapse
Federation and DNS Setup
Matrix uses .well-known delegation so your Matrix ID is @user:example.com but Synapse runs on matrix.example.com:
Option 1: .well-known delegation (recommended)
Create https://example.com/.well-known/matrix/server:
{
"m.server": "matrix.example.com:443"
}
And https://example.com/.well-known/matrix/client:
{
"m.homeserver": {
"base_url": "https://matrix.example.com"
},
"m.identity_server": {
"base_url": "https://vector.im"
}
}
Option 2: SRV DNS record
Add to your DNS:
_matrix._tcp.example.com 3600 IN SRV 10 5 443 matrix.example.com.
Nginx configuration:
# Serve .well-known on example.com
server {
listen 443 ssl;
server_name example.com;
# ... SSL config ...
location /.well-known/matrix/server {
default_type application/json;
return 200 '{"m.server": "matrix.example.com:443"}';
}
location /.well-known/matrix/client {
default_type application/json;
add_header Access-Control-Allow-Origin *;
return 200 '{"m.homeserver":{"base_url":"https://matrix.example.com"}}';
}
}
# Proxy Synapse
server {
listen 443 ssl;
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;
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;
client_max_body_size 50M;
proxy_http_version 1.1;
}
}
Test federation: https://federationtester.matrix.org/
Installing Element Web
Element Web is the web-based Matrix client:
# Download the latest Element Web release
ELEMENT_VERSION=$(curl -s https://api.github.com/repos/vector-im/element-web/releases/latest | grep tag_name | cut -d '"' -f4)
wget https://github.com/vector-im/element-web/releases/download/${ELEMENT_VERSION}/element-${ELEMENT_VERSION}.tar.gz
tar xzf element-${ELEMENT_VERSION}.tar.gz
sudo mv element-${ELEMENT_VERSION} /var/www/element
sudo ln -s /var/www/element /var/www/element-current
# Create configuration
sudo cp /var/www/element/config.sample.json /var/www/element/config.json
sudo nano /var/www/element/config.json
{
"default_server_config": {
"m.homeserver": {
"base_url": "https://matrix.example.com",
"server_name": "example.com"
}
},
"brand": "My Matrix",
"default_theme": "light",
"room_directory": {
"servers": ["matrix.org"]
},
"disable_custom_urls": false,
"disable_guests": true,
"disable_login_language_selector": false,
"integrations_ui_url": "https://scalar.vector.im/",
"integrations_rest_url": "https://scalar.vector.im/api",
"show_labs_settings": true
}
Nginx for Element Web:
server {
listen 443 ssl;
server_name element.example.com;
ssl_certificate /etc/letsencrypt/live/element.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/element.example.com/privkey.pem;
root /var/www/element;
index index.html;
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options SAMEORIGIN;
add_header X-XSS-Protection "1; mode=block";
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src * data:; connect-src *; font-src 'self'; media-src *";
location / {
try_files $uri $uri/ =404;
}
}
End-to-End Encryption
Matrix supports E2EE by default in Element. Key concepts:
- Device keys: each device has a keypair
- Cross-signing: verify all your devices from one trusted device
- Key backup: encrypted key backup on the server
Enable key backup in Element:
- Open Security & Privacy settings
- Scroll to "Secure Backup"
- Click "Set up" and save the Security Key securely
For server-side settings, ensure the crypto module is enabled:
# homeserver.yaml - these are on by default in Synapse:
# enable_media_repo: true (for encrypted media)
Bridges (Slack, IRC, Telegram)
Bridges connect Matrix rooms to other platforms. Use mautrix bridges:
Telegram bridge:
# Install mautrix-telegram
pip3 install mautrix-telegram
# Create config
mautrix-telegram -c /etc/mautrix-telegram/config.yaml -g # Generate config
nano /etc/mautrix-telegram/config.yaml
Key config settings:
homeserver:
address: https://matrix.example.com
domain: example.com
appservice:
address: http://localhost:29317
port: 29317
as_token: "generate-random-token"
hs_token: "generate-random-token"
telegram:
api_id: 12345 # Get from https://my.telegram.org
api_hash: "yourhash"
bot_token: "optional" # For bot bridging
Register the bridge with Synapse:
# Generate the registration file
mautrix-telegram -c /etc/mautrix-telegram/config.yaml -r /etc/matrix-synapse/appservices/telegram.yaml
# Add to homeserver.yaml
app_service_config_files:
- /etc/matrix-synapse/appservices/telegram.yaml
sudo systemctl restart matrix-synapse
Slack bridge (mautrix-slack): similar setup, configure with Slack workspace token.
IRC bridge (matrix-appservice-irc):
npm install -g matrix-appservice-irc
# Configure and register as an appservice
User Management
# Create admin user (requires registration_shared_secret in config)
register_new_matrix_user \
-c /etc/matrix-synapse/homeserver.yaml \
-u admin \
-p SecurePassword123 \
-a # -a flag = make admin
# Or use the Admin API
ADMIN_TOKEN=$(curl -s -X POST https://matrix.example.com/_matrix/client/v3/login \
-H 'Content-Type: application/json' \
-d '{"type":"m.login.password","user":"admin","password":"SecurePassword123"}' \
| python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")
# List users
curl -H "Authorization: Bearer ${ADMIN_TOKEN}" \
https://matrix.example.com/_synapse/admin/v2/users?from=0&limit=10
# Create a user via Admin API
curl -X PUT \
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
-H "Content-Type: application/json" \
https://matrix.example.com/_synapse/admin/v2/users/@newuser:example.com \
-d '{"password": "UserPassword123", "displayname": "New User"}'
# Deactivate a user
curl -X POST \
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
https://matrix.example.com/_synapse/admin/v1/deactivate/@spammer:example.com \
-d '{"erase": true}'
# Room management
curl -H "Authorization: Bearer ${ADMIN_TOKEN}" \
"https://matrix.example.com/_synapse/admin/v1/rooms?search_term=test"
Troubleshooting
Synapse fails to start:
sudo journalctl -u matrix-synapse -f
# Common: wrong PostgreSQL credentials, port conflict, invalid YAML
Federation not working:
# Test federation
curl https://matrix.example.com/_matrix/federation/v1/version
# Check the federation tester
# https://federationtester.matrix.org/?domain=example.com
High memory usage:
# Synapse can use a lot of RAM with many users
# Add to homeserver.yaml:
caches:
global_factor: 0.5 # Reduce cache sizes
# Enable synapse-admin for monitoring
Room sync is slow:
# Run Synapse maintenance tasks
sudo -u matrix-synapse synapse_homeserver --config-path /etc/matrix-synapse/homeserver.yaml \
--run-background-tasks
# Purge old room history via Admin API (careful!)
curl -X POST -H "Authorization: Bearer ${ADMIN_TOKEN}" \
https://matrix.example.com/_synapse/admin/v1/purge_history/!roomid:example.com \
-d '{"purge_up_to_ts": 1640000000000}'
Conclusion
Synapse with Element Web provides a complete self-hosted, end-to-end encrypted messaging platform that federates with the wider Matrix network, meaning your users can communicate with anyone on any Matrix server. The bridge ecosystem (Telegram, Slack, IRC, Signal, WhatsApp) makes it possible to centralize all your team communication in a single self-hosted platform. For production deployments, use PostgreSQL, configure regular PostgreSQL backups, and monitor Synapse's memory usage as it scales with the number of rooms and users.


