No description
  • PHP 89.7%
  • HTML 10.3%
Find a file
2026-03-04 09:18:22 +01:00
config Initial Commit 2026-03-03 12:36:59 +01:00
matrix Update Config 2026-03-04 08:10:00 +01:00
nginx Update Config 2026-03-04 08:10:00 +01:00
.env Update Config 2026-03-04 08:10:00 +01:00
docker-compose.yml Update Config 2026-03-04 08:10:00 +01:00
integration-example.html Update Config 2026-03-04 08:10:00 +01:00
README.md Update Readme.md 2026-03-04 09:18:22 +01:00
verify-example.php Update Config 2026-03-04 08:10:00 +01:00

mCaptcha + Matrix + Bridges Self-Hosted Setup

Vollständige Anleitung für eine self-hosted Infrastruktur bestehend aus mCaptcha (Datenschutzfreundliches CAPTCHA), Matrix Synapse (Kommunikationsserver), Element Web (Chat-Client) und Bridges zu Telegram, Discord und WhatsApp alles per Docker Compose auf Ubuntu Server mit Nginx Proxy Manager auf einem separaten Server im selben LAN.


Inhaltsverzeichnis


1. Was ist mCaptcha?

mCaptcha ist ein datenschutzfreundliches, Open-Source CAPTCHA-System auf Basis von Proof-of-Work (PoW). Im Gegensatz zu Google reCAPTCHA oder hCaptcha werden keine persönlichen Daten gesammelt und keine Cookies gesetzt.

Vorteile:

  • 100 % self-hosted keine Abhängigkeit von Drittanbietern
  • Kein Tracking, keine Cookies, DSGVO-konform
  • Proof-of-Work statt Bildrätseln benutzerfreundlich
  • Kostenlos und Open Source (AGPL-3.0)

2. Netzwerkarchitektur

Important

In diesem Setup laufen Nginx Proxy Manager (NPM) und die Applikationsserver auf zwei verschiedenen Hosts im selben LAN. Docker-Netzwerke sind host-lokal und können nicht serverübergreifend genutzt werden. Die Kommunikation zwischen NPM und den Diensten läuft daher über die interne LAN-IP des App-Servers.

┌─────────────────────────────────────────────────────────────┐
│                         Internet                            │
└───────────────────────────┬─────────────────────────────────┘
                            │ :80 / :443
┌───────────────────────────▼─────────────────────────────────┐
│           PROXY-SERVER  (z.B. 192.168.1.10)                 │
│         Nginx Proxy Manager  SSL-Terminierung              │
│         Let's Encrypt  Reverse Proxy                       │
└───────────────────────────┬─────────────────────────────────┘
                            │ internes LAN (192.168.1.0/24)
┌───────────────────────────▼─────────────────────────────────┐
│           APP-SERVER  (z.B. 192.168.1.20)                   │
│                                                             │
│  ┌──────────────────────┐  ┌───────────────────────────┐   │
│  │  mcaptcha_app        │  │  matrix_synapse           │   │
│  │  LAN:7000            │  │  LAN:8008                 │   │
│  ├──────────────────────┤  ├───────────────────────────┤   │
│  │  mcaptcha_db (intern)│  │  matrix_db (intern)       │   │
│  ├──────────────────────┤  ├───────────────────────────┤   │
│  │  mcaptcha_cache      │  │  matrix_element  LAN:8080 │   │
│  │  (intern)            │  │  matrix_register LAN:8090 │   │
│  └──────────────────────┘  └───────────────────────────┘   │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  Bridges  (nur intern  kein Port nach außen!)      │   │
│  │  bridge_telegram · bridge_discord · bridge_whatsapp │   │
│  └─────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘

Wichtige Konsequenzen dieser Architektur:

Aspekt Konsequenz
Kein gemeinsames Docker-Netzwerk Kein proxy external network NPM ist auf anderem Host
Ports binden an LAN-IP ${LAN_IP}:7000:7000 statt 127.0.0.1:7000:7000
NPM trägt LAN-IP ein Forward Host = 192.168.1.20, nicht der Container-Name
Firewall Pflicht Ports nur für die IP des Proxy-Servers freigeben
Bridges: kein Port nach außen Bridges kommunizieren intern per Docker-Netzwerk mit Synapse

3. Voraussetzungen

App-Server (wo Docker läuft):

  • Ubuntu Server mit Docker & Docker Compose v2.x
  • Bekannte, feste LAN-IP (Beispiel hier: 192.168.1.20)
  • ufw für die Firewall

Proxy-Server (wo NPM läuft):

  • Nginx Proxy Manager bereits installiert
  • Bekannte LAN-IP (Beispiel hier: 192.168.1.10)
  • Ports 80 und 443 aus dem Internet erreichbar

DNS:

  • Alle Domains zeigen per A-Record auf die öffentliche IP des Proxy-Servers

4. Verzeichnisstruktur

mcaptcha/
├── docker-compose.yml
├── .env                                   # Secrets  NIEMALS committen!
├── config/
│   └── config.toml
└── matrix/
    ├── docker-compose-matrix.yml
    ├── homeserver.yaml
    ├── log.config
    ├── element-config.json
    ├── registration/
    │   └── index.php
    └── bridges/
        ├── docker-compose-bridges.yml
        ├── telegram/
        │   ├── config.yaml
        │   └── registration.yaml          # (auto-generiert)
        ├── discord/
        │   ├── config.yaml
        │   └── registration.yaml          # (auto-generiert)
        └── whatsapp/
            ├── config.yaml
            └── registration.yaml          # (auto-generiert)
mkdir -p /opt/docker/mcaptcha/{config,matrix/registration,matrix/bridges/{telegram,discord,whatsapp}}
cd /opt/docker/mcaptcha

5. mCaptcha Konfigurationsdateien

5.1 docker-compose.yml

Note

Die Ports binden an ${LAN_IP} die LAN-IP des App-Servers. Es gibt kein proxy Docker-Netzwerk, da NPM auf einem anderen Host läuft und Docker-Netzwerke nicht serverübergreifend funktionieren.

version: "3.8"

services:
  db:
    image: postgres:15-alpine
    container_name: mcaptcha_db
    restart: unless-stopped
    environment:
      POSTGRES_USER: ${MCAPTCHA_DB_USER}
      POSTGRES_PASSWORD: ${MCAPTCHA_DB_PASSWORD}
      POSTGRES_DB: ${MCAPTCHA_DB_NAME}
    volumes:
      - mcaptcha_db_data:/var/lib/postgresql/data
    networks:
      - mcaptcha_internal
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${MCAPTCHA_DB_USER}"]
      interval: 10s
      timeout: 5s
      retries: 5

  cache:
    image: redis:7-alpine
    container_name: mcaptcha_cache
    restart: unless-stopped
    command: redis-server --requirepass ${REDIS_PASSWORD}
    volumes:
      - mcaptcha_redis_data:/data
    networks:
      - mcaptcha_internal

  mcaptcha:
    image: mcaptcha/mcaptcha:latest
    container_name: mcaptcha_app
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
      cache:
        condition: service_healthy
    environment:
      MCAPTCHA_DATABASE_URL: postgres://${MCAPTCHA_DB_USER}:${MCAPTCHA_DB_PASSWORD}@db:5432/${MCAPTCHA_DB_NAME}
      MCAPTCHA_REDIS_URL: redis://:${REDIS_PASSWORD}@cache:6379
      MCAPTCHA_SERVER_IP: 0.0.0.0
      MCAPTCHA_SERVER_PORT: 7000
      MCAPTCHA_APP_SECRET_KEY: ${APP_SECRET_KEY}
      MCAPTCHA_SERVER_DOMAIN: ${MCAPTCHA_DOMAIN}
      MCAPTCHA_ALLOW_REGISTRATION: ${ALLOW_REGISTRATION:-true}
    volumes:
      - ./config/config.toml:/etc/mcaptcha/config.toml:ro
    ports:
      # Nur für den NPM-Server erreichbar  Firewall absichern! (Kapitel 6)
      - "${LAN_IP}:7000:7000"
    networks:
      - mcaptcha_internal

networks:
  mcaptcha_internal:
    driver: bridge
    internal: true

volumes:
  mcaptcha_db_data:
  mcaptcha_redis_data:

5.2 .env

Warning

Diese Datei niemals committen in .gitignore eintragen!

# ── Netzwerk ─────────────────────────────────────────────────
# LAN-IP dieses App-Servers (nicht die öffentliche IP!)
LAN_IP=192.168.1.20

# ── mCaptcha ─────────────────────────────────────────────────
MCAPTCHA_DB_USER=mcaptcha
MCAPTCHA_DB_PASSWORD=SICHERES_DB_PASSWORT_HIER
MCAPTCHA_DB_NAME=mcaptcha

REDIS_PASSWORD=SICHERES_REDIS_PASSWORT_HIER

# Generieren mit: openssl rand -base64 64
APP_SECRET_KEY=DEIN_GEHEIMER_APP_SCHLUESSEL_MINDESTENS_64_ZEICHEN

# Nur der Hostname, ohne https://
MCAPTCHA_DOMAIN=captcha.deine-domain.de

# Nach erster Registrierung auf "false" setzen!
ALLOW_REGISTRATION=true

5.3 config/config.toml

[server]
ip = "0.0.0.0"
port = 7000

[database]
pool_size = 5

[captcha]
salt = 32

[logging]
level = "info"

[settings]
source_code = "https://github.com/mCaptcha/mCaptcha"
allow_demo = false

6. Firewall einrichten

Important

Da die Ports an der LAN-IP hängen, muss die Firewall auf dem App-Server sicherstellen, dass ausschließlich der Proxy-Server (192.168.1.10) auf die App-Ports zugreifen darf. Alle anderen Verbindungen werden geblockt.

# Auf dem App-Server (192.168.1.20) ausführen:

# SSH zuerst dauerhaft erlauben!
sudo ufw allow ssh

# ── mCaptcha ─────────────────────────────────────────────────
sudo ufw allow from 192.168.1.10 to any port 7000 comment "mCaptcha  NPM"

# ── Matrix Synapse ────────────────────────────────────────────
sudo ufw allow from 192.168.1.10 to any port 8008 comment "Synapse  NPM"

# ── Element Web ───────────────────────────────────────────────
sudo ufw allow from 192.168.1.10 to any port 8080 comment "Element  NPM"

# ── Registrierungsseite ───────────────────────────────────────
sudo ufw allow from 192.168.1.10 to any port 8090 comment "Matrix Registrierung  NPM"

# Firewall aktivieren
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw enable

# Prüfen
sudo ufw status verbose

Note

Die Bridges (Telegram, Discord, WhatsApp) bekommen keinen Port nach außen. Sie kommunizieren ausschließlich intern über das Docker-Netzwerk matrix_internal mit Synapse keine Firewall-Regel nötig.


7. Nginx Proxy Manager einrichten

NPM läuft auf dem Proxy-Server (192.168.1.10) und leitet Anfragen an die LAN-IP des App-Servers weiter.

Important

Im Feld Forward Hostname / IP immer die LAN-IP des App-Servers (192.168.1.20) eintragen niemals den Docker-Container-Namen, da NPM auf einem anderen Host läuft und diese Namen nicht auflösen kann.

Proxy Hosts Übersicht

Domain Forward Host Port Websockets SSL
captcha.deine-domain.de 192.168.1.20 7000 Let's Encrypt
matrix.deine-domain.de 192.168.1.20 8008 Let's Encrypt
element.deine-domain.de 192.168.1.20 8080 Let's Encrypt
register.deine-domain.de 192.168.1.20 8090 Let's Encrypt

Custom Config mCaptcha

Im Advanced Tab des Proxy Hosts für captcha.deine-domain.de:

add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;

proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 60s;

Custom Config Matrix Synapse

proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 86400s;
client_max_body_size 50M;

add_header Access-Control-Allow-Origin "*" always;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
add_header Access-Control-Allow-Headers "Origin, X-Requested-With, Content-Type, Accept, Authorization" always;

if ($request_method = OPTIONS) {
    return 204;
}

Well-Known Delegation

Für kurze Matrix-Adressen (@nutzer:deine-domain.de) im Proxy Host von deine-domain.de unter Advanced eintragen:

location /.well-known/matrix/client {
    return 200 '{"m.homeserver":{"base_url":"https://matrix.deine-domain.de"}}';
    add_header Content-Type application/json;
    add_header Access-Control-Allow-Origin *;
}
location /.well-known/matrix/server {
    return 200 '{"m.server":"matrix.deine-domain.de:443"}';
    add_header Content-Type application/json;
    add_header Access-Control-Allow-Origin *;
}

8. mCaptcha Installation & Start

# Secret Key generieren und als APP_SECRET_KEY in .env eintragen
openssl rand -base64 64

# LAN_IP in .env auf die tatsächliche LAN-IP setzen

cd /opt/docker/mcaptcha
docker compose up -d
docker compose logs -f mcaptcha

Admin-Account erstellen:

  1. https://captcha.deine-domain.de aufrufen
  2. Register klicken und Account anlegen
  3. In .env setzen: ALLOW_REGISTRATION=false
  4. Neu starten: docker compose up -d --force-recreate mcaptcha

9. Sitekey erstellen

  1. Nach dem Login auf Add Site klicken
  2. Name und Domain der Website eintragen
  3. Schwierigkeitsstufen konfigurieren:
    • Level 1 Threshold: 0, Difficulty: 1000 (Normalbetrieb)
    • Level 2 Threshold: 100, Difficulty: 50000 (unter Last)
  4. Sitekey kopieren wird für das Widget und die Matrix-Registrierungsseite benötigt

10. Integration in die eigene Website

Frontend (HTML)

<!-- Im <head> -->
<script src="https://captcha.deine-domain.de/api/v1/pow/config.js"></script>

<!-- Im Formular -->
<div
  class="mcaptcha__widget-container"
  data-sitekey="DEIN_SITEKEY"
  data-mcaptcha-url="https://captcha.deine-domain.de"
></div>

<!-- Vor </body> -->
<script
  src="https://captcha.deine-domain.de/static/widget/bundle/bundle.js"
  data-sitekey="DEIN_SITEKEY"
  data-mcaptcha-url="https://captcha.deine-domain.de"
></script>

Server-seitige Verifikation (PHP)

Important

Der Token muss serverseitig verifiziert werden. Clientseitige Prüfung allein ist unsicher.

function verifiziereMcaptcha(string $token, string $sitekey): bool
{
    $ch = curl_init('https://captcha.deine-domain.de/api/v1/pow/siteverify');
    curl_setopt_array($ch, [
        CURLOPT_POST           => true,
        CURLOPT_POSTFIELDS     => json_encode(['token' => $token, 'key' => $sitekey]),
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER     => ['Content-Type: application/json'],
    ]);
    $response = curl_exec($ch);
    curl_close($ch);
    return (json_decode($response, true)['valid'] ?? false) === true;
}

$token = $_POST['mcaptcha__token'] ?? '';
if (!verifiziereMcaptcha($token, 'DEIN_SITEKEY')) {
    die('CAPTCHA-Verifikation fehlgeschlagen.');
}

11. mCaptcha Wartung & Betrieb

Befehl Beschreibung
docker compose up -d Container starten
docker compose down Container stoppen
docker compose logs -f Live-Logs
docker compose pull && docker compose up -d Update
docker image prune -f Alte Images entfernen

Backup:

docker exec mcaptcha_db pg_dump -U mcaptcha mcaptcha > backup_$(date +%Y%m%d).sql

12. mCaptcha Fehlerbehebung

502 Bad Gateway im NPM

# Vom Proxy-Server (192.168.1.10) testen:
curl -v http://192.168.1.20:7000

# Firewall auf dem App-Server prüfen:
sudo ufw status verbose | grep 7000

# Lauscht der Port auf der richtigen IP?
ss -tlnp | grep 7000
# Erwartet: 192.168.1.20:7000

Widget lädt nicht / CORS-Fehler

  • MCAPTCHA_DOMAIN muss exakt mit der aufgerufenen Domain übereinstimmen
  • Websockets Support im NPM Proxy Host aktiviert?

Token-Verifikation schlägt fehl

  • Content-Type: application/json im Request gesetzt?
  • Endpunkt prüfen: /api/v1/pow/siteverify

13. Matrix Synapse Integration mit mCaptcha

Da Synapse keine native mCaptcha-Unterstützung bietet, läuft die Registrierung über eine eigene PHP-Seite, die nach erfolgreicher CAPTCHA-Prüfung den Nutzer per HMAC-signiertem Shared Secret bei Synapse registriert.

Container-Übersicht:

Container Port (LAN) Zweck
matrix_synapse 192.168.1.20:8008 Homeserver API + Federation
matrix_db intern PostgreSQL für Matrix
matrix_element 192.168.1.20:8080 Element Web Client
matrix_registration 192.168.1.20:8090 Registrierungsseite mit mCaptcha

Warning

enable_registration: false muss in homeserver.yaml gesetzt sein. Die PHP-Seite ist der einzige Registrierungsweg.

13.1 matrix/docker-compose-matrix.yml

version: "3.8"

services:
  synapse:
    image: matrixdotorg/synapse:latest
    container_name: matrix_synapse
    restart: unless-stopped
    depends_on:
      matrix_db:
        condition: service_healthy
    environment:
      SYNAPSE_CONFIG_PATH: /data/homeserver.yaml
    volumes:
      - synapse_data:/data
      - ./matrix/homeserver.yaml:/data/homeserver.yaml:ro
      - ./matrix/log.config:/data/log.config:ro
    ports:
      - "${LAN_IP}:8008:8008"
    networks:
      - matrix_internal

  matrix_db:
    image: postgres:15-alpine
    container_name: matrix_db
    restart: unless-stopped
    environment:
      POSTGRES_USER: ${MATRIX_DB_USER}
      POSTGRES_PASSWORD: ${MATRIX_DB_PASSWORD}
      POSTGRES_DB: ${MATRIX_DB_NAME}
      POSTGRES_INITDB_ARGS: "--encoding=UTF8 --lc-collate=C --lc-ctype=C"
    volumes:
      - matrix_db_data:/var/lib/postgresql/data
    networks:
      - matrix_internal
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${MATRIX_DB_USER}"]
      interval: 10s
      timeout: 5s
      retries: 5

  element_web:
    image: vectorim/element-web:latest
    container_name: matrix_element
    restart: unless-stopped
    volumes:
      - ./matrix/element-config.json:/app/config.json:ro
    ports:
      - "${LAN_IP}:8080:80"

  matrix_registration:
    image: php:8.2-apache
    container_name: matrix_registration
    restart: unless-stopped
    volumes:
      - ./matrix/registration:/var/www/html:ro
    ports:
      - "${LAN_IP}:8090:80"
    networks:
      - matrix_internal   # Muss Synapse intern erreichen können
    environment:
      SYNAPSE_URL: http://matrix_synapse:8008
      MATRIX_SERVER_NAME: ${MATRIX_DOMAIN}
      MATRIX_REGISTRATION_SECRET: ${MATRIX_REGISTRATION_SECRET}
      MCAPTCHA_URL: https://${MCAPTCHA_DOMAIN}
      MCAPTCHA_SITEKEY: ${MCAPTCHA_SITEKEY}

networks:
  matrix_internal:
    driver: bridge
    internal: true

volumes:
  synapse_data:
  matrix_db_data:

13.2 .env Matrix-Variablen ergänzen

# Matrix Synapse
MATRIX_DB_USER=synapse
MATRIX_DB_PASSWORD=SICHERES_MATRIX_DB_PASSWORT_HIER
MATRIX_DB_NAME=synapse

MATRIX_DOMAIN=deine-domain.de

# python3 -c "import secrets; print(secrets.token_hex(64))"
MATRIX_REGISTRATION_SECRET=DEIN_REGISTRATION_SHARED_SECRET_HIER

# Sitekey aus dem mCaptcha Dashboard
MCAPTCHA_SITEKEY=DEIN_SITEKEY_AUS_DEM_MCAPTCHA_DASHBOARD

13.3 matrix/homeserver.yaml

Caution

server_name darf nach dem ersten Start niemals geändert werden!

server_name: "deine-domain.de"

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: MATRIX_DB_PASSWORT_HIER   # = MATRIX_DB_PASSWORD aus .env
    database: synapse
    host: matrix_db
    port: 5432
    cp_min: 5
    cp_max: 10

media_store_path: /data/media_store
log_config: "/data/log.config"
signing_key_path: "/data/deine-domain.de.signing.key"

trusted_key_servers:
  - server_name: "matrix.org"

enable_registration: false
registration_shared_secret: "DEIN_REGISTRATION_SHARED_SECRET_HIER"
allow_guest_access: false
max_upload_size: 50M

14. Matrix Installation & Erststart

# Secrets generieren
openssl rand -base64 32                                         # → MATRIX_DB_PASSWORD
python3 -c "import secrets; print(secrets.token_hex(64))"      # → MATRIX_REGISTRATION_SECRET

# Signing Key + initiale Config generieren (einmalig!)
docker run --rm \
  -v $(pwd)/matrix:/data \
  -e SYNAPSE_SERVER_NAME=deine-domain.de \
  -e SYNAPSE_REPORT_STATS=no \
  matrixdotorg/synapse:latest generate

# homeserver.yaml anpassen (DB-Passwort + Registration Secret eintragen)

# Firewall-Regeln aktivieren (Kapitel 6)

# Matrix-Container starten
docker compose -f docker-compose.yml \
               -f matrix/docker-compose-matrix.yml up -d

# Erreichbarkeit vom Proxy-Server testen:
# curl -s http://192.168.1.20:8008/_matrix/client/versions

Admin-Account erstellen:

docker exec -it matrix_synapse register_new_matrix_user \
  -c /data/homeserver.yaml \
  -u admin -p 'SICHERES_PASSWORT' \
  --admin http://localhost:8008

15. Matrix Wartung

Befehl Beschreibung
docker compose logs -f synapse Live-Logs
docker exec -it matrix_synapse bash Shell
docker exec matrix_db psql -U synapse DB-Shell

Backup:

docker exec matrix_db pg_dump -U synapse synapse > matrix_backup_$(date +%Y%m%d).sql

docker run --rm -v synapse_data:/data -v $(pwd):/backup alpine \
  tar czf /backup/synapse_media_$(date +%Y%m%d).tar.gz /data/media_store

16. Bridges Telegram, Discord & WhatsApp

Bridges verbinden Matrix mit anderen Plattformen. Alle drei Bridges laufen rein intern im Docker-Netzwerk matrix_internal sie haben keinen Port nach außen und sind damit vom Internet vollständig isoliert.

Bridge Image Interner Port Extern erreichbar
Telegram dock.mau.dev/mautrix/telegram 29317 ✗ Nein
Discord dock.mau.dev/mautrix/discord 29334 ✗ Nein
WhatsApp dock.mau.dev/mautrix/whatsapp 29318 ✗ Nein

16.1 matrix/bridges/docker-compose-bridges.yml

version: "3.8"

services:
  bridge_telegram:
    image: dock.mau.dev/mautrix/telegram:latest
    container_name: matrix_bridge_telegram
    restart: unless-stopped
    depends_on:
      bridge_telegram_db:
        condition: service_healthy
    volumes:
      - ./matrix/bridges/telegram:/data
    networks:
      - matrix_internal   # Erreicht Synapse intern per Container-Name
      - bridge_internal   # Isoliertes Netz für DB-Zugriff
    # Kein ports:  Bridge ist nicht von außen erreichbar!

  bridge_telegram_db:
    image: postgres:15-alpine
    container_name: matrix_bridge_telegram_db
    restart: unless-stopped
    environment:
      POSTGRES_USER: ${TELEGRAM_BRIDGE_DB_USER}
      POSTGRES_PASSWORD: ${TELEGRAM_BRIDGE_DB_PASSWORD}
      POSTGRES_DB: ${TELEGRAM_BRIDGE_DB_NAME}
    volumes:
      - telegram_bridge_db:/var/lib/postgresql/data
    networks:
      - bridge_internal
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${TELEGRAM_BRIDGE_DB_USER}"]
      interval: 10s
      timeout: 5s
      retries: 5

  bridge_discord:
    image: dock.mau.dev/mautrix/discord:latest
    container_name: matrix_bridge_discord
    restart: unless-stopped
    depends_on:
      bridge_discord_db:
        condition: service_healthy
    volumes:
      - ./matrix/bridges/discord:/data
    networks:
      - matrix_internal
      - bridge_internal

  bridge_discord_db:
    image: postgres:15-alpine
    container_name: matrix_bridge_discord_db
    restart: unless-stopped
    environment:
      POSTGRES_USER: ${DISCORD_BRIDGE_DB_USER}
      POSTGRES_PASSWORD: ${DISCORD_BRIDGE_DB_PASSWORD}
      POSTGRES_DB: ${DISCORD_BRIDGE_DB_NAME}
    volumes:
      - discord_bridge_db:/var/lib/postgresql/data
    networks:
      - bridge_internal
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${DISCORD_BRIDGE_DB_USER}"]
      interval: 10s
      timeout: 5s
      retries: 5

  bridge_whatsapp:
    image: dock.mau.dev/mautrix/whatsapp:latest
    container_name: matrix_bridge_whatsapp
    restart: unless-stopped
    depends_on:
      bridge_whatsapp_db:
        condition: service_healthy
    volumes:
      - ./matrix/bridges/whatsapp:/data
    networks:
      - matrix_internal
      - bridge_internal

  bridge_whatsapp_db:
    image: postgres:15-alpine
    container_name: matrix_bridge_whatsapp_db
    restart: unless-stopped
    environment:
      POSTGRES_USER: ${WHATSAPP_BRIDGE_DB_USER}
      POSTGRES_PASSWORD: ${WHATSAPP_BRIDGE_DB_PASSWORD}
      POSTGRES_DB: ${WHATSAPP_BRIDGE_DB_NAME}
    volumes:
      - whatsapp_bridge_db:/var/lib/postgresql/data
    networks:
      - bridge_internal
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${WHATSAPP_BRIDGE_DB_USER}"]
      interval: 10s
      timeout: 5s
      retries: 5

networks:
  matrix_internal:
    external: true
    name: mcaptcha_matrix_internal  # Von Matrix-Compose erstellt
  bridge_internal:
    driver: bridge
    internal: true

volumes:
  telegram_bridge_db:
  discord_bridge_db:
  whatsapp_bridge_db:

16.2 .env Bridge-Passwörter ergänzen

TELEGRAM_BRIDGE_DB_USER=tg_bridge
TELEGRAM_BRIDGE_DB_PASSWORD=SICHERES_TELEGRAM_DB_PASSWORT
TELEGRAM_BRIDGE_DB_NAME=tg_bridge

DISCORD_BRIDGE_DB_USER=dc_bridge
DISCORD_BRIDGE_DB_PASSWORD=SICHERES_DISCORD_DB_PASSWORT
DISCORD_BRIDGE_DB_NAME=dc_bridge

WHATSAPP_BRIDGE_DB_USER=wa_bridge
WHATSAPP_BRIDGE_DB_PASSWORD=SICHERES_WHATSAPP_DB_PASSWORT
WHATSAPP_BRIDGE_DB_NAME=wa_bridge

16.3 Allgemeiner Initialisierungsablauf

Note

Jede Bridge muss vor dem ersten Start ihre registration.yaml generieren. Diese Datei verbindet Bridge und Synapse über AS-Token und HS-Token ohne sie ignoriert Synapse alle Bridge-Nachrichten.

# 1. config.yaml anpassen (Domain, DB-Passwort, Credentials)

# 2. Registration generieren  Beispiel Telegram:
docker run --rm \
  -v /opt/docker/mcaptcha/matrix/bridges/telegram:/data \
  dock.mau.dev/mautrix/telegram:latest

# 3. registration.yaml in den Synapse-Datenordner kopieren
mkdir -p matrix/synapse_data/bridges
cp matrix/bridges/telegram/registration.yaml \
   matrix/synapse_data/bridges/telegram-registration.yaml
# (ebenso für discord und whatsapp)

# 4. homeserver.yaml ergänzen (s. 16.4)

# 5. Synapse neu starten
docker compose -f matrix/docker-compose-matrix.yml restart synapse

# 6. Alle Bridges starten
docker compose \
  -f docker-compose.yml \
  -f matrix/docker-compose-matrix.yml \
  -f matrix/bridges/docker-compose-bridges.yml \
  up -d

16.4 homeserver.yaml App Services eintragen

Am Ende der homeserver.yaml ergänzen:

app_service_config_files:
  - /data/bridges/telegram-registration.yaml
  - /data/bridges/discord-registration.yaml
  - /data/bridges/whatsapp-registration.yaml

Warning

Synapse muss nach jeder Änderung an app_service_config_files neu gestartet werden.


17. Telegram Bridge

17.1 API-Credentials besorgen

  1. https://my.telegram.org/apps öffnen
  2. API development tools → Neue Anwendung erstellen
  3. api_id und api_hash notieren

17.2 matrix/bridges/telegram/config.yaml

homeserver:
  address: http://matrix_synapse:8008   # Container-Name funktioniert (gleicher Host!)
  domain: deine-domain.de

appservice:
  address: http://bridge_telegram:29317
  database: postgres://tg_bridge:PASSWORT@bridge_telegram_db:5432/tg_bridge

bridge:
  permissions:
    "*": relay
    "deine-domain.de": user
    "@admin:deine-domain.de": admin

telegram:
  api_id: 12345678
  api_hash: abcdef0123456789
  bot_token: disabled

17.3 Starten & Account verknüpfen

docker run --rm \
  -v /opt/docker/mcaptcha/matrix/bridges/telegram:/data \
  dock.mau.dev/mautrix/telegram:latest

cp matrix/bridges/telegram/registration.yaml \
   matrix/synapse_data/bridges/telegram-registration.yaml
docker compose -f matrix/docker-compose-matrix.yml restart synapse
docker compose -f matrix/bridges/docker-compose-bridges.yml \
  up -d bridge_telegram bridge_telegram_db

DM an @telegrambot:deine-domain.de in Element:

login    → Login starten
sync     → Chats synchronisieren
help     → Alle Befehle

18. Discord Bridge

18.1 matrix/bridges/discord/config.yaml

homeserver:
  address: http://matrix_synapse:8008
  domain: deine-domain.de
  public_address: https://matrix.deine-domain.de

appservice:
  address: http://bridge_discord:29334
  database:
    type: postgres
    uri: postgres://dc_bridge:PASSWORT@bridge_discord_db:5432/dc_bridge

bridge:
  permissions:
    "*": relay
    "deine-domain.de": user
    "@admin:deine-domain.de": admin
  bridge_reactions: true
  bridge_replies: true
  encryption:
    allow: true
    default: false

18.2 Starten & Account verknüpfen

docker run --rm \
  -v /opt/docker/mcaptcha/matrix/bridges/discord:/data \
  dock.mau.dev/mautrix/discord:latest

cp matrix/bridges/discord/registration.yaml \
   matrix/synapse_data/bridges/discord-registration.yaml
docker compose -f matrix/docker-compose-matrix.yml restart synapse
docker compose -f matrix/bridges/docker-compose-bridges.yml \
  up -d bridge_discord bridge_discord_db

DM an @discordbot:deine-domain.de:

login-token          → Login mit Discord-Token
guilds status        → Verbundene Server anzeigen
guild bridge <ID>    → Server bridgen

Discord-Token extrahieren:

  1. Discord im Browser öffnen (nicht Desktop-App)
  2. F12 → Netzwerk-Tab
  3. Beliebige Aktion ausführen
  4. In Request-Headern nach Authorization suchen

Warning

User-Token-Login verstößt formal gegen Discords ToS. Für produktive Umgebungen einen eigenen Discord-Bot mit Bot-Token verwenden.


19. WhatsApp Bridge

Nutzt WhatsApps offizielle Multi-Device API kein Root nötig, Handy muss nicht dauerhaft online sein.

19.1 matrix/bridges/whatsapp/config.yaml

homeserver:
  address: http://matrix_synapse:8008
  domain: deine-domain.de
  public_address: https://matrix.deine-domain.de

appservice:
  address: http://bridge_whatsapp:29318
  database:
    type: postgres
    uri: postgres://wa_bridge:PASSWORT@bridge_whatsapp_db:5432/wa_bridge

bridge:
  permissions:
    "*": relay
    "deine-domain.de": user
    "@admin:deine-domain.de": admin
  encryption:
    allow: true
    default: true    # E2E empfohlen!
  history_sync:
    backfill: true
    max_initial_conversations: 20
    create_portals: true

19.2 Starten & Account verknüpfen

docker run --rm \
  -v /opt/docker/mcaptcha/matrix/bridges/whatsapp:/data \
  dock.mau.dev/mautrix/whatsapp:latest

cp matrix/bridges/whatsapp/registration.yaml \
   matrix/synapse_data/bridges/whatsapp-registration.yaml
docker compose -f matrix/docker-compose-matrix.yml restart synapse
docker compose -f matrix/bridges/docker-compose-bridges.yml \
  up -d bridge_whatsapp bridge_whatsapp_db

DM an @whatsappbot:deine-domain.de:

login              → QR-Code erhalten
                     WhatsApp → Einstellungen → Verknüpfte Geräte → QR scannen

sync               → Chats synchronisieren
pm +49123456789    → Neuen Chat öffnen
help               → Alle Befehle

20. Bridges Wartung & Fehlerbehebung

Alle Container auf einmal starten

cd /opt/docker/mcaptcha

docker compose \
  -f docker-compose.yml \
  -f matrix/docker-compose-matrix.yml \
  -f matrix/bridges/docker-compose-bridges.yml \
  up -d

Updates

docker compose -f matrix/bridges/docker-compose-bridges.yml pull
docker compose -f matrix/bridges/docker-compose-bridges.yml up -d

Logs

Befehl Beschreibung
docker compose logs -f bridge_telegram Telegram Bridge
docker compose logs -f bridge_discord Discord Bridge
docker compose logs -f bridge_whatsapp WhatsApp Bridge
docker stats Ressourcenverbrauch aller Container

Fehlerbehebung

502 Bad Gateway im NPM

# Vom Proxy-Server (192.168.1.10) testen:
curl -v http://192.168.1.20:8008/_matrix/client/versions

# Firewall auf dem App-Server prüfen:
sudo ufw status verbose
ss -tlnp | grep -E "7000|8008|8080|8090"

Bridge antwortet nicht in Element

  • registration.yaml korrekt nach synapse_data/bridges/ kopiert?
  • Synapse danach neu gestartet?
  • Interne Konnektivität testen:
docker exec matrix_bridge_telegram \
  curl -s http://matrix_synapse:8008/_matrix/client/versions

Telegram: 401 Unauthorized

  • api_id / api_hash korrekt? → Im Bridge-Chat login erneut

Discord: Bridge trennt Verbindung

  • Token nach Passwortwechsel zurückgesetzt → Token neu extrahieren, login-token erneut

WhatsApp: QR abgelaufen oder Session verloren

  • QR-Codes sind nur 60 Sekunden gültig → reconnect im Element-Chat
  • Session komplett verloren: WhatsApp → Verknüpfte Geräte → Gerät entfernen → login

Medien werden nicht übertragen

  • max_upload_size: 50M in homeserver.yaml gesetzt?
  • NPM Advanced Tab: client_max_body_size 100M;
  • Speicherplatz prüfen: df -h

.gitignore

# Secrets niemals committen!
.env

# Synapse-Laufzeitdaten
matrix/synapse_data/

# Auto-generierte Bridge-Registrierungen (enthalten Tokens!)
matrix/bridges/telegram/registration.yaml
matrix/bridges/discord/registration.yaml
matrix/bridges/whatsapp/registration.yaml

# Signing Keys
*.signing.key

Ressourcen

Projekt Dokumentation
mCaptcha https://mcaptcha.org/docs
Matrix Synapse https://matrix-org.github.io/synapse/latest/
Element Web https://github.com/element-hq/element-web
mautrix Bridges https://docs.mau.fi/bridges/
mautrix-telegram https://docs.mau.fi/bridges/python/telegram/
mautrix-discord https://docs.mau.fi/bridges/go/discord/
mautrix-whatsapp https://docs.mau.fi/bridges/go/whatsapp/
Nginx Proxy Manager https://nginxproxymanager.com/guide/