Matrix ist ein offenes Protokoll für dezentrale Echtzeitkommunikation Chat, Sprach- und Videoanrufe, Dateiübertragung. Anders als bei geschlossenen Messengern gibt es keinen zentralen Betreiber: Jeder Server ist Teil eines Verbunds (Federation), in dem Nutzer über Serviergrenzen hinweg miteinander schreiben können, vergleichbar mit E-Mail. Wer seinen eigenen Homeserver betreibt, behält die volle Kontrolle über Nutzerkonten, Räume und Nachrichteninhalte und kann per Ende-zu-Ende-Verschlüsselung zusätzlich absichern, dass selbst der Serverbetreiber nicht mitlesen kann. Der Aufbau eines Matrix-Homeservers ist allerdings kein Ein-Container-Projekt. Neben dem eigentlichen Homeserver (Synapse) kommen schnell PostgreSQL, ein Web-Client wie Element, ein Identity-Dienst, TURN-Server für Anrufe, Bridges zu anderen Netzwerken und Admin-Werkzeuge hinzu. Genau hier setzt matrix-docker-ansible-deploy an. Das Playbook orchestriert die komplette Matrix-Landschaft als Docker-Container, konfiguriert die Komponenten aufeinander abgestimmt und lässt sich über eine einzige Konfigurationsdatei steuern.
| Datum | Änderungen |
| 17.09.2022 | Erstellung dieser Anleitung |
| 18.09.2022 | Kapitel 15, 16, 17, 18 hinzugefügt |
| 26.09.2022 | Kapitel 19 bearbeitet. |
| 08.12.2022 | Kapitel 8 hinzugefügt. Matrix Docker Variable angepasst |
| 15.12.2022 | Kapitel 19 angepasst, Matrix Docker Variable angepasst |
| 29.03.2023 | Kapitel 4 und 7 angepasst an die neue Version |
| 25.04.2023 | Kapitel 4 erweitert. Danke an @drr00t |
| 29.05.2023 | Anpassungen an neue Traefik Anleitung |
| 07.08.2023 | Kapitel 5 an Debian 12 angepasst, Kapitel 7 Traefik Pfad angepasst. Danke an @gokala |
| 29.09.2023 | Kapitel 4.1 Sliding Sync hinzugefügt |
| 10.01.2024 | Kapitel 16 wurde angepasst. Danke an @nightshifter |
| 30.01.2024 | Traefik Labels wurden für die aktuelle Matrix Version angepasst |
| 04.02.2024 | Kapitel 7.1 hinzugefügt |
| 21.11.2024 | Anpassung Traefik v3, Anpassung aktuelle Matrix Version, Anpassung Federation Danke an @2tap2b |
| 29.07.2026 | Komplette Überarbeitung dieser Anleitung, Angepasst an aktuelle Matrix Version |
1. Grundvoraussetzung
- Docker & Docker Compose v2 (Debian / Ubuntu)
- Traefik ab v3.6 mit CrowdSec installieren und konfigurieren
2. Verzeichnis anlegen
Im ersten Schritt legen wir uns ein Verzeichnis an.
mkdir -p /opt/containers/matrix
3. Aktuelle Version herunterladen
Nun kopieren wir uns die aktuelle Version von git.
git clone https://github.com/spantaleev/matrix-docker-ansible-deploy.git /opt/containers/matrix
cd /opt/containers/matrixCode-Sprache: PHP (php)
4. Ansible und Werkzeuge installieren
Damit später das Setup korrekt durchläuft, müssen wir nun noch ein paar Vorbereitungen treffen. Ansible wandert dabei in eine eigene, gekapselte Umgebung. Das System-Python bleibt unangetastet, apt und Ansible kommen sich nicht in die Quere, und ein apt upgrade kann euch die Ansible-Installation nicht zerschießen. Das Werkzeug dafür heißt pipx.
apt update
apt install -y pipx make pwgen
pipx ensurepath trägt ~/.local/bin dauerhaft in euren PATH ein.
pipx ensurepath
Damit die Änderung in der aktuellen Sitzung greift, startet die Shell einmal neu:
exec $SHELL -lCode-Sprache: PHP (php)
Dann Ansible installieren:
pipx install --include-deps ansibleCode-Sprache: PHP (php)
Nun gebt beide Befehle ein:
ansible --version
ansible-galaxy --version
Wenn alles korrekt installiert ist, solltet ihr so etwas sehen:
ansible --version
ansible-galaxy --version
ansible [core 2.21.2]
config file = None
configured module search path = ['/root/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
ansible python module location = /root/.local/share/pipx/venvs/ansible/lib/python3.13/site-packages/ansible
ansible collection location = /root/.ansible/collections:/usr/share/ansible/collections
executable location = /root/.local/bin/ansible
python version = 3.13.5 (main, Jul 15 2026, 20:25:40) [GCC 14.2.0] (/root/.local/share/pipx/venvs/ansible/bin/python)
jinja version = 3.1.6
pyyaml version = 6.0.3 (with libyaml v0.2.5)
ansible-galaxy [core 2.21.2]
config file = None
configured module search path = ['/root/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
ansible python module location = /root/.local/share/pipx/venvs/ansible/lib/python3.13/site-packages/ansible
ansible collection location = /root/.ansible/collections:/usr/share/ansible/collections
executable location = /root/.local/bin/ansible-galaxy
python version = 3.13.5 (main, Jul 15 2026, 20:25:40) [GCC 14.2.0] (/root/.local/share/pipx/venvs/ansible/bin/python)
jinja version = 3.1.6
pyyaml version = 6.0.3 (with libyaml v0.2.5)Code-Sprache: JavaScript (javascript)
5. Beispielkonfiguration erstellen und anpassen
Früher haben wir uns die Beispielkonfiguration von Hand erstellt. Nun holen wir uns ein Skript zur Hilfe:
cd /opt/containers/matrix
make add-inventory-host domain=euredomain.de ip=1.2.3.4
euredomain.de = eure Webseiten Domain
ip = die öffentliche IP eures ServersCode-Sprache: PHP (php)
Nun wird folgendes angelegt:
inventory/hosts
inventory/host_vars/matrix.euredomain.de/vars.yml
Anschließend erstellen wir uns noch ein Skript, welches für uns die Konfiguration weiter anpasst:
nano /opt/containers/matrix/setup.sh
Inhalt:
#!/usr/bin/env bash
set -euo pipefail
FETT="$(tput bold 2>/dev/null || true)"
NORMAL="$(tput sgr0 2>/dev/null || true)"
schritt() { echo; echo "${FETT}[$1]${NORMAL} $2"; }
fehler() { echo; echo "FEHLER: $1" >&2; exit 1; }
erzeuge_secret() {
dd if=/dev/urandom bs=1024 count=16 2>/dev/null \
| LC_ALL=C tr -dc 'A-Za-z0-9' \
| cut -c1-64
}
setze_wert() {
local datei="$1"
local schluessel="$2"
local wert="$3"
SETZE_KEY="$schluessel" SETZE_VAL="$wert" awk '
BEGIN { k = ENVIRON["SETZE_KEY"]; v = ENVIRON["SETZE_VAL"]; gefunden = 0 }
index($0, k ":") == 1 { print k ": " v; gefunden = 1; next }
{ print }
END { exit (gefunden ? 0 : 1) }
' "$datei" > "${datei}.neu"
mv "${datei}.neu" "$datei"
}
echo "==============================================================="
echo " goNeuland.de - Matrix-Konfiguration anpassen"
echo "==============================================================="
echo
echo "Dieses Skript ist fuer eine NEUE Installation gedacht."
echo "Es erzeugt neue Datenbank- und Homeserver-Geheimnisse."
echo "Auf einer laufenden Instanz wuerde das die Datenbank aussperren."
# ---------------------------------------------------------------------
schritt 1 "Dateien ermitteln"
[[ -f setup.yml ]] || fehler "setup.yml nicht gefunden.
Wechsle in das Playbook-Verzeichnis und rufe das Skript dort auf:
cd /opt/containers/matrix"
[[ -f inventory/hosts ]] || fehler "inventory/hosts fehlt.
Lege das Inventory zuerst an:
make add-inventory-host domain=euredomain.de ip=EURE-IP"
HOST_VERZ="$(find inventory/host_vars -mindepth 1 -maxdepth 1 -type d | head -n1)"
[[ -n "$HOST_VERZ" ]] || fehler "In inventory/host_vars liegt kein Verzeichnis."
MATRIX_HOST="$(basename "$HOST_VERZ")"
VARS="${HOST_VERZ}/vars.yml"
[[ -f "$VARS" ]] || fehler "${VARS} nicht gefunden."
echo " Hostname : ${FETT}${MATRIX_HOST}${NORMAL}"
echo " vars.yml : ${VARS}"
echo " Inventory: inventory/hosts"
# ---------------------------------------------------------------------
schritt 2 "Backup anlegen"
BACKUP="backup-goneuland-$(date +%Y%m%d-%H%M%S)"
mkdir -p "$BACKUP"
cp inventory/hosts "$BACKUP/hosts"
cp "$VARS" "$BACKUP/vars.yml"
echo " Gesichert nach: ${FETT}${BACKUP}/${NORMAL}"
# ---------------------------------------------------------------------
schritt 3 "inventory/hosts: ansible_connection=local ergaenzen"
if HOSTS_HOST="$MATRIX_HOST" awk '
BEGIN { h = ENVIRON["HOSTS_HOST"]; geaendert = 0 }
index($0, h) == 1 {
if ($0 ~ /ansible_connection=/) {
print
} else {
print $0 " ansible_connection=local"
geaendert = 1
}
next
}
{ print }
END { exit (geaendert ? 0 : 1) }
' inventory/hosts > inventory/hosts.neu; then
mv inventory/hosts.neu inventory/hosts
echo " Ergaenzt."
else
rm -f inventory/hosts.neu
echo " Host-Zeile hat bereits ein ansible_connection - unveraendert."
fi
# ---------------------------------------------------------------------
schritt 4 "Geheimnisse erzeugen und eintragen"
SECRET_KEY="$(erzeuge_secret)"
DB_PASSWORT="$(erzeuge_secret)"
setze_wert "$VARS" "matrix_homeserver_generic_secret_key" "'${SECRET_KEY}'" \
|| fehler "matrix_homeserver_generic_secret_key nicht in ${VARS} gefunden."
echo " matrix_homeserver_generic_secret_key gesetzt (64 Zeichen)"
setze_wert "$VARS" "postgres_connection_password" "'${DB_PASSWORT}'" \
|| fehler "postgres_connection_password nicht in ${VARS} gefunden."
echo " postgres_connection_password gesetzt (64 Zeichen)"
# ---------------------------------------------------------------------
schritt 5 "IPv6 abschalten"
setze_wert "$VARS" "devture_systemd_docker_base_ipv6_enabled" "false" \
|| fehler "devture_systemd_docker_base_ipv6_enabled nicht in ${VARS} gefunden."
echo " devture_systemd_docker_base_ipv6_enabled: false"
# ---------------------------------------------------------------------
schritt 6 "Reverse-Proxy-Typ umstellen"
setze_wert "$VARS" "matrix_playbook_reverse_proxy_type" "other-traefik-container" \
|| fehler "matrix_playbook_reverse_proxy_type nicht in ${VARS} gefunden."
echo " matrix_playbook_reverse_proxy_type: other-traefik-container"
# ---------------------------------------------------------------------
schritt 7 "goNeuland-Block anhaengen"
cat >> "$VARS" <<'EOF'
########################################################################
# Anbindung an bestehendes Traefik + CrowdSec
########################################################################
# Docker ist bereits installiert - das Playbook soll es nicht anfassen
matrix_playbook_docker_installation_enabled: false
# Name unseres Traefik-Containers und das Netz, in dem er haengt
matrix_playbook_reverse_proxy_hostname: traefik
matrix_playbook_reverse_proxy_container_network: proxy
# Ersetzt die frueheren Einzel-Labels pro Dienst
traefik_entrypoint_primary: websecure
traefik_certResolver_primary: http_resolver
# Wo unser Traefik seine ACME-Datei ablegt
traefik_certs_dumper_ssl_path: "/opt/containers/traefik-crowdsec-stack/data/traefik/certs"
traefik_certs_dumper_ssl_acme_file_name: "acme_letsencrypt.json"
# Basisdomain: nur den well-known-Pfad ausliefern, damit bestehende
# Dienste auf der Basisdomain unberuehrt bleiben
matrix_static_files_container_labels_base_domain_enabled: true
matrix_static_files_container_labels_base_domain_traefik_path_prefix: /.well-known/matrix
matrix_static_files_file_index_html_enabled: false
matrix_static_files_container_labels_base_domain_root_path_redirection_enabled: false
matrix_static_files_container_labels_base_domain_traefik_priority: 100
EOF
echo " Block an ${VARS} angehaengt."
# ---------------------------------------------------------------------
schritt 8 "Fertig"
chmod 600 "$VARS"
Code-Sprache: PHP (php)
Nun starten wir das Skript:
cd /opt/containers/matrix/
chmod +x /opt/containers/matrix/setup.sh
./setup.sh
6. Traefik Anpassungen vornehmen
Nun müssen wir noch Anpassungen für Traefik vornehmen. Dazu öffnen wir folgende Datei:
nano /opt/containers/traefik-crowdsec-stack/data/traefik/traefik.yml
Hier fügt ihr folgendes hinzu:
matrix-federation:
address: ":8448"
matrix-internal-matrix-client-api:
address: ":8008"
forwardedHeaders:
insecure: trueCode-Sprache: JavaScript (javascript)
Bei mir sieht es dann so aus:
.
.
.
entryPoints:
ping:
address: ':88'
web:
address: ':80'
http:
redirections:
entryPoint:
to: websecure
scheme: https
websecure:
address: ':443'
http3: {}
http:
middlewares:
- crowdsec@file
- default@file
matrix-federation:
address: ":8448"
matrix-internal-matrix-client-api:
address: ":8008"
forwardedHeaders:
insecure: true
.
.
.Code-Sprache: JavaScript (javascript)
Als nächstes müssen wir noch einen Port nach außen freigeben:
nano /opt/containers/traefik-crowdsec-stack/compose/traefik.yml
Fügt hier folgendes hinzu:
- mode: host
target: 8448
published: "8448"
protocol: tcpCode-Sprache: JavaScript (javascript)
Sieht bei mir dann so aus:
.
.
.
ports:
- mode: host
target: 80
published: "80"
protocol: tcp
- mode: host
target: 443
published: "443"
protocol: tcp
- mode: host
target: 443
published: "443"
protocol: udp
- mode: host
target: 8448
published: "8448"
protocol: tcp
.
.
.Code-Sprache: JavaScript (javascript)
Nun starten wir Traefik neu, damit alle Änderungen wirksam werden:
cd /opt/containers/traefik-crowdsec-stack/
docker compose up -d --force-recreate
7. CrowdSec AppSec Whitelist erstellen (optional)
Wenn ihr AppSec aktiv habt, dann müsst ihr unbedingt diese Whitelist erstellen, da ihr sonst nach wenigen Sekunden gebannt werdet:
nano /opt/containers/traefik-crowdsec-stack/data/crowdsec/config/appsec-rules/matrix-whitelist.yaml
Inhalt:
name: custom/matrix-whitelist
seclang_rules:
# Whitelist für Matrix Client-Server-API (PUT auf account_data, State, Events)
- "SecRule REQUEST_URI \"@beginsWith /_matrix/\" \"id:14001,phase:1,pass,nolog,noauditlog,ctl:ruleRemoveById=911100,msg:'Whitelist Matrix API Methods'\""
# Whitelist für Matrix Event-IDs und MSC-Namensraeume (Punkte/Slashes werden als LFI gewertet)
- "SecRule REQUEST_URI \"@beginsWith /_matrix/\" \"id:14002,phase:2,pass,nolog,noauditlog,ctl:ruleRemoveById=949110,ctl:ruleRemoveById=980170,msg:'Whitelist Matrix API Anomaly'\""
# Whitelist für Server-Delegation (well-known)
- "SecRule REQUEST_URI \"@beginsWith /.well-known/matrix/\" \"id:14003,phase:1,pass,nolog,noauditlog,ctl:ruleRemoveById=911100,msg:'Whitelist Matrix well-known Methods'\""
- "SecRule REQUEST_URI \"@beginsWith /.well-known/matrix/\" \"id:14004,phase:2,pass,nolog,noauditlog,ctl:ruleRemoveById=949110,ctl:ruleRemoveById=980170,msg:'Whitelist Matrix well-known Anomaly'\""Code-Sprache: PHP (php)
Nun aktivieren wir diese Whitelist noch:
nano /opt/containers/traefik-crowdsec-stack/data/crowdsec/config/appsec-configs/mrks-config.yaml
Fügt dort die neu erstellte Whitelist hinzu:
name: custom/mrks-config
default_remediation: ban
inband_rules:
- custom/matrix-whitelist <--- neu hinzugefügt
.
.
.
- crowdsecurity/base-config
- crowdsecurity/vpatch-*
- crowdsecurity/generic-*
- crowdsecurity/crs
- crowdsecurity/experimental-*
outofband_rules:
- crowdsecurity/appsec-generic-test
Nun starten wir CrowdSec neu:
docker restart crowdsec
9. Ansible Abhängigkeiten installieren
Nun installieren wir alle Ansible Abhängigkeiten. Daugebt ihr folgendes ein:
cd /opt/containers/matrix
make roles
10. Installation per Ansible durchführen
Dazu gebt ihr einfach folgenden Befehl ein:
ansible-playbook -i inventory/hosts setup.yml --tags=install-all,ensure-matrix-users-created
Nach einigen Minuten sollte es dann in etwa so aussehen bei euch:

11. Matrix starten
Dazu gebt ihr einfach folgenden Befehl ein:
ansible-playbook -i inventory/hosts setup.yml --tags=start
Hier solltet ihr wieder eine solche Ausgabe sehen:

12. Systemcheck durchführen
Dazu legen wir uns ein Skript an:
cd /opt/containers/matrix
nano test.sh
Inhalt:
#!/usr/bin/env bash
#
set -uo pipefail
FETT="$(tput bold 2>/dev/null || true)"
NORMAL="$(tput sgr0 2>/dev/null || true)"
OK=" ${FETT}OK${NORMAL} "
NOK=" ${FETT}FEHLER${NORMAL} "
INFO=" HINWEIS"
PROBLEME=0
melde_ok() { echo "${OK}$1"; }
melde_nok() { echo "${NOK}$1"; PROBLEME=$((PROBLEME + 1)); }
# Liest einen Wert aus der vars.yml und entfernt Anfuehrungszeichen
hole_wert() {
awk -v k="$1" '
index($0, k ":") == 1 {
sub("^" k ":[ \t]*", "")
gsub(/^["'\'']|["'\'']$/, "")
sub(/[ \t]*#.*$/, "")
print
exit
}
' "$VARS"
}
echo "==============================================================="
echo " goNeuland - Matrix-Delegation pruefen"
echo "==============================================================="
# ---------------------------------------------------------------------
# Domain ermitteln
# ---------------------------------------------------------------------
[[ -f setup.yml ]] || { echo "FEHLER: Bitte im Playbook-Verzeichnis aufrufen (/opt/containers/matrix)." >&2; exit 1; }
HOST_VERZ="$(find inventory/host_vars -mindepth 1 -maxdepth 1 -type d 2>/dev/null | head -n1)"
[[ -n "$HOST_VERZ" ]] || { echo "FEHLER: In inventory/host_vars liegt kein Verzeichnis." >&2; exit 1; }
VARS="${HOST_VERZ}/vars.yml"
[[ -f "$VARS" ]] || { echo "FEHLER: ${VARS} nicht gefunden." >&2; exit 1; }
BASIS_DOMAIN="$(hole_wert matrix_domain)"
[[ -n "$BASIS_DOMAIN" ]] || { echo "FEHLER: matrix_domain steht nicht in ${VARS}." >&2; exit 1; }
# Kann in der vars.yml ueberschrieben sein, Standard ist matrix.<domain>
MATRIX_FQDN="$(hole_wert matrix_server_fqn_matrix)"
[[ -n "$MATRIX_FQDN" ]] || MATRIX_FQDN="matrix.${BASIS_DOMAIN}"
echo
echo " Basisdomain : ${FETT}${BASIS_DOMAIN}${NORMAL}"
echo " Homeserver : ${FETT}${MATRIX_FQDN}${NORMAL}"
# ---------------------------------------------------------------------
# Abruf
# ---------------------------------------------------------------------
KOPF="$(mktemp)"
trap 'rm -f "$KOPF"' EXIT
abrufen() {
: > "$KOPF"
BODY="$(curl -sS --max-time 10 -D "$KOPF" "$1" 2>&1)"
CURL_RC=$?
STATUS="$(awk 'toupper($1) ~ /^HTTP/ {code=$2} END {print code}' "$KOPF")"
CTYPE="$(grep -i '^content-type:' "$KOPF" | tail -n1 | cut -d: -f2- | tr -d '\r' | sed 's/^[ \t]*//;s/[ \t]*$//')"
CORS="$(grep -i '^access-control-allow-origin:' "$KOPF" | tail -n1 | cut -d: -f2- | tr -d '\r' | sed 's/^[ \t]*//;s/[ \t]*$//')"
}
# ---------------------------------------------------------------------
echo
echo "${FETT}[1] /.well-known/matrix/server${NORMAL}"
echo " https://${BASIS_DOMAIN}/.well-known/matrix/server"
echo
abrufen "https://${BASIS_DOMAIN}/.well-known/matrix/server"
if [[ $CURL_RC -ne 0 ]]; then
melde_nok "Abruf fehlgeschlagen: ${BODY}"
elif [[ "$STATUS" != "200" ]]; then
melde_nok "HTTP-Status ${STATUS:-unbekannt} (erwartet: 200)"
[[ "$STATUS" == "404" ]] && echo "${INFO} 404 heisst meist: Der Traefik-Router fuer den well-known-Pfad"
[[ "$STATUS" == "404" ]] && echo "${INFO} fehlt oder der Dienst auf der Basisdomain gewinnt die Route."
else
melde_ok "HTTP-Status 200"
if [[ "$CTYPE" == application/json* ]]; then
melde_ok "Content-Type: ${CTYPE}"
else
melde_nok "Content-Type: ${CTYPE:-fehlt} (erwartet: application/json)"
fi
if grep -q '"m.server"' <<<"$BODY"; then
melde_ok "Schluessel m.server vorhanden"
else
melde_nok "Schluessel m.server fehlt"
fi
if grep -q "${MATRIX_FQDN}:8448" <<<"$BODY"; then
melde_ok "Verweist auf ${MATRIX_FQDN}:8448"
else
melde_nok "Verweist nicht auf ${MATRIX_FQDN}:8448"
fi
echo
echo " Inhalt: ${BODY}"
fi
# ---------------------------------------------------------------------
echo
echo "${FETT}[2] /.well-known/matrix/client${NORMAL}"
echo " https://${BASIS_DOMAIN}/.well-known/matrix/client"
echo
abrufen "https://${BASIS_DOMAIN}/.well-known/matrix/client"
if [[ $CURL_RC -ne 0 ]]; then
melde_nok "Abruf fehlgeschlagen: ${BODY}"
elif [[ "$STATUS" != "200" ]]; then
melde_nok "HTTP-Status ${STATUS:-unbekannt} (erwartet: 200)"
else
melde_ok "HTTP-Status 200"
if [[ "$CTYPE" == application/json* ]]; then
melde_ok "Content-Type: ${CTYPE}"
else
melde_nok "Content-Type: ${CTYPE:-fehlt} (erwartet: application/json)"
fi
if grep -q '"m.homeserver"' <<<"$BODY"; then
melde_ok "Schluessel m.homeserver vorhanden"
else
melde_nok "Schluessel m.homeserver fehlt"
fi
if grep -q "https://${MATRIX_FQDN}" <<<"$BODY"; then
melde_ok "base_url zeigt auf https://${MATRIX_FQDN}"
else
melde_nok "base_url zeigt nicht auf https://${MATRIX_FQDN}"
fi
if [[ -n "$CORS" ]]; then
melde_ok "Access-Control-Allow-Origin: ${CORS}"
else
melde_nok "Access-Control-Allow-Origin fehlt - Browser-Clients wie Element lesen die Datei nicht"
fi
echo
fi
# ---------------------------------------------------------------------
echo
echo "==============================================================="
if [[ $PROBLEME -eq 0 ]]; then
echo " ${FETT}Delegation sieht korrekt aus.${NORMAL}"
echo
echo " Abschliessend extern gegenpruefen (gegen die BASISDOMAIN):"
echo " https://federationtester.matrix.org/#${BASIS_DOMAIN}"
else
echo " ${FETT}${PROBLEME} Punkt(e) zu klaeren.${NORMAL}"
echo
echo " Naechste Schritte:"
echo " 1. Router pruefen: docker logs traefik | tail -50"
echo " 2. Dienst neu ausrollen:"
echo " ansible-playbook -i inventory/hosts setup.yml \\"
echo " --tags=install-matrix-static-files,start"
fi
echo "==============================================================="
echo
exit $(( PROBLEME > 0 ? 1 : 0 ))Code-Sprache: PHP (php)
Nun führen wir dieses Skript aus:
chmod +x test.sh
./test.sh
Wenn alles korrekt ist, geht ihr auf den Link, der euch ganz unten im Skript angezeigt wird:
===============================================================
Delegation sieht korrekt aus.
Abschliessend extern gegenpruefen (gegen die BASISDOMAIN):
https://federationtester.matrix.org/#euredomain.deCode-Sprache: PHP (php)
Wenn alles gut ist, dann solltet ihr folgende Ausgabe sehen:

Zum Schluss könnt ihr noch einen Selbsttest durchführen:
ansible-playbook -i inventory/hosts setup.yml --tags=self-checkCode-Sprache: PHP (php)
13. Benutzer anlegen
Nun legen wir unseren ersten Benutzer an. Dazu gebt ihr folgendes ein:
cd /opt/containers/matrix
ansible-playbook -i inventory/hosts setup.yml \
--tags=register-user \
--extra-vars='username=alice password=EIN_SICHERES_PASSWORT admin=yes'
Hier gibt es die Möglichkeit auch Benutzer ohne Admin Rechte anzulegen (admin=no)Code-Sprache: PHP (php)
14. Element Webclient nutzen
Nun nutzen wir unseren Element Webclient. Geht dazu auf folgende Seite matrix.euredomain.de
Hier sollte nun folgendes erscheinen:

Klickt nun auf anmelden. Der Server sollte nun bereits eingetragen sein und ihr gebt einfach die Daten des Benutzers an, welchen ihr eben angelegt habt.

Wenn alles korrekt war, dann solltet ihr folgendes sehen:

15. Möglichkeiten der Benutzerverwaltung
Nun könnt ihr noch Synapse Admin zur Benutzerverwaltung installieren. Eine Anleitung dazu findet ihr hier.
Zusätzlich habe ich euch hier noch einige weitere Möglichkeiten zusammengefasst.
16. Matrix E-Mail aktivieren
Ihr könnt euren E-Mail Account hinzufügen, damit Matrix E-Mails senden kann. Dazu öffnet ihr folgende Datei. Wichtig ist, dass ihr hier das Verzeichnis entsprechend eurer Domain anpassen müsst.
nano /opt/containers/matrix/inventory/host_vars/matrix.euredomain.de/vars.yml
Nun fügt ihr folgendes hinzu:
exim_relay_sender_address: "absender@euredomain.de"
exim_relay_relay_use: true
exim_relay_relay_host_name: "mail.euredomain.de"
exim_relay_relay_host_port: 587
exim_relay_relay_auth: true
exim_relay_relay_auth_username: "absender@euredomain.de"
exim_relay_relay_auth_password: "euerPasswort"Code-Sprache: CSS (css)
Nun müsst ihr noch folgenden Befehl ausführen, damit Matrix eure Einstellungen übernimmt:
cd /opt/containers/matrix
ansible-playbook -i inventory/hosts setup.yml --tags=setup-all,start
17. Matrix updaten
Updates könnt ihr sehr schnell durchführen. Dazu gebt ihr einfach folgenden Befehl ein:
cd /opt/containers/matrix
git pull
Nun wurden alle Updates heruntergeladen. Jetzt solltet ihr euch die Änderungen anschauen. Dies kann wichtig sein, falls etwas grundsätzliches Verändert wurde. Dazu gebt ihr folgendes ein:
nano CHANGELOG.mdCode-Sprache: CSS (css)
Jetzt könnt ihr das Installationsskript ausführen und anschließend Matrix erneut starten.
ansible-playbook -i inventory/hosts setup.yml --tags=setup-all,start
18. Matrix stoppen / beenden
Wenn ihr Matrix beenden möchtet, so gebt ihr einfach folgenden Befehl ein:
cd /opt/containers/matrix
ansible-playbook -i inventory/hosts setup.yml --tags=stop
19. Matrix deinstallieren / löschen
Wenn ihr Matrix komplett löschen wollt, so müsst ihr nur folgenden Code ausführen:
/matrix/bin/remove-all
Jetzt seht ihr folgenden Dialog:
WARNING! You are about to remove everything the playbook installs for matrix.euredomain.de: matrix, docker images,...
If you're sure you want to do this, type: 'Yes, I really want to remove everything!'Code-Sprache: JavaScript (javascript)
Gebt also nun “Yes, I really want to remove everything!” ein, um Matrix zu deinstallieren.
20. WhatsApp / Telegram / … Gateway
Hier findet ihr die Anleitung, wie ihr Gateways zu euren bisherigen Chat Diensten integrieren könnt. Ich habe diese Integration beispielhaft für WhatsApp und Telegram durchgeführt.

Wollte schon lange mal Matrix-Server selber hosten, aber wie ich sehe ist der Aufwand immens und erinnert mich an die andere populäre Bloatware Nextcloud.
Vielleicht könnte man Dendrite als Matrix Server anstelle von Synapse vorstellen.
Ich muss leider noch einmal lästig sein mit einer Bitte.
Klappt bei euch die Federation-Verifizierung in Schritt 12 mit den letzten Anpassungen noch?
Ich hoffe, ich nerve nicht bereits, aber die Matrix-Anleitung hier ist für mich persönlich die wichtigste Anleitung im Blog, da ich darüber mit Family und Freunden kommuniziere.
Installation hat nur einmal bei mir geklappt auf dem gleichen Server.
Beim zweiten Mal komme ich nicht mehr auf die Startseite und bekomme nur einen SSL Fehler. Einen Systemcheck durchführen führt zu einem SRV Fehler. Dann gab es noch einen Datenbankfehler. Ich gebe leider auf…!
I know its a bit off Topic but maybe someone found a Solution.
When using the Element app installed by Google Play Store you only can choose the “Notification Method”-> “Google Services” when using Element from F-Droid you only have the Option “Background Synchronization” which is Battery Consuming if you want a short Sync Intervall.
I tried ntfy as a Notification Method and it seems to work good and is less intense on Battery.
BUT All my friend are just ok with installing the Element App from the Play store, I would not be able to get them to also install and configure ntfy.
So does anyone found a way to make “Google Services” as Notification method working with this Matrix Synapse Server Setup ?
Vielen Dank für die Anleitung!
Leider ist sie seit dem 15.1.2024 veraltet, nginx wurde komplett aussortiert, und funktioniert nicht mehr.
Wäre toll, wenn der Autor ein Update machen könnte. Danke!
Hallo zusammen,
vielen Dank für die tollen Step by Step Anleitungen 👍
Mir ist eine kleine Änderung bei der Einbindung des Mailversand aufgefallen.
Laut Fehlermeldung wurde auf Exim umgestellt
Fehlermeldung:
The matrix-mailer role in the playbook has been replaced with the exim-relay role
Das ganze lässt sich wirklich leicht anpassen, aber eventuell könnte man die Anleitung mal anpassen?
ALT:
matrix_mailer_sender_address: “eMail@Domain.de”
matrix_mailer_relay_use: true
matrix_mailer_relay_host_name: “FQDN”
matrix_mailer_relay_host_port: 587
matrix_mailer_relay_auth: true
matrix_mailer_relay_auth_username: “login”
matrix_mailer_relay_auth_password: “passwort”
NEU:
exim_relay_sender_address: “eMail@Domain.de”
exim_relay_relay_use: true
exim_relay_relay_host_name: “FQDN”
exim_relay_relay_host_port: 587
exim_relay_relay_auth: true
exim_relay_relay_auth_username: “login”
exim_relay_relay_auth_password: “passwort”
Hallo Meine Freunde 🙂
Nach 5 Wochen endlich aus dem Krankenhaus herausen .
Weiß einer ob es irgend eine Änderung gab?
Weil ich kam jetzt grad nach hause .. Und wollte gleich meinen Server komplett up-to-date machen.
Soweit hat alles funktioniert. Nur wenn ich den Matrix ordner mit gitpull up-to-date bringen will . Bricht er mir die ganze sache ab wegen einen Merge Fehler
Hatte das schon mal jemand?
Kann mir da jemand weiterhelfen.. Hab angst das am ende alles weg ist.
Nachdem beim zerstörten Server der Umzug vom Matrix samt Daten reibungslos geklappt hat. Wäre das jetzt nicht so prickelnd ^^
Wäre fuer jede Hilfe Dankbar :/
Und natürlich nachträglich noch an alle Ein wunderschönes Neues Jahr 🙂
Hoffe ihr habt schön fleißig gefeiert
greez Gn0me
Problem hat sich gelöst. git reset –hard
und dann git pull. Fehler behoben alles läuft wies soll :>
Hallo Christian.
Ich habe auf meinem Server Mastodon laufen – meinedomain.de. Kann ich dann paralell dazu mit deiner Anleitung einen Matrix Server auf matrix.meinedomain.de betreiben?
huhu Christian . ICh hätte mal eine frage. Und zwar hat es mir vor paar tagen meinen server zerlegt. der neue steht schon mit traefik und crowd…. Jetzt aber meine frage.. jetzt hät ich versuch meine matrix installation auf den neuen server umzuziehen..
Mit den ordner /opt/containers/..
hat soweit ich auch super funktioniert.. Nur leider sind dann alle registrierten daten weg. Kann das sein das ich irgend was überseh ? :>
Schönes wochenende noch 🙂
Hallo zusammen. Leider bekomme ich beim installieren diesen Fehler. Wäre super wenn mir jemand helfen könnte. Danke 🙂
TASK [galaxy/com.devture.ansible.role.postgres : Ensure Postgres systemd service installed]
***
An exception occurred during task execution. To see the full traceback, use -vvv. The error
was: ansible.errors.AnsibleError: An unhandled exception occurred while templating ‘{{ devtu
re_postgres_postgres_process_extra_arguments_auto + devture_postgres_postgres_process_extra_
arguments_custom }}’. Error was a <class ‘ansible.errors.AnsibleError’>, original message: A
n unhandled exception occurred while templating ‘[“-c ‘max_connections={{ devture_postgres_m
ax_connections|int }}'”, “-c ‘shared_buffers={{ (devture_postgres_shared_buffers|int/1024)|i
nt }}MB'”, “-c ‘effective_cache_size={{ (devture_postgres_effective_cache_size|int/1024)|int
}}MB'”, “-c ‘maintenance_work_mem={{ (devture_postgres_maintenance_work_mem|int/1024)|int }
}MB'”, “-c ‘checkpoint_completion_target={{ devture_postgres_checkpoint_completion_target }}
‘”, “-c ‘wal_buffers={{ (devture_postgres_wal_buffers|int/1024)|int }}MB'”, “-c ‘default_sta
tistics_target={{ devture_postgres_default_statistics_target|int }}'”, “-c ‘random_page_cost
={{ devture_postgres_random_page_cost }}'”, “-c ‘effective_io_concurrency={{ devture_postgre
s_effective_io_concurrency|int }}'”, “-c ‘work_mem={{ devture_postgres_work_mem|int }}kB'”,
“-c ‘huge_pages={{ devture_postgres_huge_pages }}'”, “-c ‘min_wal_size={{ (devture_postgres_
min_wal_size|int/1024)|int }}MB'”, “-c ‘max_wal_size={{ (devture_postgres_max_wal_size|int/1
024)|int }}MB'”, “-c ‘max_worker_processes={{ devture_postgres_max_worker_processes|int }}'”
, “-c ‘max_parallel_workers={{ devture_postgres_max_parallel_workers|int }}'”, “-c ‘max_para
llel_workers_per_gather={{ devture_postgres_max_parallel_workers_per_gather|int }}'”, “-c ‘m
ax_parallel_maintenance_workers={{ devture_postgres_max_parallel_maintenance_workers|int }}’
“]’. Error was a <class ‘ansible.errors.AnsibleError’>, original message: An unhandled excep
tion occurred while templating ‘{{ ’64’ if devture_postgres_work_mem_raw|int <= 64 else devt
ure_postgres_work_mem_raw|int }}’. Error was a <class ‘ansible.errors.AnsibleError’>, origin
al message: An unhandled exception occurred while templating ‘{{ ((devture_postgres_memtotal
_kb|int – devture_postgres_shared_buffers|int)/(devture_postgres_max_connections|int*3))/dev
ture_postgres_max_parallel_workers_per_gather|int }}’. Error was a <class ‘ansible.errors.An
sibleError’>, original message: An unhandled exception occurred while templating ‘{{ (‘4’ if
ansible_processor_nproc/2 > 4 else ansible_processor_nproc|int/2) | round(0, ‘ceil’) | int
}}’. Error was a <class ‘ansible.errors.AnsibleError’>, original message: Unexpected templat
ing type error occurred on ({{ (‘4’ if ansible_processor_nproc/2 > 4 else ansible_processor_
nproc|int/2) | round(0, ‘ceil’) | int }}): must be real number, not str. must be real number
, not str
fatal: [matrix.x.art]: FAILED! => changed=false
msg: ‘AnsibleError: An unhandled exception occurred while templating ”{{ devture_postgres
_postgres_process_extra_arguments_auto + devture_postgres_postgres_process_extra_arguments_c
ustom }}”. Error was a <class ”ansible.errors.AnsibleError”>, original message: An unhand
led exception occurred while templating ”[“-c ”max_connections={{ devture_postgres_max_con
nections|int }}””, “-c ”shared_buffers={{ (devture_postgres_shared_buffers|int/1024)|int }
}MB””, “-c ”effective_cache_size={{ (devture_postgres_effective_cache_size|int/1024)|int }
}MB””, “-c ”maintenance_work_mem={{ (devture_postgres_maintenance_work_mem|int/1024)|int }
}MB””, “-c ”checkpoint_completion_target={{ devture_postgres_checkpoint_completion_target
}}””, “-c ”wal_buffers={{ (devture_postgres_wal_buffers|int/1024)|int }}MB””, “-c ”defau
lt_statistics_target={{ devture_postgres_default_statistics_target|int }}””, “-c ”random_p
age_cost={{ devture_postgres_random_page_cost }}””, “-c ”effective_io_concurrency={{
devture_postgres_effective_io_concurrency|int }}””, “-c ”work_mem={{ devture_postgres_
work_mem|int }}kB””, “-c ”huge_pages={{ devture_postgres_huge_pages }}””, “-c ”min_wal_s
ize={{ (devture_postgres_min_wal_size|int/1024)|int }}MB””, “-c ”max_wal_size={{ (devture_
postgres_max_wal_size|int/1024)|int }}MB””, “-c ”max_worker_processes={{ devture_postgres_
max_worker_processes|int }}””, “-c ”max_parallel_workers={{ devture_postgres_max_parallel_
workers|int }}””, “-c ”max_parallel_workers_per_gather={{ devture_postgres_max_parallel_wo
rkers_per_gather|int }}””, “-c ”max_parallel_maintenance_workers={{ devture_postgres_max_p
arallel_maintenance_workers|int }}””]”. Error was a <class ”ansible.errors.AnsibleError”
>, original message: An unhandled exception occurred while templating ”{{ ”64” if devture
_postgres_work_mem_raw|int <= 64 else devture_postgres_work_mem_raw|int }}”. Error was a <c
lass ”ansible.errors.AnsibleError”>, original message: An unhandled exception occurred
while templating ”{{ ((devture_postgres_memtotal_kb|int – devture_postgres_shared_buffe
rs|int)/(devture_postgres_max_connections|int*3))/devture_postgres_max_parallel_workers_per_
gather|int }}”. Error was a <class ”ansible.errors.AnsibleError”>, original message: An u
nhandled exception occurred while templating ”{{ (”4” if ansible_processor_nproc/2 > 4 el
se ansible_processor_nproc|int/2) | round(0, ”ceil”) | int }}”. Error was a <class ”ansi
ble.errors.AnsibleError”>, original message: Unexpected templating type error occurred on (
{{ (”4” if ansible_processor_nproc/2 > 4 else ansible_processor_nproc|int/2) | round(0, ”
ceil”) | int }}): must be real number, not str. must be real number, not str’