Вт. Окт 13th, 2026

Настройка 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.

Но когда всё починится — ощущение, что прорубил окно в мир, которое никто не закроет.