MTProto, VLESS+Reality и панель управления — на одном порту 443, с маскировкой под собственный работающий сайт.
Архитектура · Установка · Диагностика · Благодарности · English
Есть известный фикс для MTProto-прокси: SYN rate limit на nftables. Он лечит бесконечные переподключения клиентов Telegram, которые начались, когда провайдерская фильтрация стала реагировать на паттерн реконнектов. Пока прокси на сервере один — фикс ставится в одну строку и работает.
Сложность начинается, когда на тот же сервер и тот же порт 443 хочется повесить
ещё и VPN за nginx с SNI-роутингом. Фикс оказывается некуда поставить.
Вариант А — лимит на :443, перед nginx. В момент прихода SYN никакого SNI ещё
не существует: TLS даже не начался. Ядро не может отличить MTProto-соединение от
VPN-соединения и режет всё подряд. VPN с его совсем другим профилем трафика попадает
под лимит, рассчитанный на MTProto, и отваливается.
Вариант Б — лимит на локальный порт, за nginx. Не работает по другой причине.
nginx терминирует TCP-соединение клиента и открывает к бэкенду своё, новое.
Исходный SYN до локального порта не доезжает вовсе, а источником всех соединений
становится 127.0.0.1. Правило «не больше N с одного адреса» теряет смысл: с точки
зрения ядра все клиенты слились в один localhost.
В ядре, до nginx, делать не лимит, а классификацию.
redirect в nftables — это DNAT, а не проксирование. Пакету переписывается порт
назначения, но он остаётся тем же самым пакетом: исходный SYN клиента сохраняется
целиком. По отпечатку TCP SYN (JA4T) iOS-трафик уводится на отдельный порт, где
минует гейт HAProxy — тот самый, который его душил. Всё остальное идёт через nginx,
и уже там HAProxy применяет лимит, видя настоящий IP клиента через PROXY protocol.
Лимит в итоге живёт на ветке MTProto, а не на порту целиком. VPN его не касается.
Побочно из этой же схемы получается маскировка: неаутентифицированные соединения отдаются на собственный работающий сайт с настоящим сертификатом Let's Encrypt. Не «прокси, притворяющийся сайтом», а сервер, который при любой проверке отдаёт настоящий сайт.
flowchart LR
C["Клиент<br/>всегда :443"] --> J{"nftables<br/>отпечаток TCP SYN"}
J -->|"iOS → :8443"| N["nginx<br/>слушает оба порта<br/>разбирает SNI"]
J -->|"остальные → :443"| N
N -->|"MTProto с :443"| H["HAProxy<br/>лимит 54 за 60 сек"]
H --> T["telemt · MTProto"]
N -->|"MTProto с :8443"| T
N -->|"VPN"| X["Xray · Reality"]
N -->|"панель"| P["3x-ui"]
N -->|"SNI не опознан"| D["Разрыв соединения"]
T -.->|"нет секрета"| F["Заглушка — настоящий сайт"]
X -.->|"не аутентифицирован"| F
Клиент всегда подключается на :443 — :8443 появляется только как результат redirect'а внутри ядра (nftables), а не как отдельная точка входа. Редирект существует, чтобы увести iOS-клиентов из-под лимита HAProxy, который их душил; на самом iOS сейчас лимита нет. Лимит 54 соединения за 60 секунд в HAProxy применяется только к не-iOS MTProto-трафику.
Подробно — 01-architecture.md.
Читать по порядку — каждый шаг опирается на предыдущий.
| Шаг | О чём | |
|---|---|---|
| 01 | Архитектура | развязка целиком: JA4T, SNI-роутер, self-steal |
| 02 | Установка | сервер, система, docker, nginx, файрвол |
| 03 | Сертификаты | wildcard через DNS-01, хук продления |
| 04 | Сервисы | заглушки, SNI-роутер, MTProto, JA4T-сплит |
| 05 | Диагностика и грабли | разобранные ошибки |
Только читают состояние — ничего не меняют и не перезапускают.
sudo ./scripts/check-stack.sh # все слои разом
sudo ./scripts/diagnose-reality.sh 200 plug2.example.com # разбор сессий по логуВ configs/ — шаблоны, повторяющие структуру путей на сервере.
Все реальные значения заменены плейсхолдерами:
| Плейсхолдер | Чем заменить |
|---|---|
example.com |
ваш домен |
plug1.example.com · plug2.example.com |
ваши поддомены-заглушки |
dashboard.example.com |
поддомен панели |
YOUR_SERVER_IP |
IP сервера |
<PANEL_PATH> |
секретный префикс URL панели |
<MTPROTO_SECRET> |
openssl rand -hex 16 |