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:

  1. Open Security & Privacy settings
  2. Scroll to "Secure Backup"
  3. 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.