Matrix Federation: Налаштування та діагностика¶
Federation дозволяє користувачам з різних Matrix серверів спілкуватися між собою. @karas:eliah.one може писати @cetym:eliah-b.one — хоча вони на різних серверах. Цей мануал описує покрокове налаштування federation між двома Synapse серверами з використанням Caddy як reverse proxy.
Передумови¶
Перед налаштуванням federation переконайтесь що маєте:
- Два працюючих Synapse сервери з різними
server_name - Caddy як reverse proxy на обох серверах
- Публічні DNS записи для обох доменів
- TLS сертифікати (Caddy отримує автоматично через Let's Encrypt)
- Порт 8448 відкритий на файрволі для вхідних з'єднань
Архітектура¶
flowchart LR
subgraph nodeA["Вузол A"]
CA["Caddy :8448<br/>protocols h1"]
SA["Synapse A<br/>server_name: server-a.example"]
end
subgraph nodeB["Вузол B"]
CB["Caddy :8448<br/>protocols h1"]
SB["Synapse B<br/>server_name: server-b.example"]
end
CA <-->|"Federation API<br/>HTTP/1.1 :8448"| CB
SA --> CA
SB --> CB
style nodeA fill:#d1fae5
style nodeB fill:#dbeafeКрок 1: DNS¶
Кожен сервер потребує публічні DNS записи. Зовнішні сервери повинні знати куди стукатися.
server-a.example A 203.0.113.10
matrix.server-a.example A 203.0.113.10
server-b.example A 198.51.100.20
matrix-b.server-b.example A 198.51.100.20
dig +short server-a.example A
dig +short matrix-b.server-b.example A
Пастка: dnsmasq override¶
Якщо на сервері є dnsmasq з override записами (наприклад для WireGuard routing), Synapse у Docker контейнері може резолвити hostname у приватну IP адресу замість публічної.
Чому це проблема: Synapse має вбудований SSRF захист і блокує з'єднання до приватних IP (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16). Якщо DNS резолвить federation target у приватну IP — Synapse відмовить.
Лог виглядає так:
Blocked 10.10.11.1 from DNS resolution to matrix-b.server-b.example
Перевірте:
# Перевірити dnsmasq overrides
cat /etc/dnsmasq.d/*.conf | grep -i "matrix\|server-b"
# Перевірити що Docker контейнер резолвить у публічну IP
docker exec synapse getent hosts matrix-b.server-b.example
# Має бути: 198.51.100.20 (публічна IP)
Рішення — видалити override та перезапустити:
sed -i '/address=\/matrix-b\.server-b\.example/d' /etc/dnsmasq.d/matrix.conf
systemctl restart dnsmasq
docker restart synapse
Крок 2: Well-known¶
Matrix federation починається з discovery. Сервер A хоче знайти сервер B — він звертається до https://server-b.example/.well-known/matrix/server.
Caddy конфігурація¶
server-b.example {
bind 198.51.100.20
header /.well-known/matrix/* Content-Type application/json
respond /.well-known/matrix/server `{"m.server": "matrix-b.server-b.example:8448"}`
respond /.well-known/matrix/client `{"m.homeserver": {"base_url": "https://matrix-b.server-b.example"}}`
}
Перевірка¶
curl -s https://server-b.example/.well-known/matrix/server | python3 -m json.tool
# {"m.server": "matrix-b.server-b.example:8448"}
Крок 3: Caddy — Federation endpoint¶
Federation API працює на порту 8448. Caddy має слухати на цьому порту і проксувати запити до Synapse.
# Federation endpoint — публічний!
matrix-b.server-b.example:8448, server-b.example:8448 {
bind 198.51.100.20
reverse_proxy /_matrix/* localhost:8008
reverse_proxy /_synapse/* localhost:8008
}
Обов'язково bind на публічну IP — federation приходить ззовні.
Критично: вимкнути HTTP/2 на порту 8448¶
Synapse використовує Twisted HTTP клієнт для federation. Twisted має несумісність з HTTP/2 — при h2 з'єднанні він не бачить Content-Type header у відповіді і падає з помилкою.
Додайте у global options Caddyfile:
{
servers 198.51.100.20:8448 {
protocols h1
}
}
curl -s localhost:2019/config/apps/http/servers/ | \
python3 -m json.tool | grep -A3 -iE "listen|protocols"
# Кожен listener на :8448 має мати "protocols": ["h1"]
Перевірка¶
# Має бути HTTP/1.1, НЕ HTTP/2
curl -sv https://server-b.example:8448/_matrix/key/v2/server 2>&1 | grep -iE "alpn|HTTP"
# Очікуємо: ALPN: server accepted http/1.1
# HTTP/1.1 200 OK
Пастка: HTTP/2 та Twisted — детально¶
Симптоми у логах Synapse (на сервері що намагається отримати ключі):
Error reading response GET matrix-federation://server-a.example/_matrix/key/v2/server:
Failed to send request: RuntimeError: No Content-Type header received from remote server
При цьому curl з того самого контейнера працює:
docker exec synapse curl -s https://server-a.example:8448/_matrix/key/v2/server
# JSON ✓ — curl це robust h2 client
А curl --http1.1 теж працює:
docker exec synapse curl -s --http1.1 https://server-a.example:8448/_matrix/key/v2/server
# JSON ✓
Проблема саме у Twisted HTTP клієнті, який не коректно парсить HTTP/2 response headers від Caddy. Після вимкнення h2 на порту 8448 — Twisted отримує нормальний HTTP/1.1 response і все працює.
Крок 4: Synapse homeserver.yaml¶
Базові federation налаштування¶
server_name: "server-b.example"
# Ключ підпису — генерується автоматично при першому запуску
# НІКОЛИ не змінюйте після першого запуску!
signing_key_path: "/data/server-b.example.signing.key"
# Federation whitelist — список серверів з якими дозволена federation
# Якщо відсутній — дозволена federation з усіма серверами
federation_domain_whitelist:
- server-a.example
- matrix.org
# Trusted key servers для верифікації signing keys
trusted_key_servers:
- server_name: "matrix.org"
Пастка: federation_ip_range_whitelist¶
Якщо у homeserver.yaml є federation_ip_range_whitelist або ip_range_whitelist — це обмежить outbound federation ТІЛЬКИ до вказаних IP діапазонів.
# ❌ Це обмежить federation тільки до приватної підмережі
federation_ip_range_whitelist:
- '10.10.11.0/24'
ip_range_whitelist:
- '10.10.11.0/24'
# ✅ Видаліть ці параметри якщо federation target має публічну IP
Симптом ідентичний HTTP/2 проблемі ("No Content-Type header"), бо Synapse обриває з'єднання до забороненої IP ще до отримання response.
Signing Keys¶
Кожен Synapse має унікальний signing key. Коли сервер A надсилає federation запит серверу B, запит підписується ключем A. Сервер B верифікує підпис через endpoint /_matrix/key/v2/server.
curl -s https://server-a.example:8448/_matrix/key/v2/server | python3 -m json.tool
# Має містити:
# "server_name": "server-a.example",
# "verify_keys": {"ed25519:a_xykP": {"key": "..."}}
# "valid_until_ts": 1776964675668
Якщо Synapse B не може отримати ключ A — він спробує через matrix.org як notary:
Requesting keys [...] from notary server matrix.org
Якщо і notary не допоможе — буде 401 Unauthorized.
Крок 5: Фаєрвол¶
Порт 8448/tcp має бути відкритий для вхідних з'єднань.
# UFW
ufw allow 8448/tcp
# iptables
iptables -A INPUT -p tcp --dport 8448 -j ACCEPT
Діагностика¶
Federation Tester¶
https://federationtester.matrix.org/api/report?server_name=server-b.example
Чек-лист з нуля¶
- ☐ DNS записи резолвляться в публічні IP
- ☐ Well-known endpoint повертає JSON з
m.serverна порту 8448 - ☐ Caddy слухає на :8448 з bind на публічну IP
- ☐ HTTP/2 вимкнений на :8448 (global
serversзprotocols h1) - ☐
federation_domain_whitelistмістить потрібні домени - ☐ Немає
federation_ip_range_whitelistщо блокує публічні IP - ☐ Signing key endpoint доступний ззовні через HTTP/1.1
- ☐ dnsmasq не override-ить federation targets у приватні IP
- ☐ Docker контейнер резолвить правильні IP (
getent hosts) - ☐ Порт 8448 відкритий на файрволі
- ☐
curl -svпоказуєALPN: server accepted http/1.1
Типові помилки¶
| Лог | Причина | Рішення |
|---|---|---|
Blocked 10.x.x.x from DNS resolution |
dnsmasq override → приватна IP | Видалити override |
No Content-Type header received |
HTTP/2 на :443 або :8448, або ip_range_whitelist | h1 на :8448 + well-known → :8448 |
401 Unauthorized + Failed to find any key |
Не може отримати signing key | Перевірити /_matrix/key/v2/server |
403 Federation denied with X |
Домен не в whitelist | Додати в federation_domain_whitelist |
Connection refused :8448 |
Порт не слухає | Перевірити Caddy bind + firewall |
DNS lookup failed |
DNS не резолвить | Перевірити A-записи |
Корисні команди¶
# Перевірка з Docker контейнера
docker exec synapse getent hosts matrix-b.server-b.example
docker exec synapse curl -sv https://server-a.example:8448/_matrix/key/v2/server
# Tail federation логів
docker logs -f synapse 2>&1 | \
grep -iE "federation|invite|key|well-known|blocked" | grep -v "prev group"
# Caddy протоколи
curl -s localhost:2019/config/apps/http/servers/ | \
python3 -m json.tool | grep -A3 "listen\|protocols"
Пов'язані теми¶
- Synapse — встановлення та налаштування
- Адміністрування — управління користувачами
- Caddy Reverse Proxy — налаштування Caddy
- Глосарій: Federation — теоретичні основи
Шлях: services/matrix/federation-setup.md