Zum Inhalt

Self-Hosting Decentralized Chat: Matrix Synapse on Debian Linux (Olm/Megolm E2EE, Federation & PostgreSQL)

Executive Summary: Matrix is an open IETF/Matrix.org standard for decentralized, federated, End-to-End Encrypted (E2EE) real-time communication. Just as an email user at @nejat-hakan.de can message anyone at @debian.org without sharing a central server, a Matrix Synapse Homeserver gives you sovereign identities (@nejat:nejat-hakan.de) encrypted via the Double-Ratchet Olm/Megolm cryptographic state machine and accessible from sleek modern clients like Element X, Element Web, and FluffyChat.

  • Sovereign @user:nejat-hakan.de ID


    Use .well-known/matrix/server delegation so your Matrix ID uses your clean root domain (@nejat:nejat-hakan.de) while hosting Synapse on matrix.nejat-hakan.de.

  • Olm & Megolm Double-Ratchet E2EE


    Cross-signed device verification ensures that private rooms and voice/video calls remain cryptographically unreadable even to intermediate federated servers.

  • Mandatory C Collation PostgreSQL


    Configure PostgreSQL 17 with --lc-collate=C --lc-ctype=C as required by Synapse's directed acyclic event graph (DAG) state resolver.

  • Federation or Private-Only Citadel


    Join global open-source developer rooms (#debian:matrix.org) or lock federation_domain_whitelist for a 100% isolated internal organization server.


1. Matrix Federation & .well-known Domain Delegation Architecture

A common mistake when deploying Matrix Synapse is setting server_name: matrix.nejat-hakan.de in homeserver.yaml—which permanently locks your user IDs to @nejat:matrix.nejat-hakan.de (server_name cannot be changed after database initialization!).

Instead, set server_name: "nejat-hakan.de" and serve two tiny JSON endpoints from your root website (https://nejat-hakan.de/.well-known/matrix/server and client) that delegate Matrix traffic to matrix.nejat-hakan.de:443:

%%{init: {'flowchart': {'wrappingWidth': 600}}}%%
flowchart TB
    ELEM["📱 Element X / Web Client<br/>User: @nejat:nejat-hakan.de"]
    WELL["🌐 nejat-hakan.de/.well-known/<br/>matrix/client & server<br/>(Delegates to matrix.*:443)"]
    SYN["🛡️ Matrix Synapse Server<br/>matrix.nejat-hakan.de<br/>(127.0.0.1:8008)"]
    PG["🗄️ PostgreSQL 17 (LC_COLLATE=C)<br/>Event DAG & State Store"]

    ELEM -->|"1. Lookup .well-known"| WELL
    ELEM <==>|"2. Client-Server API (:443)"| SYN
    SYN <--> PG

Configuring .well-known Delegation on nejat-hakan.de

Serve these two JSON payloads with Access-Control-Allow-Origin: * on your root domain (nejat-hakan.de):

  • https://nejat-hakan.de/.well-known/matrix/server: {"m.server": "matrix.nejat-hakan.de:443"}
  • https://nejat-hakan.de/.well-known/matrix/client: {"m.homeserver": {"base_url": "https://matrix.nejat-hakan.de"}}

2. Generating homeserver.yaml & Deploying with PostgreSQL (LC_COLLATE=C)

Synapse Requires LC_COLLATE=C and LC_CTYPE=C in PostgreSQL!

Matrix Synapse refuses to start if its PostgreSQL database is initialized with a locale other than C. Always pass POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C" when creating the Synapse database container.

# File: /opt/matrix-ops/01_generate_synapse_config.sh
#!/usr/bin/env bash
set -euo pipefail

sudo mkdir -p /srv/containers/synapse/{data,postgres}

# Generate initial homeserver.yaml and Ed25519 signing key for 'nejat-hakan.de'
docker run -it --rm \
    -v /srv/containers/synapse/data:/data \
    -e SYNAPSE_SERVER_NAME=nejat-hakan.de \
    -e SYNAPSE_REPORT_STATS=no \
    docker.io/matrixdotorg/synapse:latest generate

Now deploy compose.yaml:

# File: /srv/containers/synapse/compose.yaml
services:
  synapse:
    image: docker.io/matrixdotorg/synapse:latest
    container_name: matrix-synapse
    depends_on:
      synapse-db:
        condition: service_healthy
    restart: unless-stopped
    ports:
      - "127.0.0.1:8008:8008/tcp"
    environment:
      UID: "991"
      GID: "991"
      TZ: "Europe/Berlin"
    volumes:
      - /srv/containers/synapse/data:/data:rw

  synapse-db:
    image: docker.io/library/postgres:17-bookworm
    container_name: matrix-postgres
    restart: unless-stopped
    environment:
      POSTGRES_USER: "synapse"
      POSTGRES_PASSWORD: "${SYNAPSE_DB_PASSWORD}"
      POSTGRES_DB: "synapse"
      POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C"
    volumes:
      - /srv/containers/synapse/postgres:/var/lib/postgresql/data:rw
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U synapse -d synapse"]
      interval: 10s
      timeout: 5s
      retries: 5

3. Essential homeserver.yaml Hardening Settings

Edit /srv/containers/synapse/data/homeserver.yaml to connect PostgreSQL, disable open public registration, and configure shared-secret registration for CLI user creation:

# File: /srv/containers/synapse/data/homeserver.yaml (Key Sections)
server_name: "nejat-hakan.de"
public_baseurl: "https://matrix.nejat-hakan.de/"
pid_file: /data/homeserver.pid

listeners:
  - port: 8008
    tls: false
    type: http
    x_forwarded: true
    bind_addresses: ['0.0.0.0']
    resources:
      - names: [client, federation]
        compress: false

database:
  name: psycopg2
  args:
    user: synapse
    password: "Your_Strong_Synapse_DB_Password"
    database: synapse
    host: synapse-db
    cp_min: 5
    cp_max: 10

enable_registration: false
registration_shared_secret: "Replace_With_32_Byte_Random_Hex_Secret"
suppress_key_server_warning: true

Create your admin user via CLI:

# File: /opt/matrix-ops/02_register_admin_user.sh
docker exec -it matrix-synapse register_new_matrix_user \
    -u nejat \
    -p "Strong_Matrix_Password_2026!" \
    -a \
    -c /data/homeserver.yaml \
    http://127.0.0.1:8008

Where to Go Next

Explore Mattermost team collaboration, Caddy reverse proxying, and off-site backups:

  • Mattermost Team Chat


    Compare decentralized Matrix Synapse with Mattermost's single-binary Slack-compatible DevOps chat.

    Mattermost Guide

  • Caddy Reverse Proxy


    Serve /.well-known/matrix/* JSON headers and reverse-proxy matrix.nejat-hakan.de effortlessly with Caddy.

    Caddy Guide

  • Vaultwarden Password Manager


    Back up your Element E2EE Recovery Key (Security Key) safely inside your Vaultwarden vault.

    Vaultwarden Guide