Matrix: der eigene Chat-Server mit Synapse (Teil 1)
Synapse 1.164 mit Postgres 18 hinter Traefik: Server-Name, Delegation, geschlossene Registrierung – und der Föderationstest, der am Ende grün sein muss.
Inhaltsverzeichnis
Ein eigener Chat-Server, der mit dem Rest der Matrix-Welt reden kann: Deine Nachrichten liegen auf deinem Server, und trotzdem schreibst du Leuten auf matrix.org oder bei ihrem eigenen Server. Der Preis dafür sind ein paar Entscheidungen, die man am Anfang trifft und später nur schwer ändert.
Was bauen wir?
Wir betreiben Synapse 1.164.2 mit PostgreSQL 18.6 hinter Traefik. Am Ende hast du einen Matrix-Homeserver mit geschlossener Registrierung, einem eigenen Benutzerkonto und einer funktionierenden Föderation. Dass sie funktioniert, belegt der offizielle Föderationstester.
Dieses Tutorial ist Teil 1 und behandelt bewusst nur den Server. Element Web als Oberfläche, der Alltag mit Räumen und die Administration kommen in Teil 2, sonst würde daraus ein Buch.
Eine Entscheidung, die bleibt
server_name wird Teil jeder Benutzer-ID (@du:DEINE_DOMAIN) und jeder Raumadresse
(#kochabend:DEINE_DOMAIN). Ihn nachträglich zu ändern ist praktisch unmöglich: Alle Konten wären
andere, die Föderation verlöre jede Zuordnung. Überleg dir vor dem ersten Start, welche Domain du dauerhaft willst. Am besten kurz
und schön, denn sie steht später hinter jedem Namen.Voraussetzungen
- Traefik als Reverse Proxy mit funktionierendem Let’s-Encrypt-Resolver
- Eine Domain, die auf den Server zeigt (A- und idealerweise AAAA-Record)
- Mindestens 2 GB Arbeitsspeicher. Frisch gestartet und ohne Last braucht Synapse gut 100 MB und Postgres rund 60 MB; sobald du Räumen mit vielen Teilnehmern beitrittst, wächst der Bedarf deutlich
VPS 2000 G12.5
8 vCore · 16 GB RAM · 256 GB SSD
ab 26,92 €/Monat
Matrix wächst mit den Räumen, denen du beitrittst – der größere VPS ist hier keine Verschwendung.
💶 1 Monat gratis:
Einmal einlösbar, gilt für VPS 2000 G12.5. Im Warenkorb einlösen →
💶 5,95 € Gutschein für netcup-Neukunden: immer gültig · nicht für Domains und VPS Lite
Schritt für Schritt
Schritt 1: Server-Name, Host und Delegation auseinanderhalten
Matrix kennt zwei verschiedene Adressen, und das ist die häufigste Verwirrung am Anfang:
- Der
server_namesteht in den IDs:@koch:DEINE_DOMAIN. Er ist reine Namensgebung. - Der Host, auf dem Synapse tatsächlich läuft, kann ein ganz anderer sein, etwa
matrix.DEINE_DOMAIN.
Damit fremde Server den zweiten finden, wenn sie nur den ersten kennen, gibt es Delegation:
Unter https://DEINE_DOMAIN/.well-known/matrix/server liegt eine kleine JSON-Datei, die auf den
echten Host zeigt. Genauso findet ein Chat-Programm über
https://DEINE_DOMAIN/.well-known/matrix/client, wohin es sich verbinden soll.
In dieser Anleitung laufen server_name und Host auf derselben Domain. Das ist der einfachste Fall,
und Synapse kann die beiden Dateien dann selbst ausliefern. Einen zusätzlichen Webserver brauchst
du nicht. Willst du später auf matrix.DEINE_DOMAIN umziehen, bleibt der server_name gleich und
nur die Delegationsdatei ändert sich.
Schritt 2: Die Konfiguration erzeugen lassen
Synapse schreibt sich seine Grundkonfiguration selbst, inklusive der Geheimnisse und des Signaturschlüssels:
mkdir -p /opt/matrix/data && cd /opt/matrix
docker run --rm -v /opt/matrix/data:/data \
-e SYNAPSE_SERVER_NAME=DEINE_DOMAIN \
-e SYNAPSE_REPORT_STATS=no \
ghcr.io/element-hq/synapse:v1.164.2 generateCreating log config /data/DEINE_DOMAIN.log.config
Setting ownership on /data to 991:991
Generating config file /data/homeserver.yaml
Generating signing key file /data/DEINE_DOMAIN.signing.key
A config file has been generated in '/data/homeserver.yaml' for server name 'DEINE_DOMAIN'. Please review this file and customise it to your needs.Der Signaturschlüssel ist unersetzlich
DEINE_DOMAIN.signing.key ist die Identität deines Servers gegenüber allen anderen
Matrix-Servern. Geht sie verloren, hält dich das restliche Netz für einen Hochstapler und lehnt
deine Events ab. Sie gehört vom ersten Tag an ins Backup, nicht erst, wenn Daten drin sind. Dasselbe gilt für registration_shared_secret, macaroon_secret_key und form_secret aus
der homeserver.yaml.Die erzeugte Datei benutzt SQLite. Für alles außer einem Einzelplatz-Test ist das zu wenig; wir
stellen in der homeserver.yaml auf PostgreSQL um und ergänzen die Einstellungen, die diesen Server
betreffen:
# /opt/matrix/data/homeserver.yaml (Auszug – Geheimnisse bleiben unangetastet)
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: DEIN_DB_PASSWORT
dbname: synapse
host: db
port: 5432
cp_min: 5
cp_max: 10
public_baseurl: https://DEINE_DOMAIN/
serve_server_wellknown: true
enable_registration: false
suppress_key_server_warning: true
max_upload_size: 50M
url_preview_enabled: falseVier Zeilen verdienen eine Erklärung:
x_forwarded: true: Synapse liest die echte Besucher-IP aus demX-Forwarded-For-Header, den Traefik setzt. Ohne das steht in jedem Protokoll die Adresse des Proxys. Die erzeugte Datei hat den Schalter schon gesetzt; lass ihn beim Umbau stehen.public_baseurl: Daraus baut Synapse die Client-Delegation und alle Links in Einladungen.serve_server_wellknown: true: Synapse liefert/.well-known/matrix/serverselbst aus. Das ist der Grund, warum wir keinen zweiten Webserver brauchen.url_preview_enabled: false: Die Vorschau von Links lässt deinen Server jede URL abrufen, die jemand in einen Raum schreibt. Das ist eine hübsche Funktion und gleichzeitig eine Möglichkeit, deinen Server als Abrufwerkzeug für fremde Adressen zu missbrauchen. Lass sie zunächst aus.
Schritt 3: PostgreSQL mit der richtigen Sortierung
Synapse verlangt eine Datenbank mit der Sortierreihenfolge C. Der Grund ist unspektakulär und
die Folge unangenehm: Mit einer sprachabhängigen Sortierung wie de_DE.utf8 vergleicht Postgres
Zeichenketten anders, und Synapse verweigert deshalb von vornherein den Start.
Im Docker-Image erreichst du das über POSTGRES_INITDB_ARGS – beim allerersten Start, denn
danach steht die Sortierreihenfolge fest:
environment:
POSTGRES_INITDB_ARGS: "--encoding=UTF8 --lc-collate=C --lc-ctype=C"Nachsehen kannst du das jederzeit:
docker compose exec db psql -U synapse -d synapse \
-c "SELECT datname, pg_encoding_to_char(encoding), datcollate FROM pg_database WHERE datname='synapse';" datname | pg_encoding_to_char | datcollate
---------+---------------------+------------
synapse | UTF8 | CSchritt 4: Der Stack
# /opt/matrix/compose.yaml
services:
db:
image: postgres:18.6-alpine
container_name: matrix-db
restart: unless-stopped
environment:
POSTGRES_USER: synapse
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: synapse
POSTGRES_INITDB_ARGS: "--encoding=UTF8 --lc-collate=C --lc-ctype=C"
volumes:
- pgdata:/var/lib/postgresql
networks: [matrix-intern]
healthcheck:
test: ["CMD-SHELL", "pg_isready -U synapse"]
interval: 10s
timeout: 5s
retries: 6
synapse:
image: ghcr.io/element-hq/synapse:v1.164.2
container_name: synapse
restart: unless-stopped
depends_on:
db:
condition: service_healthy
environment:
SYNAPSE_CONFIG_PATH: /data/homeserver.yaml
TZ: Europe/Berlin
volumes:
- ./data:/data
networks: [matrix-intern, proxy]
labels:
- traefik.enable=true
- traefik.http.routers.synapse.rule=Host(`DEINE_DOMAIN`)
- traefik.http.routers.synapse.entrypoints=websecure
- traefik.http.routers.synapse.tls.certresolver=le
- traefik.http.services.synapse.loadbalancer.server.port=8008
volumes:
pgdata:
networks:
matrix-intern:
internal: true
proxy:
external: trueDie Datenbank liegt in einem Netz mit internal: true – von außen und aus dem proxy-Netz nicht
erreichbar. Das Datenbankpasswort steht in einer .env daneben und muss mit dem Wert in der
homeserver.yaml übereinstimmen.
Hinweis
/var/lib/postgresql und nicht auf /var/lib/postgresql/data.
Ab PostgreSQL 18 verweigert der Container mit dem alten Pfad den Start, auch bei leerem Volume.docker compose up -d
docker compose psNAME STATUS
matrix-db Up 55 seconds (healthy)
synapse Up 45 seconds (healthy)Beim ersten Start legt Synapse das Schema an und fährt anschließend Hintergrund-Migrationen.
In den Logs stehen dann minutenlang Zeilen wie
Running background update 'threads_backfill'. Das ist normal und stört den Betrieb nicht.
Schritt 5: Die vier Endpunkte, die stimmen müssen
Bevor du irgendein Chat-Programm anfasst, prüfst du von außen vier Adressen. Zuerst, ob die Client-API überhaupt antwortet:
curl -s https://DEINE_DOMAIN/_matrix/client/versions{"versions":["r0.0.1","r0.1.0", … ,"v1.16","v1.17"],"unstable_features":{ … }}Dann die beiden Delegationsdateien, die Synapse dank serve_server_wellknown selbst ausliefert:
curl -s https://DEINE_DOMAIN/.well-known/matrix/server
curl -s https://DEINE_DOMAIN/.well-known/matrix/client{"m.server":"DEINE_DOMAIN:443"}
{"m.homeserver":{"base_url":"https://DEINE_DOMAIN/"}}Und schließlich der Schlüsselendpunkt, über den fremde Server die Signaturen deines Servers prüfen:
curl -s https://DEINE_DOMAIN/_matrix/key/v2/server{"old_verify_keys":{},"server_name":"DEINE_DOMAIN","signatures":{"DEINE_DOMAIN":{"ed25519:a_udcw":" … "}}, … }Zum Schluss die Kontrolle, dass die Tür zu ist:
curl -s -X POST https://DEINE_DOMAIN/_matrix/client/v3/register -d '{}'{"errcode":"M_FORBIDDEN","error":"Registration has been disabled"}Offene Registrierung ist keine Option
server_name auf Sperrlisten anderer Server, und das bekommst du nur
schwer wieder los. enable_registration: false bleibt, und Konten legst du von Hand an.Schritt 6: Das erste Konto anlegen
Weil die Registrierung zu ist, benutzt du das mitgelieferte Werkzeug. Es authentifiziert sich über
das registration_shared_secret aus der Konfiguration, -a macht den Benutzer zum Administrator:
docker compose exec synapse register_new_matrix_user \
-u DEIN_BENUTZERNAME -p DEIN_PASSWORT -a \
-c /data/homeserver.yaml http://localhost:8008Sending registration request...
Success!Schritt 7: Gegenprobe über die Client-API
Jetzt der eigentliche Funktionstest, ohne Chat-Programm und nur mit curl. Erst anmelden:
curl -s -X POST https://DEINE_DOMAIN/_matrix/client/v3/login \
-H "Content-Type: application/json" \
-d '{"type":"m.login.password","identifier":{"type":"m.id.user","user":"DEIN_BENUTZERNAME"},"password":"DEIN_PASSWORT"}'Die Antwort enthält user_id, device_id und einen access_token. Die Benutzer-ID zeigt, dass der
server_name sitzt:
"user_id":"@DEIN_BENUTZERNAME:DEINE_DOMAIN"Mit dem Token legst du einen Raum an, schickst eine Nachricht hinein und liest sie wieder aus. Damit ist die komplette Kette aus Client-API, Datenbank und Ereignisspeicher belegt:
curl -s -X POST https://DEINE_DOMAIN/_matrix/client/v3/createRoom \
-H "Authorization: Bearer DEIN_TOKEN" -H "Content-Type: application/json" \
-d '{"name":"Kochabend","preset":"private_chat"}'{"room_id":"!68AeABE_Q59hjgJkh8wIl3zdGbp_n7k4a9uOf4yDrF0"}Die Raum-ID trägt keinen Servernamen: Synapse legt neue Räume in Raumversion 12 an, und deren IDs sind reine Prüfsummen. Was es damit auf sich hat, steht in Teil 2.
curl -s -X PUT "https://DEINE_DOMAIN/_matrix/client/v3/rooms/DEINE_RAUM_ID/send/m.room.message/txn1" \
-H "Authorization: Bearer DEIN_TOKEN" -H "Content-Type: application/json" \
-d '{"msgtype":"m.text","body":"Zwiebelkuchen um 19:15 Uhr"}'{"event_id":"$WHceSNXyP34qT0CoQjq0E97dm7p7ZVLArH7mZdKftyA"}Schritt 8: Föderation prüfen
Alles bisher ging um deinen Server allein. Ob der Rest der Matrix-Welt mit ihm reden kann, beantwortet der offizielle Föderationstester. Er löst deine Delegation auf, baut eine Verbindung auf, prüft das Zertifikat und vergleicht den Servernamen mit der Signatur:
curl -s "https://federationtester.matrix.org/api/report?server_name=DEINE_DOMAIN" \
| python3 -c "import sys,json; d=json.load(sys.stdin); print('FederationOK:', d['FederationOK'])"FederationOK: TrueDer Prüfer führt beide Adressen (IPv4 und IPv6) einzeln auf; bei beiden muss
ValidCertificates: True und MatchingServerName: True stehen. Genau das willst du sehen, bevor du jemandem deine Matrix-Adresse gibst.
Dieselbe Prüfung gibt es auch als Weboberfläche unter
federationtester.matrix.org; die API eignet sich besser, um
sie später gelegentlich zu wiederholen.
Schritt 9: Die Admin-API abschotten
Synapse bringt eine eigene Verwaltungs-API mit, die unter /_synapse/admin/ liegt. Mit dem Token
deines Administratorkontos siehst du damit zum Beispiel alle Konten:
curl -s "https://DEINE_DOMAIN/_synapse/admin/v2/users?from=0&limit=5" \
-H "Authorization: Bearer DEIN_TOKEN"total: 1
@DEIN_BENUTZERNAME:DEINE_DOMAIN | admin: True | deactivated: FalseOhne Token bekommst du erwartungsgemäß eine Abfuhr:
{"errcode":"M_MISSING_TOKEN","error":"Missing access token"}Ein Endpunkt fällt allerdings aus der Reihe:
curl -s https://DEINE_DOMAIN/_synapse/admin/v1/server_version{"server_version":"1.164.2"}Dieser Endpunkt antwortet ohne jede Authentifizierung. Synapse macht das absichtlich, verrät
damit aber jedem, welche Version du fährst und welche bekannten Lücken für dich interessant sind. Wenn
du den Verwaltungspfad von außen gar nicht brauchst, sperrst du ihn am besten im Reverse Proxy:
Eine IPAllowList-Middleware auf /_synapse/admin kostet zwei Zeilen und nimmt das Thema vom
Tisch. Das Muster dafür steht in
Dienste nur im VPN erreichbar machen.
Wenn es nicht funktioniert
Synapse startet nicht und meldet IncorrectDatabaseSetup: Database has incorrect collation. Die Datenbank wurde mit einer sprachabhängigen Sortierung angelegt, etwa de_DE.utf8; Synapse erwartet C. Das lässt sich nachträglich nicht umstellen, denn die Sortierreihenfolge steht beim Anlegen der Datenbank fest. Lösch das Volume und starte mit POSTGRES_INITDB_ARGS: "--encoding=UTF8 --lc-collate=C --lc-ctype=C" neu. Die Fehlermeldung bietet allow_unsafe_locale als Ausweg an; nimm den nicht, denn falsch sortierte Indizes fallen dir später bei der Suche auf die Füße.
Der Föderationstester meldet FederationOK: false, obwohl die Seite im Browser lädt. Dann stimmt meist die Delegation nicht. Prüf curl -s https://DEINE_DOMAIN/.well-known/matrix/server. Die Antwort muss den Host mit Port enthalten, also etwa {"m.server":"DEINE_DOMAIN:443"}. Kommt hier ein 404, fehlt serve_server_wellknown: true in der Konfiguration oder ein anderer Dienst fängt den Pfad ab. Ebenfalls häufig: Der Servername in der Delegation zeigt auf einen Host, dessen Zertifikat auf einen anderen Namen ausgestellt ist.
In den Logs steht bei jedem Zugriff die IP von Traefik statt der des Besuchers. Es fehlt x_forwarded: true im listeners-Block. Synapse wertet die Weiterleitungs-Header nur aus, wenn du es ihm ausdrücklich erlaubst. Ohne echte Absender-IP kannst du weder Missbrauch nachvollziehen noch fail2ban sinnvoll einsetzen.
Die Anmeldung schlägt mit M_FORBIDDEN fehl, obwohl das Passwort stimmt. Prüf, ob du dich wirklich am richtigen Server anmeldest: Die Benutzer-ID lautet @name:server_name, nicht @name:host. Wenn server_name und der Host auseinanderfallen und die Client-Delegation fehlt, landet dein Programm am falschen Ort. curl -s https://DEINE_DOMAIN/.well-known/matrix/client muss die Basis-URL des Homeservers liefern.
Der Server läuft, aber der Speicher wird knapp. Synapse belegt im Leerlauf wenig (gut 100 MB), wächst aber deutlich, sobald du großen, stark föderierten Räumen beitrittst, weil er deren gesamte Historie mitverwaltet. Wenn es eng wird, hilft als Erstes, solche Räume zu verlassen und anschließend mit der Aufräumfunktion der Admin-API alte Ereignisse zu entfernen. Mehr Arbeitsspeicher ist die andere Antwort; ein Homeserver, der in echten Räumen mitspielt, ist auf 1 GB nicht glücklich.
Dein Problem ist nicht dabei? Alle Fehlermeldungen durchsuchen →
Wartung & Backups
Updates kommen häufig: Synapse veröffentlicht etwa alle zwei Wochen eine neue Version, und Sicherheitskorrekturen sind darunter. Das macht einen festen Rhythmus nötig:
cd /opt/matrix
docker compose exec -T db pg_dump -U synapse synapse > vor-update-$(date +%F).sql
sed -i 's/synapse:v1.164.2/synapse:vNEUE_VERSION/' compose.yaml
docker compose pull && docker compose up -d
docker compose logs synapse --tail 20Lies vor jedem Sprung die Release Notes, insbesondere den Abschnitt zu „deprecations“: Synapse wirft regelmäßig alte Konfigurationsschlüssel raus, und der Server startet dann mit einer klaren, aber unerwarteten Meldung nicht mehr. Datenbankmigrationen laufen beim Start automatisch; bei großen Instanzen kann der erste Start nach einem Update deshalb mehrere Minuten dauern.
Sichern musst du vier Dinge, und das erste ist das wichtigste:
/opt/matrix/data/DEINE_DOMAIN.signing.key # die Identität deines Servers
/opt/matrix/data/homeserver.yaml # enthält die drei Geheimnisse
pg_dump-Ausgabe # Räume, Nachrichten, Konten
/opt/matrix/data/media_store/ # Bilder und Dateien aus den RäumenOhne den Signaturschlüssel kannst du den Server nicht wiederherstellen. Du könntest ihn nur neu aufsetzen, und alle anderen Server würden ihn als neuen, unbekannten Server behandeln. Alles zusammen gehört verschlüsselt ins Restic-Backup.
Der media_store wächst unbemerkt: Jedes Bild aus jedem föderierten Raum, den du betrittst,
landet als Kopie auf deiner Platte. Schau alle paar Monate mit du -sh /opt/matrix/data/media_store
nach; Synapse bringt für das Aufräumen eine Admin-API mit, die alte Medien löscht.
Realistisch gerechnet kostet das Aufsetzen einen Abend. Danach sind es zwei Termine pro Monat: einmal Update samt Blick in die Release Notes, einmal Plattenplatz und Föderationstest. Matrix ist kein Dienst, den man aufsetzt und vergisst; dafür entwickelt sich das Protokoll zu schnell.
Feedback per E-Mail: feedback@serverkueche.de
Wie geht's weiter?

Das könnte dir auch schmecken


