Skip to main content

How to Self-Host Matrix Synapse: Decentralized E2E Encrypted Messaging 2026

·OSSAlt Team
matrixsynapseelementmessagingself-hostingdockerencryption2026

TL;DR

Matrix is an open standard for decentralized, E2E-encrypted messaging. Synapse (Apache 2.0, ~12K GitHub stars, Python) is the reference homeserver — your own private messaging infrastructure that federates with the global Matrix network. Element is the polished web/desktop/mobile client. Matrix is the chat infrastructure used by the German federal government, France's Defense Ministry, and thousands of companies who need encrypted, self-sovereign communications.

Key Takeaways

  • Matrix/Synapse: Apache 2.0, ~12K stars — decentralized, federated, E2E encrypted messaging
  • Federation: Users on your server can message users on any Matrix server (matrix.org, etc.)
  • Bridges: Connect your Matrix server to Slack, Discord, Telegram, WhatsApp, Signal via bridges
  • Element: The best Matrix client — web, desktop, iOS, Android — all free
  • E2E by default: All DMs and rooms can be E2E encrypted; keys verified via cross-signing
  • vs Signal: Matrix is self-hosted and federated; Signal is centralized but simpler

Part 1: Docker Setup

# docker-compose.yml
services:
  synapse:
    image: matrixdotorg/synapse:latest
    container_name: synapse
    restart: unless-stopped
    ports:
      - "8008:8008"
    volumes:
      - synapse_data:/data
    environment:
      SYNAPSE_SERVER_NAME: "yourdomain.com"
      SYNAPSE_REPORT_STATS: "no"
    depends_on:
      postgres:
        condition: service_healthy

  postgres:
    image: postgres:16-alpine
    restart: unless-stopped
    environment:
      POSTGRES_USER: synapse
      POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
      POSTGRES_DB: synapse
      POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C"
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U synapse"]
      interval: 10s
      start_period: 20s

volumes:
  synapse_data:
  postgres_data:

Generate Synapse config

# Generate the homeserver.yaml config:
docker run --rm \
  -v synapse_data:/data \
  -e SYNAPSE_SERVER_NAME=yourdomain.com \
  -e SYNAPSE_REPORT_STATS=no \
  matrixdotorg/synapse:latest generate

# The config is now at: synapse_data/homeserver.yaml
# Edit it to configure PostgreSQL:
# homeserver.yaml (key sections):
server_name: "yourdomain.com"

# PostgreSQL database:
database:
  name: psycopg2
  args:
    user: synapse
    password: "your-db-password"
    database: synapse
    host: postgres
    cp_min: 5
    cp_max: 10

# Email for notifications:
email:
  smtp_host: smtp.yourdomain.com
  smtp_port: 587
  smtp_user: "matrix@yourdomain.com"
  smtp_pass: "your-smtp-password"
  notif_from: "Matrix <matrix@yourdomain.com>"
  enable_notifs: true

# Disable registration (invite-only):
enable_registration: false
registration_requires_token: true

# Enable federation:
federation_domain_whitelist:
  # Remove to allow all federation, or list allowed servers:
  # - matrix.org
  # - element.io
# Start Synapse:
docker compose up -d

# Create admin user:
docker exec synapse register_new_matrix_user \
  -u admin -p your-password -a \
  -c /data/homeserver.yaml http://localhost:8008

Part 2: HTTPS with Caddy

Matrix requires specific routing for federation:

yourdomain.com {
    # Matrix federation API:
    handle /_matrix/* {
        reverse_proxy localhost:8008
    }

    # Matrix client API:
    handle /.well-known/matrix/* {
        respond `{"m.homeserver":{"base_url":"https://yourdomain.com"},"m.identity_server":{"base_url":"https://vector.im"}}` 200
    }
}

# If using a subdomain for Matrix (matrix.yourdomain.com):
matrix.yourdomain.com {
    reverse_proxy localhost:8008
}

Run Synapse on matrix.yourdomain.com but have user IDs as @user:yourdomain.com:

yourdomain.com {
    handle /.well-known/matrix/server {
        respond `{"m.server":"matrix.yourdomain.com:443"}` 200
    }
    handle /.well-known/matrix/client {
        respond `{"m.homeserver":{"base_url":"https://matrix.yourdomain.com"}}` 200
    }
}

matrix.yourdomain.com {
    reverse_proxy localhost:8008
}

Part 3: Element Web Client

Host Element Web yourself:

# Add to docker-compose.yml:
services:
  element-web:
    image: vectorim/element-web:latest
    container_name: element
    restart: unless-stopped
    ports:
      - "8080:80"
    volumes:
      - ./element-config.json:/app/config.json:ro
{
  "default_server_config": {
    "m.homeserver": {
      "base_url": "https://matrix.yourdomain.com",
      "server_name": "yourdomain.com"
    }
  },
  "disable_custom_urls": true,
  "brand": "My Matrix Chat",
  "show_labs_settings": false,
  "features": {
    "feature_video_rooms": false
  }
}
chat.yourdomain.com {
    reverse_proxy localhost:8080
}

Part 4: Bridges

Bridges connect your Matrix server to other chat platforms:

Telegram bridge (mautrix-telegram)

# Add to docker-compose.yml:
services:
  mautrix-telegram:
    image: dock.mau.dev/mautrix/telegram:latest
    container_name: mautrix_telegram
    restart: unless-stopped
    volumes:
      - ./bridges/telegram:/data
    depends_on:
      - synapse
# Generate config:
docker run --rm -v ./bridges/telegram:/data dock.mau.dev/mautrix/telegram:latest

# Edit ./bridges/telegram/config.yaml:
# homeserver.address: https://matrix.yourdomain.com
# bridge.permissions: {"@admin:yourdomain.com": "admin"}
# appservice.as_token and hs_token: generate random strings

# Register bridge with Synapse (add to homeserver.yaml):
# app_service_config_files:
#   - /data/telegram-registration.yaml

Available bridges

PlatformBridgeGitHub
Telegrammautrix-telegramdock.mau.dev/mautrix/telegram
WhatsAppmautrix-whatsappdock.mau.dev/mautrix/whatsapp
Discordmautrix-discorddock.mau.dev/mautrix/discord
Slackmautrix-slackdock.mau.dev/mautrix/slack
Signalmautrix-signaldock.mau.dev/mautrix/signal
iMessagemautrix-imessagedock.mau.dev/mautrix/imessage (macOS only)

Part 5: User Management

# Create a user:
docker exec synapse register_new_matrix_user \
  -u alice -p secure-password \
  -c /data/homeserver.yaml http://localhost:8008

# Create a registration token (for invite-only signup):
docker exec synapse synapse_port_db --config-file /data/homeserver.yaml \
  --generate-registration-token

# Or via Admin API:
curl -X POST "https://matrix.yourdomain.com/_synapse/admin/v1/registration_tokens/new" \
  -H "Authorization: Bearer $ADMIN_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"uses_allowed": 1}'

# List users:
curl "https://matrix.yourdomain.com/_synapse/admin/v2/users?from=0&limit=100" \
  -H "Authorization: Bearer $ADMIN_ACCESS_TOKEN" | jq '.users[].name'

# Deactivate a user:
curl -X PATCH "https://matrix.yourdomain.com/_synapse/admin/v2/users/@alice:yourdomain.com" \
  -H "Authorization: Bearer $ADMIN_ACCESS_TOKEN" \
  -d '{"deactivated": true}'

Part 6: E2E Encryption and Cross-Signing

Matrix supports E2E encryption with device verification:

  1. Enable E2E in a room: Room Settings → Security → Enable Encryption
  2. Cross-signing: In Element → Security → Set up → creates a cross-signing key
  3. Verification: Verify other users' devices via emoji comparison or QR code
  4. Secure backup: Store encrypted key backup so you don't lose messages if you lose your device
# Check E2E status via API:
curl "https://matrix.yourdomain.com/_matrix/client/v3/keys/query" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d '{"device_keys": {"@alice:yourdomain.com": []}}'

Maintenance

# Update Synapse:
docker compose pull
docker compose up -d

# Database maintenance (run monthly):
docker exec synapse synapse_port_db --config-file /data/homeserver.yaml --update-stats

# Purge old room events (reduce database size):
curl -X POST "https://matrix.yourdomain.com/_synapse/admin/v1/purge_history_jobs" \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -d '{"delete_local_events": true, "purge_up_to_ts": 1609459200000}'

# Backup:
docker exec synapse-postgres-1 pg_dump -U synapse synapse \
  | gzip > synapse-db-$(date +%Y%m%d).sql.gz

# Logs:
docker compose logs -f synapse

See all open source communication tools at OSSAlt.com/categories/communication.

Comments