🤖 Выдача WEB-прокси Telegram из своего бота

О чём заметка
WEB-прокси — новый, четвёртый тип прокси в Telegram, где трафик едет внутри обычных HTTPS-запросов к вашему сайту. Что это такое и зачем нужно — в обзорной заметке WEB-прокси Telegram: трафик внутри обычного сайта; как поднять сервер — в Установка tproxy-server. Здесь разбирается только один вопрос: как раздавать такой доступ пользователям из любого Telegram-бота, что для этого нужно хранить и почему автоматическая выдача упирается в неожиданное ограничение.
Насколько это проверено
Разбор сделан по исходному коду серверной части
tproxy-server(репозиторийtelegramdesktop/tproxy-server, состояние на 22 августа 2026) и по коду Telegram Desktop. Автор прямо помечает проект как доказательство работоспособности (proof-of-concept) — экспериментальную реализацию, а не готовый продукт. Поля конфигурации и формат ссылки могут поменяться без сохранения совместимости. Практических историй массовой выдачи WEB-прокси через ботов на 22 августа 2026 года нет: клиентская поддержка ещё не дошла до стабильных сборок Telegram (подробности в обзорной заметке).
TL;DR
- Пользователю нужно передать всего два значения — имя хоста и 32-символьный шестнадцатеричный секрет. Это на порядок проще, чем выдача VLESS или WireGuard, где генерируются ключи и целые конфигурационные файлы.
- Ссылка для передачи выглядит как
https://t.me/webproxy?server=proxy.example.com&secret=000102…, есть и вариантtg://webproxyс теми же параметрами. - Секреты живут в файле
profiles.jsonна сервере. Их количество ограничено параметромmax_profiles— по умолчанию 32. - Главная проблема автоматизации: сервер не умеет перечитывать конфигурацию на лету. Добавить профиль можно только перезапуском процесса, а перезапуск рвёт активные сессии всех остальных пользователей.
- Отзыв доступа у одного человека возможен только сменой секрета его профиля. Поэтому «один профиль на пользователя» упирается в потолок из пункта 3, а «один общий секрет на всех» лишает возможности отключить конкретного человека.
- Практичный вывод на август 2026: бот может раздавать заранее нарезанный пул профилей, но не выдавать доступ по-настоящему динамически.
Что вообще нужно выдать пользователю
Сначала — чем WEB-прокси отличается от привычных схем выдачи. Когда бот выдаёт доступ к VLESS, он создаёт на сервере отдельную запись пользователя со своим идентификатором UUID — длинным уникальным номером, который сервер сверяет при каждом подключении. Когда бот выдаёт WireGuard, он генерирует пару ключей и собирает целый конфигурационный файл с адресами и маршрутами. В обоих случаях на сервере появляется физическая сущность, привязанная к конкретному человеку.
С WEB-прокси всё устроено иначе, и это устройство унаследовано от MTProxy. Пользователю передаются ровно два поля:
Hostname: proxy.example.com
Secret: 000102030405060708090a0b0c0d0e0fИмя хоста пишется без https://, без порта, без слеша и без параметров: тип прокси WEB жёстко подразумевает HTTPS и порт 443. Секрет — те же 32 шестнадцатеричных символа (16 байт), что и у обычного MTProxy; общепринята запись в нижнем регистре, её же требует установщик сервера. Никаких пользовательских ключей, сертификатов и профилей на стороне клиента не создаётся.
Проще говоря: доступ к WEB-прокси — это не персональная учётная запись, а знание пароля. Кто знает пару «домен + секрет», тот подключается; сервер не различает, один это человек или тысяча.
Из этой пары клиент сам, локально, вычисляет так называемый bridge-capability — производный «пропуск», который потом уходит в сетевой запрос вместо самого секрета. Считается он как HMAC-SHA256, где ключом служат байты секрета, а сообщением — строка tdesktop-web-proxy-bridge-v1\n вместе с именем хоста. HMAC — это способ получить из ключа и сообщения короткий отпечаток, который невозможно подделать, не зная ключа.
Практическое следствие этой формулы важнее самой криптографии: пропуск привязан к домену. Один и тот же секрет на двух разных доменах даёт два разных пропуска. Поэтому бот не может выдать пользователю «секрет вообще» — он всегда выдаёт пару. При переезде на другой домен ранее выданные ссылки ломаются просто потому, что в них записан старый домен. Сам секрет при этом не обесценивается: если тот же секрет прописан в профиле нового домена, достаточно перевыдать ссылку с новым адресом. Обратное тоже верно — один секрет на двух доменах технически допустим, но связывает обе площадки одним ключом.
Формат ссылки и как её отдавать в чате
Готовая ссылка, которую бот отправляет пользователю сообщением:
https://t.me/webproxy?server=proxy.example.com&secret=000102030405060708090a0b0c0d0e0fКлиенты также принимают эквивалентную запись через внутреннюю схему приложения: tg://webproxy?server=…&secret=…. Это тот же самый набор параметров, просто ссылка открывается напрямую установленным клиентом, минуя веб-страницу.
Публичный t.me пока не знает этого маршрута
В документации проекта прямо сказано: публичный фронтенд
t.meмаршрутwebproxyпока не регистрирует. То есть ссылка видаhttps://t.me/webproxy?...на 22 августа 2026 года не обязательно откроется как прокси — при тестировании её нужно скармливать клиенту напрямую. Для бота это значит, что нельзя рассчитывать на привычный сценарий «пользователь нажал на ссылку, всё настроилось само»: пока разумнее отдавать и ссылку, и два поля отдельным текстом, чтобы человек мог ввести их руками.
Отдельная тонкость касается доменов с национальными буквами. Если ваш домен интернационализированный (например, кириллический), пользователю нужно публиковать его в форме xn--… — это ASCII-запись такого имени, называемая A-label. Причина техническая: разные версии библиотеки, на которой построен клиент, по-разному приводят некоторые символы к каноническому виду, из-за чего один и тот же домен на Windows и на Linux даёт разные пропуски и подключение молча не состоится — подробности в разборе протокола. Бот, который отдаёт домен в ASCII-форме, эту проблему обходит целиком.
Где на сервере живут секреты
Все выданные секреты перечислены в одном файле; путь к нему задаётся обязательным полем настроек. В эталонной установке оператор правит /etc/tproxy-server/profiles.json, а служба читает его копию, которую systemd подкладывает во временный каталог при каждом старте. Структура файла проста:
{
"profiles": [
{
"name": "alpha",
"secret": "0123456789abcdef0123456789abcdef",
"backend": "127.0.0.1:2398",
"carrier_mode": "https"
},
{
"name": "beta",
"secret": "fedcba9876543210fedcba9876543210",
"backend": "127.0.0.1:2399",
"carrier_mode": "websocket",
"limits": {
"max_sessions": 32,
"max_streams": 512,
"max_streams_per_session": 32
}
}
]
}Каждая запись здесь называется профилем — это именованный набор «секрет плюс правила», под которым подключается группа пользователей. От этих полей зависит вся модель выдачи.
name — идентификатор от 1 до 64 символов, уникальный внутри файла. Он не виден пользователю и нужен серверу для учёта: по имени профиля считаются его сессии, подключения к бэкенду и ограничения скорости создания новых соединений.
secret — тот самый секрет, который получает пользователь. Принимается в шестнадцатеричном виде (32 или 34 символа) либо в кодировке base64url. Раскодироваться он обязан ровно в 16 или 17 байт, причём 17-байтовый вариант должен начинаться с байта dd — это унаследованный от MTProxy признак режима случайного дополнения. Два профиля не могут иметь секреты, дающие одинаковый пропуск: такой файл сервер не примет.
backend — адрес, куда реле отправляет извлечённый из кадров трафик этого профиля, то есть адрес работающего рядом MTProxy. Обязательно локальный числовой адрес вида 127.0.0.1:2398; имена вроде localhost и внешние адреса отвергаются. Это осознанное ограничение архитектуры: реле физически не может отправить трафик наружу, куда попросит клиент, поэтому даже взломанное реле не превращается в открытый прокси для всего интернета.
carrier_mode — режим доставки, один из четырёх (https, https-lanes, websocket, websocket-lanes); по умолчанию https. Подробнее эти режимы разобраны в заметке про устройство протокола. Здесь важно другое: режим выбирается на сервере, через профиль, и клиент про него ничего не знает. Значит, бот может выдавать разным группам пользователей разные секреты и тем самым переключать им режим доставки — например, отдавать websocket тем, у кого сеть его пропускает, и консервативный https остальным. Со стороны пользователя это по-прежнему просто «другой секрет».
limits — необязательные квоты профиля: сколько одновременных сессий и потоков ему разрешено, с какой скоростью он может создавать новые, сколько данных копить. Пропущенное значение наследуется из общих настроек сервера (а два поля — предел потоков на сессию и число одновременных подключений к бэкенду — берут меньшее из общей настройки и собственного предела потоков профиля). Заданное значение может только понизить общий потолок, но не поднять; ноль означает «наследовать», поэтому обнулить лимит в профиле нельзя. Для бота это единственный встроенный механизм справедливого дележа: если один секрет раздали слишком широко, его квота не даст ему съесть весь сервер.
У файла не должно остаться ни одного бита прав для группы и для остальных — на практике это 0400, но и 0600 проверку проходит. Сервер это проверяет и отказывается стартовать, если файл доступен группе или всем остальным. Исключение сделано для механизма systemd, который подкладывает секреты сервису во временный каталог; тогда допускаются права только на чтение.
Главное ограничение: перезапуск ради каждого нового пользователя
Сервер читает конфигурацию ровно один раз — при запуске. Обработчика сигнала на перечитывание файлов в коде нет: процесс подписан только на сигналы завершения работы. Значит, добавление нового профиля в profiles.json само по себе не делает ничего — сервер продолжает работать со старым списком секретов, пока его не перезапустят.
А перезапуск, в свою очередь, не бесплатный. В документации это сказано прямо: рестарт реле аннулирует все активные сессии, и существующие соединения намеренно не восстанавливаются. Клиенты после этого пересоздают транспорт и подключаются заново, то есть у каждого активного пользователя в этот момент происходит обрыв связи на несколько секунд.
Из этих двух фактов получается неприятная арифметика. Классическая модель бота «пользователь оплатил — бот тут же выдал ему персональный доступ» здесь означает: каждая новая покупка перезапускает сервис и роняет сессии всех остальных. При десятке выдач в час прокси начинает моргать почти непрерывно. Проще говоря: выдача доступа перестаёт быть операцией над одним пользователем и становится операцией над всем сервером.
Отсюда следуют три работающие стратегии, и выбирать приходится осознанно.
Прежде чем их перечислять, важная деталь, которую легко упустить: секрет должен знать не только реле, но и MTProxy за ним. Реле передаёт байты рукопожатия прозрачно и в них не заглядывает, поэтому каждый выданный секрет обязан быть перечислен в аргументах запуска MTProxy. Профиль, добавленный только в файл профилей, даст странную картину: настройки вроде бы верные, а подключение не проходит. Хорошая новость в том, что перезапуск MTProxy, в отличие от перезапуска реле, активные сессии не рвёт — потоки просто переподключаются через живую сессию.
Пул заранее нарезанных профилей. Оператор один раз создаёт N профилей со случайными секретами (по умолчанию их не может быть больше 32 — это параметр max_profiles), прописывает те же секреты бэкенду MTProxy, запускает сервер и дальше просто раздаёт неиспользованные секреты из этого пула. Бот хранит соответствие «профиль → пользователь» в своей базе, а сервер о пользователях ничего не знает. Перезапуски не нужны вовсе, пока пул не исчерпан. Плата: потолок числа выданных секретов на один домен — по умолчанию 32, поднять его можно, но это правка файла настроек и, значит, тот же перезапуск, — и необходимость планировать пул заранее.
Пакетное применение. Бот копит заявки и применяет их окном — например, раз в сутки ночью, одним перезапуском. Пользователь получает доступ не мгновенно, а к утру. Для платного сервиса это обычно неприемлемо, для закрытого круга — вполне.
Один общий секрет. Все пользователи получают один и тот же секрет, как это давно делается с обычным MTProxy. Выдача становится мгновенной и бесплатной, перезапусков нет вообще. Плата — невозможность отключить одного человека.
Отзыв доступа: то, чего в этой модели нет
Отзыв — обратная сторона «доступ это просто знание пароля». Раз сервер не различает пользователей внутри профиля, отобрать доступ у одного человека, не тронув остальных, невозможно. Единственный способ сделать секрет недействительным — изменить его в profiles.json и перезапустить реле. После этого доступ теряют все, кому этот секрет был выдан.
Для сравнения: в VLESS отзыв — это удаление конкретной записи пользователя на узле, и соседи ничего не замечают. Именно поэтому подписочные сервисы обычно строятся на VLESS, а не на MTProxy: без индивидуального отзыва невозможно закрыть доступ по окончании оплаченного периода.
Если индивидуальный отзыв нужен, остаётся схема «один профиль на пользователя»: тогда смена секрета в его профиле бьёт только по нему. Но эта схема сразу упирается во всё те же два ограничения — потолок числа профилей и перезапуск сервиса ради изменения. Плюс появляется третье, но здесь важно не преувеличить. Если всем профилям достаточно общей политики и маршрутизации, им хватит одного процесса MTProxy, которому просто перечислены все секреты. Отдельный процесс на своём порту (и своя строка в правилах файрвола) нужен только тем профилям, которым требуется собственная маршрутизация или раздельная статистика на стороне MTProxy: сам MTProxy ведёт учёт на уровне процесса, а не отдельного секрета.
Честный вывод про биллинг
На август 2026 WEB-прокси не годится для сервиса с оплатой по подписке и автоматическим отключением после окончания периода. Механизма, который делает это дёшево, в архитектуре нет: ни персональных учётных записей, ни горячего применения изменений, ни отзыва без ущерба соседям. Он хорошо подходит для другого — раздать рабочий доступ ограниченному кругу людей там, где остальные способы не проходят по сети.
Что бот должен хранить у себя
Раз сервер не ведёт учёт пользователей, всё состояние ложится на бота. Минимальный набор полей на одну выдачу:
- имя хоста, с которого выдан доступ (оно же часть пропуска, поэтому обязательно сохранять вместе с секретом, а не подставлять «текущий домен» при показе);
- имя профиля на сервере — чтобы знать, какую запись править при отзыве;
- сам секрет в открытом виде: из работающего процесса реле его уже не достать (после старта в памяти остаётся только производный пропуск), но на сервере он продолжает лежать открытым текстом в файле профилей и в переменных окружения MTProxy. Хранить его у бота нужно не потому, что он безвозвратно теряется, а чтобы не ходить за ним на сервер и не зависеть от его переустановки;
- идентификатор пользователя Telegram и дата выдачи;
- режим доставки, если разным группам выдаются разные (иначе потом не понять, почему у части людей другое поведение).
Отдельно стоит подумать про учёт трафика. Реле публикует метрики на локальном служебном порту, но это счётчики классов событий по процессу, а не разбивка по людям — и это сделано намеренно, чтобы не хранить лишнего. Если нужен учёт хотя бы по группам, единственный доступный способ — дать каждой группе свой профиль со своим MTProxy и снимать статистику с этого процесса. Индивидуального учёта по пользователям в этой схеме нет, и в архитектуре первой версии места под него не заложено.
Сколько ботов и сколько доменов
Ещё одно ограничение, которое стоит заложить в архитектуру сразу: один процесс реле обслуживает ровно одно имя хоста — в настройках ровно одно поле для публичного домена (подробнее в заметке про установку).
Поэтому масштабирование выглядит так: каждому новому серверу — свой домен, своя DNS-запись, свой сертификат и свой набор профилей. Бот, раздающий доступ на несколько серверов, обязан хранить домен вместе с каждой выдачей и не пытаться «переназначить» пользователя на другой сервер простой заменой адреса — секрет там другой, и пропуск не совпадёт.
Обратная сторона: домены получаются независимыми. Если один из них попадёт под блокировку, остальные продолжат работать, а бот сможет перевыдать пострадавшим пользователям пару с другого сервера. Для схемы с одним общим секретом это, пожалуй, главный аргумент держать хотя бы два домена.
Пошаговый план внедрения
- Поднять сервер по инструкции из заметки про установку и убедиться, что обычный сайт на домене открывается, а служебные порты снаружи закрыты.
- Решить модель выдачи: пул профилей, пакетное применение или один общий секрет. От этого зависит вся остальная логика бота.
- Если выбран пул — сгенерировать секреты заранее (
openssl rand -hex 16на каждый), вписать их вprofiles.jsonодним разом и запустить сервер; после этого перезапуски не понадобятся. - Завести в базе бота таблицу выдач с полями из раздела выше, обязательно включая домен и имя профиля.
- Научить бота отдавать пользователю и готовую ссылку, и два поля отдельным текстом — на случай, если ссылка
t.me/webproxyв его клиенте не сработает. - Заранее написать процедуру отзыва (смена секрета плюс перезапуск) и честно посчитать, скольких пользователей она затронет.
- Проверить, что в журналах бота и сервера не оседают ни секреты, ни ссылки с пропуском: адрес bridge-запроса содержит производный пропуск, и его попадание в логи равносильно утечке доступа.
📚 См. также
- WEB-прокси Telegram: трафик внутри обычного сайта — обзор нового типа прокси и ответ на вопрос, можно ли им пользоваться уже сейчас.
- Установка tproxy-server: свой WEB-прокси на своём домене — развёртывание сервера от DNS-записи до проверки.
- Как устроен WEB-прокси изнутри — кадры, пропуск, режимы транспорта и лимиты.
- MTProxy и Telegram-транспорты — раздел — обычный MTProxy, от которого WEB-прокси унаследовал модель секретов.
- 🔗 telegramdesktop/tproxy-server — исходники серверной части и вся официальная документация проекта.
🤖 Эти статьи открыты — можно обучать на них ИИ
При желании вы можете натренировать ИИ на наших статьях. Исходное форматирование доступно в Forgejo: исходник этой заметки · скачать весь репозиторий одним zip-архивом.