<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Element – Serverküche</title><link>https://serverkueche.de/tags/element/</link><description>Element – Neueste Beiträge von Serverküche</description><generator>Hugo</generator><language>de-DE</language><managingEditor>feedback@serverkueche.de (Serverküche)</managingEditor><webMaster>feedback@serverkueche.de (Serverküche)</webMaster><copyright>2026 Serverküche</copyright><lastBuildDate>Thu, 12 Nov 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://serverkueche.de/tags/element/index.xml" rel="self" type="application/rss+xml"/><item><title>Matrix Teil 2: Element Web, Föderation und Admin-Alltag</title><link>https://serverkueche.de/tutorials/matrix-element-federation/</link><pubDate>Thu, 12 Nov 2026 00:00:00 +0000</pubDate><author>feedback@serverkueche.de (Serverküche)</author><guid>https://serverkueche.de/tutorials/matrix-element-federation/</guid><description>Element Web neben Synapse auf derselben Domain, Geräteverifizierung, verschlüsselte Räume, Einladungstokens und die Admin-API für Konten, Räume und Medien.</description><content:encoded><![CDATA[<p>Der Server aus Teil 1 läuft, föderiert und hat genau ein Konto. Nur sieht ihn niemand. Jetzt kommt
die Oberfläche dazu, und mit ihr die Fragen, die im Alltag wirklich auftauchen: Wie kommen Freunde
rein, ohne dass die Registrierung offensteht? Wer räumt die Räume auf?</p>
<h2 id="was-bauen-wir">Was bauen wir?</h2>
<p>Wir stellen Element Web 1.12.33 neben Synapse 1.164.2, und zwar auf derselben Domain; Traefik teilt
die Anfragen nach Pfad auf. Danach hast du eine fertige Chat-Oberfläche im Browser, einen
Ende-zu-Ende-verschlüsselten Raum, eine Registrierung, die nur mit Einladungstoken funktioniert, und
die Admin-Handgriffe für Konten, Räume und Medien.</p>
<p>Das ist Teil 2 der Matrix-Serie. Teil 1 hat den Homeserver, die Delegation und die Föderation
aufgebaut; hier geht es um alles, was danach kommt.</p>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-sky-300 bg-sky-50 dark:border-sky-800 dark:bg-sky-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">ℹ️</span>Warum eine Domain und nicht zwei
  </p>
  <div class="prose-kitchen text-sm">Zwei Subdomains (<code>matrix.</code> für den Server, <code>chat.</code> für Element) sind der üblichere Weg und völlig
in Ordnung. Wir machen es hier bewusst auf nur einer Domain, weil das der schwierigere Fall ist:
Sobald sich zwei Dienste einen Hostnamen teilen, entscheidet die Reihenfolge der Traefik-Regeln
darüber, wer eine Anfrage bekommt, und genau da geht es in der Praxis schief. Wer zwei Subdomains
hat, überspringt Schritt 1 und lässt in Schritt 3 die <code>priority</code>-Zeilen weg.</div>
</div>
<h2 id="voraussetzungen">Voraussetzungen</h2>
<ul>
<li>Ein laufender Matrix-Server aus
<a href="/tutorials/matrix-synapse-server/">Matrix: der eigene Chat-Server mit Synapse (Teil 1)</a>,
inklusive grünem Föderationstest</li>
<li>Debian 13 mit Docker und einem Traefik, der die Zertifikate holt</li>
<li>Die Domain aus Teil 1; der <code>server_name</code> bleibt unverändert</li>
<li>Etwa 60 Minuten, davon ein guter Teil für den ersten Login</li>
</ul>
<div class="not-prose my-6 overflow-hidden rounded-xl border border-paprika-200 bg-paprika-50 dark:border-paprika-800 dark:bg-paprika-900/20"
     data-track-content data-content-name="Affiliate-Box · /tutorials/matrix-element-federation/" data-content-piece="VPS 2000 G12.5">
  <div class="flex items-center justify-between border-b border-paprika-200 bg-paprika-100 px-4 py-1.5 text-xs font-semibold uppercase tracking-wide text-paprika-700 dark:border-paprika-800 dark:bg-paprika-900/40 dark:text-paprika-300">
    <span>🍳 Empfehlung</span>
    <span title="Mit * markierte Links sind Affiliate-Links.">Anzeige</span>
  </div>
  <div class="flex flex-col gap-4 p-4 sm:flex-row sm:items-center sm:justify-between">
    <div>
      <p class="text-lg font-bold text-slate-900 dark:text-white">VPS 2000 G12.5</p>
      <p class="mt-1 text-sm text-slate-600 dark:text-slate-300">8 vCore · 16 GB RAM · 256 GB SSD</p>
      <p class="mt-1 text-sm font-semibold text-paprika-700 dark:text-paprika-400">ab 26,92 €/Monat</p>
      <p class="mt-2 text-sm text-slate-600 dark:text-slate-400">Synapse wächst mit dem ersten großen föderierten Raum von gut 120 auf rund 280 MB. Element selbst braucht nichts – unter 5 MB.</p>
    </div>
    <a href="https://www.netcup.com/de/server/vps/vps-2000-g12.5-24m-eu?ref=44083#vps-2000-g12.5-12m-eu" rel="sponsored noopener" target="_blank"
   data-track-event="Affiliate|netcup: Affiliate-Box|VPS 2000 G12.5 · {page}"
   class="inline-flex shrink-0 items-center justify-center rounded-lg bg-paprika-600 px-5 py-2.5 font-semibold text-white transition-colors hover:bg-paprika-700">
  Zu netcup →
</a>

  </div><div class="px-4 pb-4"><div class="not-prose my-3 rounded-lg border border-herb-500/40 bg-herb-50 px-3 py-2 text-sm text-slate-700 dark:bg-herb-900/20 dark:text-slate-200"
     data-voucher-api="vps-2000" data-voucher-msg-empty="Gerade ist für diesen Tarif kein Code frei – nimm den 5,95-€-Code." data-voucher-msg-limit="Für heute sind keine weiteren Codes drin – nimm den 5,95-€-Code oder versuch es morgen." data-voucher-msg-error="Der Code ließ sich gerade nicht abrufen – nimm den 5,95-€-Code."
     data-voucher-hint="Warenkorb vorher leeren – der Code legt den Server selbst hinein.">
  <p class="flex flex-wrap items-center gap-x-2 gap-y-1"><span>💶 <span class="font-semibold">1 Monat gratis</span>:</span>
    <button type="button" data-voucher-reveal data-copy-title="Zum Kopieren klicken"
            class="cursor-pointer rounded bg-white px-2 py-0.5 font-mono text-sm font-semibold text-herb-800 hover:ring-2 hover:ring-herb-500/40 dark:bg-slate-800 dark:text-herb-400">Code anzeigen</button>
    <button type="button" data-voucher-next hidden
            class="rounded border border-herb-500/40 px-2 py-0.5 text-xs text-herb-800 hover:bg-white dark:text-herb-400 dark:hover:bg-slate-800">anderer Code</button>
  </p><p class="mt-1 text-xs text-slate-500 dark:text-slate-400" data-voucher-hint-target>
    Einmal einlösbar, gilt für VPS 2000 G12.5.
    <a href="https://www.netcup.com/de/checkout/warenkorb?ref=44083" rel="sponsored noopener" target="_blank"
       data-track-event="Affiliate|netcup: Gutschein einlösen|VPS 2000 G12.5 · {page}"
       class="font-medium text-herb-800 underline underline-offset-2 hover:text-herb-900 dark:text-herb-400">Im Warenkorb einlösen →</a>
  </p><p class="mt-2 flex flex-wrap items-center gap-x-2 gap-y-1 border-t border-herb-500/30 pt-2">
    <span>💶 <span class="font-semibold">5,95 € Gutschein</span> für netcup-Neukunden:</span>
    <button type="button" data-voucher-code data-track-voucher="36nc17844976032"
            title="Zum Kopieren klicken" class="cursor-pointer rounded bg-white px-2 py-0.5 font-mono text-sm font-semibold text-herb-800 hover:ring-2 hover:ring-herb-500/40 dark:bg-slate-800 dark:text-herb-400">36nc17844976032</button>
    <span class="text-xs text-slate-500 dark:text-slate-400">immer gültig · nicht für Domains und VPS Lite</span>
  </p><p class="mt-1 text-xs"><a href="/netcup-gutschein/" class="font-medium text-herb-800 underline underline-offset-2 hover:text-herb-900 dark:text-herb-400">Alle netcup-Gutscheine →</a></p>
</div></div>
</div>

<h2 id="schritt-für-schritt">Schritt für Schritt</h2>
<h3 id="schritt-1-wie-sich-zwei-dienste-einen-hostnamen-teilen">Schritt 1: Wie sich zwei Dienste einen Hostnamen teilen</h3>
<p>Synapse beansprucht drei Pfadpräfixe und sonst nichts:</p>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">/_matrix         # die Client- und Föderations-API
</span></span><span class="line"><span class="cl">/_synapse        # die Verwaltungs-API
</span></span><span class="line"><span class="cl">/.well-known/matrix  # die Delegation aus Teil 1</span></span></code></pre></div>
</div>
<p>Alles andere (die Startseite, JavaScript, Bilder, Schriften) gehört Element. Traefik kann das
auseinanderhalten, wenn man ihm sagt, welche Regel zuerst geprüft wird. Dafür gibt es
<code>priority</code>: Je höher die Zahl, desto früher wird die Regel ausgewertet.</p>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Regel                                             priority   Ergebnis
</span></span><span class="line"><span class="cl">Host(`DEINE_DOMAIN`) &amp;&amp; PathPrefix(`/_matrix`)       100      Synapse
</span></span><span class="line"><span class="cl">Host(`DEINE_DOMAIN`)                                  10      Element</span></span></code></pre></div>
</div>
<p>Ohne <code>priority</code> sortiert Traefik nach Länge der Regel, und die längere gewinnt. Das führt hier
zufällig zum richtigen Ergebnis. Verlassen kannst du dich darauf nicht, denn das kann sich mit
jeder zusätzlichen Regel ändern. Schreib die Zahlen hin.</p>
<h3 id="schritt-2-element-konfigurieren">Schritt 2: Element konfigurieren</h3>
<p>Element Web ist reines JavaScript im Browser; die gesamte Einstellung steckt in einer einzigen
<code>config.json</code>. Leg sie neben die <code>compose.yaml</code>:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">cd</span> /opt/matrix
</span></span><span class="line"><span class="cl">nano element-config.json</span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">JSON</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;default_server_config&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;m.homeserver&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;base_url&#34;</span><span class="p">:</span> <span class="s2">&#34;https://DEINE_DOMAIN&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;server_name&#34;</span><span class="p">:</span> <span class="s2">&#34;DEINE_DOMAIN&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;brand&#34;</span><span class="p">:</span> <span class="s2">&#34;Kochabend Chat&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;disable_custom_urls&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;disable_guests&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;disable_3pid_login&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;default_country_code&#34;</span><span class="p">:</span> <span class="s2">&#34;DE&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;show_labs_settings&#34;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;room_directory&#34;</span><span class="p">:</span> <span class="p">{</span> <span class="nt">&#34;servers&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;DEINE_DOMAIN&#34;</span><span class="p">]</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;setting_defaults&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;breadcrumbs&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;UIFeature.feedback&#34;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;UIFeature.registration&#34;</span><span class="p">:</span> <span class="kc">false</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div>
</div>
<p>Die wichtigen Schalter:</p>
<ul>
<li><code>disable_custom_urls: true</code>: Die Anmeldemaske zeigt deinen Server fest an, statt ein
Eingabefeld für beliebige Homeserver. Ohne das melden sich Leute versehentlich bei matrix.org an
und wundern sich, warum ihr Konto nicht existiert.</li>
<li><code>disable_3pid_login</code> und <code>disable_guests</code>: Kein Login über E-Mail/Telefon (dafür bräuchte
es einen Identitätsserver) und keine Gastkonten.</li>
<li><code>UIFeature.registration: false</code>: Der Link „Konto erstellen&quot; verschwindet. Die Registrierung
regeln wir in Schritt 7 über Tokens, nicht über einen offenen Knopf.</li>
<li><code>show_labs_settings: false</code>: Element hat eine ganze Schublade unfertiger Funktionen. Auf
einem Server, den andere mitbenutzen, bleibt die besser zu.</li>
</ul>
<p>Die Datei muss für den Container <strong>lesbar</strong> sein:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">chmod <span class="m">0644</span> element-config.json</span></span></code></pre></div>
</div>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-amber-400 bg-amber-50 dark:border-amber-700 dark:bg-amber-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">⚠️</span>Wenn du den Server nach Lynis gehärtet hast
  </p>
  <div class="prose-kitchen text-sm">Ein <code>umask 027</code> in <code>/etc/login.defs</code>, wie es das
<a href="/tutorials/lynis-sicherheits-audit/">Sicherheits-Audit mit Lynis</a> vorschlägt, erzeugt neue
Dateien mit <code>0640</code>. Element läuft im Container unentprivilegiert und kann die Datei dann nicht
lesen; der Container startet mit <code>cp: can't open '/app/config.json': Permission denied</code> gar nicht
erst. Das <code>chmod</code> ist deshalb Pflicht.</div>
</div>
<h3 id="schritt-3-den-stack-erweitern">Schritt 3: Den Stack erweitern</h3>
<p>In der <code>compose.yaml</code> aus Teil 1 ändern wir die Traefik-Regel von Synapse und hängen einen zweiten
Dienst an:</p>
<div class="sk-code">
  <span class="sk-code-head">YAML</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="c"># /opt/matrix/compose.yaml (Auszug)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">synapse</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">ghcr.io/element-hq/synapse:v1.164.2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c"># ... unverändert ...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">labels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">traefik.enable=true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">traefik.http.routers.synapse.rule=Host(`DEINE_DOMAIN`) &amp;&amp; (PathPrefix(`/_matrix`) || PathPrefix(`/_synapse`) || PathPrefix(`/.well-known/matrix`))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">traefik.http.routers.synapse.priority=100</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">traefik.http.routers.synapse.entrypoints=websecure</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">traefik.http.routers.synapse.tls.certresolver=le</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">traefik.http.services.synapse.loadbalancer.server.port=8008</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">element</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">vectorim/element-web:v1.12.33</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">element</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./element-config.json:/app/config.json:ro</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">networks</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">proxy]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">labels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">traefik.enable=true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">traefik.http.routers.element.rule=Host(`DEINE_DOMAIN`)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">traefik.http.routers.element.priority=10</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">traefik.http.routers.element.entrypoints=websecure</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">traefik.http.routers.element.tls.certresolver=le</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">traefik.http.services.element.loadbalancer.server.port=80</span></span></span></code></pre></div>
</div>
<p>Element hängt nur im <code>proxy</code>-Netz. Es spricht nie mit der Datenbank und auch nicht direkt mit
Synapse: Die API-Aufrufe macht der Browser des Besuchers, nicht der Container. Das interne Netz
aus Teil 1 bleibt der Datenbank vorbehalten.</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker compose up -d
</span></span><span class="line"><span class="cl">docker compose ps</span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">NAME        STATUS
</span></span><span class="line"><span class="cl">element     Up 20 seconds (healthy)
</span></span><span class="line"><span class="cl">matrix-db   Up 20 seconds (healthy)
</span></span><span class="line"><span class="cl">synapse     Up 20 seconds (healthy)</span></span></code></pre></div>
</div>
<h3 id="schritt-4-beide-wege-gegenprüfen">Schritt 4: Beide Wege gegenprüfen</h3>
<p>Bevor du den Browser aufmachst, prüf von außen, dass die Aufteilung wirklich greift. Erst die
Wurzel, hier muss Element antworten:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">curl -s https://DEINE_DOMAIN/ <span class="p">|</span> grep -o <span class="s2">&#34;&lt;title&gt;[^&lt;]*&lt;/title&gt;&#34;</span></span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">&lt;title&gt;Element&lt;/title&gt;</span></span></code></pre></div>
</div>
<p>Dann die Konfiguration, die Element beim Start lädt:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">curl -s https://DEINE_DOMAIN/config.json <span class="p">|</span> head -5</span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">{
</span></span><span class="line"><span class="cl">  &#34;default_server_config&#34;: {
</span></span><span class="line"><span class="cl">    &#34;m.homeserver&#34;: {
</span></span><span class="line"><span class="cl">      &#34;base_url&#34;: &#34;https://DEINE_DOMAIN&#34;,</span></span></code></pre></div>
</div>
<p>Und schließlich, dass Synapse seine drei Pfade behalten hat:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">curl -s -o /dev/null -w <span class="s2">&#34;%{http_code}\n&#34;</span> https://DEINE_DOMAIN/_matrix/client/versions
</span></span><span class="line"><span class="cl">curl -s https://DEINE_DOMAIN/.well-known/matrix/server
</span></span><span class="line"><span class="cl">curl -s https://DEINE_DOMAIN/.well-known/matrix/client</span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">200
</span></span><span class="line"><span class="cl">{&#34;m.server&#34;:&#34;DEINE_DOMAIN:443&#34;}
</span></span><span class="line"><span class="cl">{&#34;m.homeserver&#34;:{&#34;base_url&#34;:&#34;https://DEINE_DOMAIN/&#34;}}</span></span></code></pre></div>
</div>
<p>Kommt bei einem der drei Aufrufe HTML statt JSON zurück, hat Element die Anfrage abgefangen. Dann
stimmt die <code>priority</code> nicht. Kommt beim ersten Aufruf JSON statt HTML, ist es umgekehrt.</p>
<h3 id="schritt-5-der-erste-login-und-die-geräteverifizierung">Schritt 5: Der erste Login und die Geräteverifizierung</h3>
<p>Ruf <code>https://DEINE_DOMAIN/</code> im Browser auf. Weil <code>disable_custom_urls</code> gesetzt ist, steht dein
Server fest in der Maske; du gibst nur Benutzernamen und Passwort aus Teil 1 ein.</p>
<p><figure class="my-6"><img src="/tutorials/matrix-element-federation/el-login_hu_f3945be571621a54.webp" srcset="/tutorials/matrix-element-federation/el-login_hu_8d04456c0cbd6952.webp 480w, /tutorials/matrix-element-federation/el-login_hu_f3945be571621a54.webp 768w, /tutorials/matrix-element-federation/el-login_hu_4d7a9a65f64bb09f.webp 1200w, /tutorials/matrix-element-federation/el-login_hu_77f1669e6e434e2c.webp 1920w" sizes="(min-width: 768px) 768px, 100vw"
    width="768" height="432"
    data-full="/tutorials/matrix-element-federation/el-login_hu_6adb444e7bae3748.webp"
    alt="Die Anmeldemaske von Element Web mit fest eingetragenem Homeserver und den Feldern für Benutzername und Passwort" title="Element fragt nur noch nach Benutzername und Passwort – der Homeserver steht fest"
    loading="lazy" decoding="async" class="rounded-lg"><figcaption class="mt-2 text-sm text-center text-slate-500 italic">Element fragt nur noch nach Benutzername und Passwort – der Homeserver steht fest</figcaption></figure></p>
<p>Beim allerersten Gerät richtet Element die Verschlüsselung selbst ein, ohne Rückfrage: Es erzeugt
im Hintergrund einen Cross-Signing-Schlüssel, der deine Geräte als deine ausweist, und eine
Schlüsselsicherung auf dem Server. Der Server speichert davon nur verschlüsselte Kopien; lesen
kann er sie nicht. Zu sehen ist davon nur ein Hinweis oben links:</p>
<p><figure class="my-6"><img src="/tutorials/matrix-element-federation/el-backup-prompt_hu_9c86d22b48386748.webp" srcset="/tutorials/matrix-element-federation/el-backup-prompt_hu_a01370c5b2f88b5c.webp 480w, /tutorials/matrix-element-federation/el-backup-prompt_hu_9c86d22b48386748.webp 768w, /tutorials/matrix-element-federation/el-backup-prompt_hu_4eb6a89021b0a234.webp 1200w, /tutorials/matrix-element-federation/el-backup-prompt_hu_f26383e9fbb898b.webp 1920w" sizes="(min-width: 768px) 768px, 100vw"
    width="768" height="432"
    data-full="/tutorials/matrix-element-federation/el-backup-prompt_hu_370248c433d82c5a.webp"
    alt="Element nach der ersten Anmeldung mit dem Hinweis Back up your chats und den Schaltflächen Dismiss und Continue" title="Der Hinweis nach dem ersten Login – hier geht es über Continue weiter, nicht über Dismiss"
    loading="lazy" decoding="async" class="rounded-lg"><figcaption class="mt-2 text-sm text-center text-slate-500 italic">Der Hinweis nach dem ersten Login – hier geht es über Continue weiter, nicht über Dismiss</figcaption></figure></p>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-paprika-400 bg-paprika-50 dark:border-paprika-700 dark:bg-paprika-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">🔥</span>Den Hinweis auf die Schlüsselsicherung nicht wegklicken
  </p>
  <div class="prose-kitchen text-sm"><p>Der Hinweis „Back up your chats&quot; sagt: „Your chats are backed up automatically. You&rsquo;ll need
your recovery key to restore them if you lose all your devices.&quot; Die Sicherung läuft also
schon, aber ohne Wiederherstellungsschlüssel kommst du nie wieder an sie heran.</p>
<p>Nimm <strong>Continue</strong>. Element öffnet die Einstellungen unter „Encryption&quot;; dort führt „Get recovery
key&quot; und „Continue&quot; zu „Save your recovery key somewhere safe&quot; mit dem Schlüssel. Der gehört in
den Passwortmanager. Wer stattdessen auf „Dismiss&quot; klickt, hat keinen.</p>
<p>Das fällt erst auf, wenn du dich auf einem weiteren Gerät anmeldest. Dann fragt Element mit
„Confirm your digital identity&quot;: „Use another device&quot; oder „Use recovery key&quot;. Ohne Schlüssel
ist die zweite Möglichkeit ausgegraut, und sind alle alten Geräte weg, bleibt nur „Can&rsquo;t
confirm?&quot;. Das setzt deine Identität zurück, und der Verlauf, der nur auf dem Server liegt, ist
nicht mehr zu entziffern. Auch nicht von dir als Serveradministrator: Ein <code>pg_dump</code> sichert die
Nachrichten, nicht die Schlüssel dazu.</p>
</div>
</div>
<h3 id="schritt-6-der-erste-verschlüsselte-raum">Schritt 6: Der erste verschlüsselte Raum</h3>
<p>Über „New room&quot; legst du einen Raum an. Zwei Einstellungen entscheiden hier über Dinge, die
später feststehen:</p>
<p><figure class="my-6"><img src="/tutorials/matrix-element-federation/el-new-room_hu_2abfc95a4a52200f.webp" srcset="/tutorials/matrix-element-federation/el-new-room_hu_a4d127e70d794188.webp 480w, /tutorials/matrix-element-federation/el-new-room_hu_2abfc95a4a52200f.webp 768w, /tutorials/matrix-element-federation/el-new-room_hu_6116fe40e65fa6b1.webp 1200w, /tutorials/matrix-element-federation/el-new-room_hu_21a85e1a2a41d70b.webp 1920w" sizes="(min-width: 768px) 768px, 100vw"
    width="768" height="432"
    data-full="/tutorials/matrix-element-federation/el-new-room_hu_52a383807415bedd.webp"
    alt="Der Dialog Create a private room mit ausgefülltem Namen, der Auswahl Invite only und aktivierter Ende-zu-Ende-Verschlüsselung" title="Invite only lässt sich später ändern, die Verschlüsselung nicht"
    loading="lazy" decoding="async" class="rounded-lg"><figcaption class="mt-2 text-sm text-center text-slate-500 italic">Invite only lässt sich später ändern, die Verschlüsselung nicht</figcaption></figure></p>
<ul>
<li>„Invite only&quot;: Die Sichtbarkeit lässt sich später ändern.</li>
<li>„Enable end-to-end encryption&quot;: Das lässt sich <strong>nicht</strong> zurücknehmen. Element sagt es
auch dazu: „You can&rsquo;t disable this later. Bridges &amp; most bots won&rsquo;t work yet.&quot; Ein verschlüsselter Raum ist für die meisten Brücken
zu anderen Netzen und für viele Bots nicht nutzbar. Wenn du später ein Telegram- oder
IRC-Bridging planst, leg dafür einen unverschlüsselten Raum an.</li>
</ul>
<p>Danach steht der Raum, und die erste Nachricht geht raus:</p>
<p><figure class="my-6"><img src="/tutorials/matrix-element-federation/el-room_hu_e45a6970f9af2d30.webp" srcset="/tutorials/matrix-element-federation/el-room_hu_2e4e2bb49193cd00.webp 480w, /tutorials/matrix-element-federation/el-room_hu_e45a6970f9af2d30.webp 768w, /tutorials/matrix-element-federation/el-room_hu_9761714310b2da4f.webp 1200w, /tutorials/matrix-element-federation/el-room_hu_6cb18b64db806216.webp 1920w" sizes="(min-width: 768px) 768px, 100vw"
    width="768" height="432"
    data-full="/tutorials/matrix-element-federation/el-room_hu_a001835334524a32.webp"
    alt="Ein verschlüsselter Matrix-Raum in Element mit dem Hinweis Encryption enabled und einer ersten Nachricht in der Zeitleiste" title="Der fertige Raum: Encryption enabled steht als Systemnachricht am Anfang der Zeitleiste"
    loading="lazy" decoding="async" class="rounded-lg"><figcaption class="mt-2 text-sm text-center text-slate-500 italic">Der fertige Raum: Encryption enabled steht als Systemnachricht am Anfang der Zeitleiste</figcaption></figure></p>
<p>Über die Admin-API kannst du gegenprüfen, dass der Server den Raum tatsächlich als verschlüsselt
führt:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">curl -s <span class="s2">&#34;https://DEINE_DOMAIN/_synapse/admin/v1/rooms?limit=5&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  -H <span class="s2">&#34;Authorization: Bearer DEIN_TOKEN&#34;</span></span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">name: Kochabend | members: 1 | encryption: m.megolm.v1.aes-sha2</span></span></code></pre></div>
</div>
<p><code>m.megolm.v1.aes-sha2</code> ist das Gruppenverschlüsselungsverfahren von Matrix. Steht dort <code>null</code>, ist
der Raum offen lesbar, für den Server und für jeden, der die Datenbank in die Finger bekommt.</p>
<h3 id="schritt-7-freunde-einlassen-ohne-die-registrierung-zu-öffnen">Schritt 7: Freunde einlassen, ohne die Registrierung zu öffnen</h3>
<p>In Teil 1 steht <code>enable_registration: false</code>. Das ist richtig, solange du allein bist. Nur müsstest
du jedes weitere Konto von Hand anlegen und das Passwort per Chat verschicken. Der bessere Weg sind
Registrierungstokens: Die Registrierung ist offen, aber nur mit einem Einladungscode, den du
ausgibst.</p>
<div class="sk-code">
  <span class="sk-code-head">YAML</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="c"># /opt/matrix/data/homeserver.yaml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">enable_registration</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">registration_requires_token</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span></span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker compose restart synapse</span></span></code></pre></div>
</div>
<p>Jetzt legst du einen Token an, hier einen, der dreimal benutzt werden darf:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">curl -s -X POST <span class="s2">&#34;https://DEINE_DOMAIN/_synapse/admin/v1/registration_tokens/new&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  -H <span class="s2">&#34;Authorization: Bearer DEIN_TOKEN&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  -H <span class="s2">&#34;Content-Type: application/json&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  -d <span class="s1">&#39;{&#34;uses_allowed&#34;: 3, &#34;length&#34;: 12}&#39;</span></span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">{&#34;token&#34;:&#34;LvM3cFXHXX7H&#34;,&#34;uses_allowed&#34;:3,&#34;pending&#34;:0,&#34;completed&#34;:0,&#34;expiry_time&#34;:null}</span></span></code></pre></div>
</div>
<p>Diese Zeichenfolge schickst du deinen Leuten. Element fragt sie während der Registrierung ab. Dass
ohne Token nichts geht, prüfst du am einfachsten am Anfang des Registrierungsvorgangs:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">curl -s -X POST <span class="s2">&#34;https://DEINE_DOMAIN/_matrix/client/v3/register&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  -H <span class="s2">&#34;Content-Type: application/json&#34;</span> -d <span class="s1">&#39;{}&#39;</span></span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">{&#34;session&#34;:&#34;PklWEdRKlnhHbBVJrwJnwnBG&#34;,
</span></span><span class="line"><span class="cl"> &#34;flows&#34;:[{&#34;stages&#34;:[&#34;m.login.registration_token&#34;,&#34;m.login.dummy&#34;]}],&#34;params&#34;:{}}</span></span></code></pre></div>
</div>
<p>Der Server verlangt jetzt zwei Stufen, und die erste ist der Token. Ohne ihn (oder mit einem
falschen, dann meldet der Server „Invalid registration token&quot;) endet der Vorgang, ohne dass ein
Konto entsteht. Nach einer erfolgreichen Registrierung siehst du den Verbrauch:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">curl -s <span class="s2">&#34;https://DEINE_DOMAIN/_synapse/admin/v1/registration_tokens&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  -H <span class="s2">&#34;Authorization: Bearer DEIN_TOKEN&#34;</span></span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">{&#34;registration_tokens&#34;:[{&#34;token&#34;:&#34;LvM3cFXHXX7H&#34;,&#34;uses_allowed&#34;:3,&#34;pending&#34;:0,&#34;completed&#34;:1,&#34;expiry_time&#34;:null}]}</span></span></code></pre></div>
</div>
<p><code>completed</code> sind fertige Registrierungen, <code>pending</code> angefangene. Ist der Token aufgebraucht, läuft
er ins Leere; du musst nichts abschalten. Mit <code>&quot;expiry_time&quot;</code> (Millisekunden seit 1970) kannst du
ihm zusätzlich ein Ablaufdatum geben.</p>
<h3 id="schritt-8-föderation-im-alltag">Schritt 8: Föderation im Alltag</h3>
<p>Der Föderationstest aus Teil 1 prüft, ob andere deinen Server erreichen. Die andere Richtung,
ob dein Server rauskommt, prüfst du, indem du nach jemandem fragst, den es nur woanders gibt:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">curl -s <span class="s2">&#34;https://DEINE_DOMAIN/_matrix/client/v3/profile/@matthew:matrix.org&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  -H <span class="s2">&#34;Authorization: Bearer DEIN_TOKEN&#34;</span></span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">{&#34;displayname&#34;:&#34;Matthew&#34;,&#34;avatar_url&#34;:&#34;mxc://matrix.org/oUxxDyzQOHdVDMxgwFzyCWEe&#34;}</span></span></code></pre></div>
</div>
<p>Diese Antwort kann dein Server nicht selbst erfunden haben: Er hat matrix.org gefragt, die Signatur
geprüft und das Ergebnis weitergereicht. Dasselbe funktioniert mit Raumadressen; der
Verzeichniseintrag verrät zusätzlich, welche Server im Raum sitzen.</p>
<p>Was das kostet, zeigt der Beitritt zu einem echten öffentlichen Raum:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">time</span> curl -s -X POST <span class="s2">&#34;https://DEINE_DOMAIN/_matrix/client/v3/join/%23element-web:matrix.org&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  -H <span class="s2">&#34;Authorization: Bearer DEIN_TOKEN&#34;</span> -H <span class="s2">&#34;Content-Type: application/json&#34;</span> -d <span class="s1">&#39;{}&#39;</span></span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">{&#34;room_id&#34;:&#34;!xUW4PpAe1CmThA3r2wI8IrgwwsK006-zqWdJCljpd10&#34;}
</span></span><span class="line"><span class="cl">real    0m2.221s</span></span></code></pre></div>
</div>
<p>Zwei Sekunden. In den zwei Minuten danach, während Synapse den Raumzustand nachlädt (gut 5.000
Zustandsereignisse), steigt sein Arbeitsspeicher von rund 120 MB auf rund 280 MB, die Datenbank
von 15 auf 63 MB. Für einen einzigen Raum. Das ist der wichtigste Satz über Matrix-Föderation:
Dein Server verwaltet für jeden Raum, in dem du sitzt, dessen gesamten Zustand mit, auch den Teil,
der von fremden Servern kommt. Ein Homeserver mit 1 GB RAM ist deshalb schnell am Ende, sobald
jemand drei große Räume betritt.</p>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-herb-400 bg-herb-50 dark:border-herb-700 dark:bg-herb-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">🧑‍🍳</span>Raum-IDs haben ihr Format geändert
  </p>
  <div class="prose-kitchen text-sm">Die Raum-ID oben hat keinen Doppelpunkt und keinen Servernamen mehr. Ab Raumversion 12 sind
Raum-IDs reine Prüfsummen ohne Herkunftsangabe. Alte IDs der Form
<code>!abcdef:matrix.org</code> bleiben gültig. Skripte, die auf den Doppelpunkt bauen, tun es nicht.</div>
</div>
<h3 id="schritt-9-der-admin-alltag">Schritt 9: Der Admin-Alltag</h3>
<p>Drei Handgriffe brauchst du regelmäßig. <strong>Konten stilllegen.</strong> Ein Konto wird nie gelöscht, sondern
deaktiviert, weil die Benutzer-ID in fremden Räumen weiterlebt:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">curl -s -X POST <span class="s2">&#34;https://DEINE_DOMAIN/_synapse/admin/v1/deactivate/@NAME:DEINE_DOMAIN&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  -H <span class="s2">&#34;Authorization: Bearer DEIN_TOKEN&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  -H <span class="s2">&#34;Content-Type: application/json&#34;</span> -d <span class="s1">&#39;{&#34;erase&#34;: true}&#39;</span></span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">{&#34;id_server_unbind_result&#34;:&#34;success&#34;}</span></span></code></pre></div>
</div>
<p><code>erase: true</code> löscht zusätzlich Anzeigename und Profilbild. Die Benutzer-ID bleibt gesperrt und kann
nicht neu vergeben werden. Das ist Absicht, sonst könnte jemand die Identität übernehmen.</p>
<p><strong>Räume loswerden</strong>, die du nicht mehr brauchst. Erst austreten und vergessen, dann vom Server
räumen:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nv">RAUM</span><span class="o">=</span><span class="s1">&#39;!DEINE_RAUM_ID&#39;</span>
</span></span><span class="line"><span class="cl">curl -s -X DELETE <span class="s2">&#34;https://DEINE_DOMAIN/_synapse/admin/v2/rooms/</span><span class="nv">$RAUM</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  -H <span class="s2">&#34;Authorization: Bearer DEIN_TOKEN&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  -H <span class="s2">&#34;Content-Type: application/json&#34;</span> -d <span class="s1">&#39;{&#34;purge&#34;: true, &#34;block&#34;: false}&#39;</span></span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">{&#34;delete_id&#34;:&#34;qUcHmFcjfVZFfHWv&#34;}</span></span></code></pre></div>
</div>
<p>Der Aufruf kommt sofort zurück, das Aufräumen läuft im Hintergrund. Den Stand fragst du unter
<code>/_synapse/admin/v2/rooms/delete_status/DELETE_ID</code> ab, bis dort <code>&quot;status&quot;:&quot;complete&quot;</code> steht. Mit
<code>&quot;block&quot;: true</code> sperrst du den Raum zusätzlich, sodass niemand ihn erneut betreten kann.</p>
<p><strong>Medien aufräumen.</strong> Jedes Bild aus jedem föderierten Raum landet als Kopie bei dir:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nv">VORHER</span><span class="o">=</span><span class="k">$((</span> <span class="o">(</span><span class="k">$(</span>date +%s<span class="k">)</span> <span class="o">-</span> <span class="m">2592000</span><span class="o">)</span> <span class="o">*</span> <span class="m">1000</span> <span class="k">))</span>
</span></span><span class="line"><span class="cl">curl -s -X POST <span class="s2">&#34;https://DEINE_DOMAIN/_synapse/admin/v1/media/DEINE_DOMAIN/delete?before_ts=</span><span class="nv">$VORHER</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  -H <span class="s2">&#34;Authorization: Bearer DEIN_TOKEN&#34;</span></span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">{&#34;deleted_media&#34;:[],&#34;total&#34;:0}</span></span></code></pre></div>
</div>
<p><code>before_ts</code> ist ein Zeitstempel in <strong>Millisekunden</strong>; die Rechnung oben ergibt „älter als 30 Tage&quot;.
Vergisst man die drei Nullen, löscht der Aufruf nichts, weil der Zeitpunkt 1970 liegt.</p>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-amber-400 bg-amber-50 dark:border-amber-700 dark:bg-amber-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">⚠️</span>Die Admin-API gehört nicht ins offene Netz
  </p>
  <div class="prose-kitchen text-sm">Alle Aufrufe in diesem Abschnitt hängen an einem einzigen Token. Wer ihn hat, kann Konten
deaktivieren und Räume löschen. Sperr <code>/_synapse/admin</code> im Reverse Proxy auf dein VPN oder deine
feste Adresse; das Muster dafür steht in
<a href="/tutorials/interne-dienste-nur-im-vpn/">Dienste nur im VPN erreichbar machen</a>.</div>
</div>
<h2 id="wenn-es-nicht-funktioniert">Wenn es nicht funktioniert</h2>
<div class="troubleshoot not-prose">
<p><strong>Unter <code>/_matrix/client/versions</code> kommt die Element-Startseite statt JSON.</strong> Beide Router hören auf denselben Host, und Traefik hat den falschen zuerst geprüft. Setz <code>traefik.http.routers.synapse.priority=100</code> und <code>…element.priority=10</code>. Ohne ausdrückliche Zahlen sortiert Traefik nach Regellänge, und das Ergebnis ändert sich, sobald eine weitere Regel dazukommt. Nach dem Ändern reicht <code>docker compose up -d</code>; Traefik liest die Labels sofort neu.</p>
<p><strong>Der Element-Container startet nicht und meldet <code>cp: can't open '/app/config.json': Permission denied</code>.</strong> Die <code>element-config.json</code> auf dem Host ist für den Container nicht lesbar. Das passiert zuverlässig auf Servern mit <code>umask 027</code>, weil neue Dateien dann mit <code>0640</code> und ohne Leserecht für andere entstehen. <code>chmod 0644 /opt/matrix/element-config.json</code> und neu starten. Das gilt für jede Konfigurationsdatei, die du per Bind-Mount in einen Container reichst.</p>
<p><strong>Element hängt bei „Confirm your digital identity&quot;, und „Use recovery key&quot; ist ausgegraut.</strong> Du meldest dich auf einem weiteren Gerät an und hast beim ersten keinen Wiederherstellungsschlüssel angelegt (Schritt 5). Ist ein altes Gerät noch angemeldet, nimm „Use another device&quot; und bestätige dort. Sonst bleibt nur „Can&rsquo;t confirm?&quot; mit anschließendem Kontopasswort: Element setzt die Identität dann zurück, Konto, Kontakte und Räume bleiben, aber Nachrichten aus der Zeit davor bleiben unlesbar. Leg danach sofort den Wiederherstellungsschlüssel an.</p>
<p><strong>Die Anmeldung schlägt plötzlich mit <code>M_LIMIT_EXCEEDED</code> fehl.</strong> In der Antwort steht <code>retry_after_ms</code>, die Wartezeit in Millisekunden. Synapse bremst Anmeldeversuche pro Konto und pro IP ab; das trifft dich, wenn du mehrere Sitzungen hintereinander aufbaust oder mit Skripten testest. Für den Dauerbetrieb lässt sich das über den <code>rc_login</code>-Block in der <code>homeserver.yaml</code> lockern, nur solltest du das nicht tun, denn genau diese Bremse hält Rateversuche auf.</p>
<p><strong>In der Geräteliste und in den Protokollen steht <code>172.x.x.x</code> statt der echten Besucher-IP.</strong> Das betrifft nur Anfragen über <strong>IPv6</strong>, bei einer Domain mit AAAA-Eintrag also die Mehrheit. Das <code>proxy</code>-Netz wird im Traefik-Tutorial mit <code>docker network create proxy</code> angelegt, und das ist ein reines IPv4-Netz. Docker kann eine eingehende IPv6-Verbindung dann nicht direkt auf den Container umschreiben und schickt sie über einen Hilfsprozess, der die Absenderadresse durch die des Bridge-Gateways ersetzt; über IPv4 stimmt sie die ganze Zeit. Abhilfe ist ein Netz mit IPv6: in allen Stacks <code>docker compose down</code>, dann <code>docker network rm proxy &amp;&amp; docker network create --ipv6 --subnet fd00:cafe::/64 proxy</code> und alles wieder hochfahren; die Schritte stehen im <a href="/tutorials/reverse-proxy-traefik/">Reverse Proxy mit Traefik</a>. Danach steht bei IPv4 <strong>und</strong> IPv6 die echte Adresse in <code>X-Forwarded-For</code>.</p>

</div>
<p class="mt-3 text-sm text-slate-500 dark:text-slate-400">
  Dein Problem ist nicht dabei?
  <a href="/fehler/" class="font-medium text-paprika-700 hover:underline dark:text-paprika-400">Alle Fehlermeldungen durchsuchen →</a>
</p>

<h2 id="wartung--backups">Wartung &amp; Backups</h2>
<p>Element und Synapse aktualisierst du getrennt. Element Web ist eine Sammlung statischer Dateien
ohne eigenen Zustand; ein Update ist risikoarm und kostet Sekunden:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">cd</span> /opt/matrix
</span></span><span class="line"><span class="cl">sed -i <span class="s1">&#39;s/element-web:v1.12.33/element-web:vNEUE_VERSION/&#39;</span> compose.yaml
</span></span><span class="line"><span class="cl">docker compose pull element <span class="o">&amp;&amp;</span> docker compose up -d element
</span></span><span class="line"><span class="cl">curl -s https://DEINE_DOMAIN/version</span></span></code></pre></div>
</div>
<p>Der letzte Aufruf gibt die laufende Version aus, etwa <code>1.12.33</code>. Element folgt Synapse locker;
ein Versionssprung des Servers macht die Oberfläche nicht kaputt. Umgekehrt gilt das ebenfalls,
solange du nicht mehrere Jahre überspringst.</p>
<p>Was zusätzlich ins Backup muss, über Teil 1 hinaus:</p>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">/opt/matrix/element-config.json   # steckt in keiner Datenbank</span></span></code></pre></div>
</div>
<p>Das ist eine kleine Datei, aber sie enthält jede Entscheidung aus Schritt 2. Geht sie verloren,
startet Element mit der Standardkonfiguration, und deine Leute stehen plötzlich vor einem
Eingabefeld, in das sie irgendeinen Homeserver tippen können.</p>
<p>Registrierungstokens brauchen Hygiene. Ein Token mit <code>uses_allowed: 3</code> ohne Ablaufdatum ist ein
Einladungscode, der jahrelang gültig bleibt. Schau alle paar Monate in die Liste und lösch, was du
nicht mehr brauchst:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">curl -s -X DELETE <span class="s2">&#34;https://DEINE_DOMAIN/_synapse/admin/v1/registration_tokens/DER_TOKEN&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  -H <span class="s2">&#34;Authorization: Bearer DEIN_TOKEN&#34;</span></span></span></code></pre></div>
</div>
<p>Der Speicherbedarf ist die eigentliche Wartungsaufgabe. Der Sprung von rund 120 auf 280 MB
für einen einzigen föderierten Raum skaliert weiter. Setz dir eine Erinnerung, alle paar Monate
<code>docker stats --no-stream</code> und <code>du -sh /opt/matrix/data/media_store</code> anzuschauen, und tritt aus
Räumen aus, in denen niemand von euch mehr schreibt. Das ist wirksamer als jede Feineinstellung.</p>
<p>Realistisch gerechnet macht Element selbst keine Arbeit: zwei, drei Updates im Jahr. Die Zeit
geht in den Homeserver: alle zwei Wochen ein Synapse-Update mit Blick in die Release Notes, einmal
im Quartal Medien und Räume aufräumen. Zusammen etwa eine Stunde im Monat.</p>
]]></content:encoded></item></channel></rss>