Настройка Matrix и Jitsi за роутером, у которого занят 443 порт: полный разбор ошибок
Когда я решил поднять свой Matrix-сервер с видеозвонками, казалось, что всё просто: ставь Synapse, вешай Jitsi Meet, открывай порты — и работает. Реальность оказалась сложнее. Под капотом встала цепочка проблем, каждая из которых отваливалась со своей уникальной ошибкой. Рассказываю по порядку — что сломалось, почему и как это чинилось.
С чего всё начиналось
Железо: сервер на Debian внутри локальной сети за роутером Keenetic. Внешний статический IP есть, порты можно пробрасывать. Казалось бы, бери и делай. Но есть нюанс: порт 443 занят самим роутером. На нём висит веб-интерфейс и внутренние сервисы Keenetic, и никак это не изменить.
Значит, все HTTPS-сервисы должны висеть на другом порту. Я выбрал 8444. И это решение потянуло за собой ворох проблем, о которых я даже не подозревал, когда читал мануалы по установке Synapse и Jitsi.
Архитектура, которая в итоге заработала
Схема получилась такой:
- Роутер смотрит в интернет статическим IP и пробрасывает порты внутрь
- На сервере крутится Caddy как reverse-proxy (он же даёт бесплатные Let’s Encrypt сертификаты)
- Synapse (Matrix-сервер) работает на внутреннем порту и торчит наружу через Caddy на порту 8444
- Jitsi Meet (видеозвонки) — отдельный Docker Compose, тоже через Caddy на порту 8444, но по другому домену
- Element Web — веб-клиент Matrix, через Caddy на том же порту 8444
- Coturn — TURN-сервер для медиатрафика (UDP 3478 + релейные порты)
Всё это заворачивается в Caddyfile, который маршрутизирует запросы по доменам и путям.
Верный алгоритм (если хочешь повторить)
Шаг 1. Пробрось порты на роутере
Как минимум понадобятся:
- TCP 80 → на сервер (для редиректа и Well-Known)
- TCP 8444 → на сервер (основной HTTPS)
- UDP 3478 → на сервер (TURN-сервер Coturn)
- UDP 10000 → на сервер (Jitsi Video Bridge)
- UDP 50100-50200 → на сервер (релейные порты TURN)
Порт 443 остаётся роутеру, с этим ничего не сделать.
Шаг 2. Подними Caddy
Caddy сам получает сертификаты, сам продлевает, сам проксирует. Конфиг пишется в Caddyfile. Самое важное правило — Caddyfile должен использовать handle-блоки, а не директивы верхнего уровня. Иначе порядок обработки может преподнести сюрпризы.
example.com:80 {
handle /.well-known/matrix/* {
reverse_proxy 127.0.0.1:8008
}
handle {
redir https://example.com:8444{uri} permanent
}
}
matrix.example.com:8444 {
handle_path /jitsi/* {
reverse_proxy 127.0.0.1:8480 {
transport http {
versions 1.1
}
}
}
handle /xmpp-websocket {
reverse_proxy 127.0.0.1:8480 {
transport http {
versions 1.1
}
}
}
handle {
reverse_proxy 127.0.0.1:8480
}
}
chat.example.com:8444 {
handle /_matrix/* {
reverse_proxy 127.0.0.1:8008
}
handle {
reverse_proxy 127.0.0.1:8081
}
}
Шаг 3. Настрой Synapse
Synapse ставится либо через apt (если Debian/Ubuntu), либо в Docker. Я выбрал нативный вариант — проще с управлением.
В конфиг /etc/matrix-synapse/homeserver.yaml добавляется секция Jitsi:
jitsi:
domain: matrix.example.com
url: https://matrix.example.com:8444/jitsi/
И extra_well_known_client_content — чтобы Element сам понимал, на какой сервер стучаться:
extra_well_known_client_content:
m.homeserver:
base_url: "https://chat.example.com:8444"
im.vector.riot.jitsi:
preferredDomain: "matrix.example.com:8444"
Шаг 4. Разверни Jitsi Meet
Jitsi проще ставить через официальный Docker Compose. Контейнеров четыре: prosody (XMPP-сервер), jicofo (фокус-менеджер), jvb (видеомост) и web (nginx с Jitsi Meet).
Важный флаг в .env:
ENABLE_S2S=1
Без него prosody не слушает s2s-порт, и jicofo с jvb не смогут подключиться.
Шаг 5. Настрой Well-Known для автоматического определения сервера
Это нужно, чтобы Element на телефоне сам понимал, на какой сервер и порт коннектиться. Без этого придётся вручную вбивать chat.example.com:8444 в поле «Домашний сервер».
Well-Known отдаётся через Caddy на 80-м порту до редиректа.
Шаг 6. TURN-сервер
Jitsi использует TURN для медиатрафика, когда прямой P2P не проходит (за симметричными NAT, корпоративными файрволами). Coturn настраивается, порты UDP пробрасываются на роутере.
Ошибки, которые мы допустили
В процессе настройки было пройдено ровно девять граблей. По порядку.
1. Серый экран Jitsi у пользователей из внешней сети
Проблема: Я заходил во внутренней сети — всё работало. Люди из интернета видели только серый экран, Jitsi не загружался.
Почему: Caddy по умолчанию использует HTTP/2 для проксирования. Jitsi nginx внутри контейнера не поддерживает h2c — он просто падает при первой же попытке соединения. В логах это выглядит как обрыв соединения без внятной ошибки.
Решение: Форсировать HTTP/1.1 для всех прокси-путей к Jitsi:
reverse_proxy 127.0.0.1:8480 {
transport http {
versions 1.1
}
}
Каждое правило, ведущее на Jitsi, должно содержать этот блок. Без исключений.
2. Jitsi iframe открывался на порт 443
Проблема: Element строил iframe для Jitsi по адресу https://matrix.example.com/ — то есть на порт 443 (роутер). Там, естественно, ничего не висело.
Почему: В конфиге Element Web preferred_domain был указан как matrix.example.com без порта. Element — молодец, он сам добавляет протокол, и получается https://matrix.example.com/ → порт 443 → роутер.
Решение: Добавить порт в preferred_domain: matrix.example.com:8444.
3. Ошибка 501 на корне домена
Проблема: При заходе на https://matrix.example.com:8444/ Jitsi выдавал 501 Not Implemented.
Почему: Catch-all в Caddyfile вёл на JWT-сервис (порт 8080), а не на Jitsi. JWT-сервис честно писал, что не знает, что делать с корневым запросом.
Решение: Перенаправить catch-all на Jitsi (8480 порт). JWT-сервис оставить по конкретному пути /jwt/*.
4. Rate-limit Synapse не пускал логин
Проблема: При попытке залогиниться вылезала ошибка M_LIMIT_EXCEEDED.
Почему: По умолчанию Synapse включает rate-limit на логины. При тестировании с одного IP это очень быстро выбирает лимит.
Решение: Для тестового пользователя можно отключить rate-limit через admin API:
POST /_synapse/admin/v1/users/testuser/override_ratelimit
{ "messages_per_second": 0, "burst_count": 0 }
5. Prosody не принимал соединения от jicofo и jvb
Проблема: После перезапуска Jitsi jicofo и jvb не могли переподключиться к prosody.
Почему: В логах было: reconnection failed: Connection refused. Prosody ждал подключения по s2s (порт 5269), но не слушал его, потому что в .env не было ENABLE_S2S=1.
Решение: Добавить ENABLE_S2S=1 в .env Jitsi и пересоздать prosody-контейнер. После этого s2s-порт открывается, и все компоненты находят друг друга.
6. MUC-комната не находилась
Проблема: Jicofo не мог создать комнату для конференции, ошибка item-not-found.
Почему: Неправильно был настроен MUC-компонент в prosody. Компонент muc.matrix.example.com отсутствовал или был настроен без параметров.
Решение: Добавить корректный Component в конфиг prosody с muc_filter_whitelist и muc_domain_mapper.
7. Element на телефоне не находил сервер
Проблема: При вводе chat.example.com (без порта) Element Classic на телефоне выдавал ошибку соединения.
Почему: Порт 443 занят роутером, а без Well-Knowm конфига Element не знает, какой порт использовать. Он стучится в 443 по умолчанию — и получает облом.
Решение:
— Добавить m.homeserver.base_url: "https://chat.example.com:8444" в extra_well_known_client_content Synapse
— Настроить Caddy на порту 80 так, чтобы .well-known/matrix/client отдавался до редиректа
После этого Element на телефоне сам подставляет правильный порт.
8. TURN был выключен, и P2P не проходил
Проблема: При звонке не было медиа — видео не доходило.
Почему: Я временно убрал TURN из конфига Jitsi, решив, что relay-порты не проброшены. На самом деле порты были проброшены, просто я не проверил.
Решение: Вернуть TURN-настройки в custom-config.js Jitsi.
9. Редирект с порта 80 не срабатывал для Well-Known
Проблема: Caddy редиректил все запросы с 80 порта на 8444, включая .well-known/matrix/client. В результате Well-Known был недоступен.
Почему: Редирект был написан как директива верхнего уровня, которая перехватывает ВСЕ запросы до того, как до них дойдёт обработчик.
Решение:
example.com:80 {
handle /.well-known/matrix/* {
reverse_proxy 127.0.0.1:8008
}
handle {
redir https://example.com:8444{uri} permanent
}
}
Первый handle перехватывает Well-Known и отдаёт его с Synapse. Второй handle (стреляет, только если первый не подошёл) делает редирект всего остального.
Почему эти ошибки возникают именно за роутером
Если у вас сервер с белым IP и порт 443 свободен — большинства проблем не возникнет. Jitsi достаётся 443-й порт, Element строит правильные ссылки, Caddy не ломает заголовки, s2s работает «из коробки».
Но если 443 занят, вылезают все подводные камни:
- Каждый iframe и WebSocket нужно явно сказать, на каком порту жить. Никакого автоопределения.
- Well-Known на порту 80 — единственный способ объяснить клиенту, куда стучаться. Без него всё приходится настраивать руками на каждом устройстве.
- Caddy по умолчанию использует h2c, который ломает Jitsi nginx. На порту 443 это редкий сценарий, на нестандартном — постоянный.
- Роутер может ресетить длинные WebSocket-соединения. Keenetic, например, любит сбрасывать неактивные соединения по таймауту. Лезет в настройки — увеличить таймаут или отключить инспекцию трафика для этих портов.
Что в итоге
Сейчас всё работает: и с ПК, и с телефона, через внешнюю сеть. Групповые звонки — до 10 человек проверено. Element Classic на всех устройствах.
Главный вывод: если у вас роутер занял 443 порт — готовьтесь к тому, что половина мануалов из интернета не подойдёт. Придётся разбираться, как работают WebSocket, заголовки прокси, Well-Known конфиги и порядок обработки запросов в Caddy.
Но когда всё починится — ощущение, что прорубил окно в мир, которое никто не закроет.
