PunchKwara doctors lament poor pay, illegal salary deductionsEl Comercio¿Meta favorece a Flávio Bolsonaro y perjudica a Lula? Las claves de la polémica electoral en BrasilRTP DesportoMotoGP: terminou a temporada para RazgatliogluESPNTransfer rumors, news: Premier League clubs eye Alexander-Arnold amid Real Madrid uncertaintyThe Jerusalem PostMandelblit says he approved questioning Netanyahu as suspect, defense challenges wordingDaily MaverickHegseth meets Netanyahu in unannounced visit to Israel, source saysInquirerMarcos vows reforms to boost investor confidence, stock marketCollider‘Sense and Sensibility’ Officially Posts One of the Weekend’s Best Per-Theater AveragesObservador DesportoMoçambique. Supostos terroristas abatidos por fuzileirosSky TG24I titoli di Sky TG24 del 20 ottobre, edizione delle 8
The Daily Newsstand · Free, Always
Tuesday, October 20, 2026

Тестируем работу WEB-proxy для Telegram

Translate

В августе появилась информация о новом варианте проксирования в Telegram. Новость заинтересовала меня, но свободного времени, чтобы разобраться, как оно устроено, тогда не было. Наконец, время нашлось, и я готов поделиться тем, что удалось выяснить.

На Хабре вышла статья, в которой подробно описана теория устройства нового прокси. Я знаком с Web и о Proxy слышал, но принципиальное отличие новой реализации оставалось неясным. Оказалось, что это всё тот же MTProxy, но «завёрнутый» в HTTPS/WebSocket‑соединение. Telegram-клиент открывает страницу в WebView, и со стороны это выглядит как обычное взаимодействие «клиент‑сервер» через HTTP+WSS. В комментариях к статье (спасибо @Mustard!) была приведена ссылка на реализацию прототипа такого WEB-proxy. Из описания понятно, что реализация строится на наборе сервисов, написанных на Go, поверх Caddy, который громко заявил о себе.

Признаюсь, лично мне пока не довелось воспользоваться этим чудо-web-сервером. В моём pet-проекте работает nginx, и он меня вполне устраивает. Тысячами хосты создавать не требуется, поэтому переход на Caddy пока не рассматривается. Инсталляция tproxy-server предлагает установить Caddy с нуля, при этом требует наличия действующей HTTPS-страницы, что выглядит немного противоречиво. На моём сервере уже достаточно сильно разросшаяся конфигурация nginx c множеством используемых портов — своего рода мини-зоопарк, только Caddy там и не хватало. Поэтому захотелось получить tproxy-server, но «с перламутровыми пуговицами». Вот тут и начинается приключение.

Решение пришло само собой. Вникать в тонкости реализации tproxy-server не захотелось, поэтому за небольшое количество токенов одного известного LLM-сервиса и 15 минут времени я получил скрипт, который не поднимает Caddy, а просто отдаёт блок с Location. Этот блок можно добавить в существующую конфигурацию nginx, сохранив полный контроль — именно то, что было нужно. Если у вас похожая ситуация, оставляю ссылку на скрипт. Команда для развёртывания выглядит так:

sudo ./deploy/install‑nginx.sh ‑hostname your-host.ru ‑base‑path base_path ‑secret dd1234567890aсdf1234567890aсdf12 ‑site‑upstream http://127.0.0.1:3001

После запуска скрипт скачал зависимости и настроил сервисы, но завершился с ошибкой:

tproxy-server did not become ready

Начал разбираться. Сервис действительно запустился, однако для успешного завершения должна пройти ready-проба http://127.0.0.1:8081/readyz. Выяснилось, что у меня этот порт уже был занят: порты 8080 и 8081 используются для работы прокси, и на моём хосте они тоже были заняты. В итоге я сделал глобальную замену портов для директории tproxy-server:

grep -rl '8080' . | xargs sed -i 's/8080/8083/g'
grep -rl '8081' . | xargs sed -i 's/8081/8082/g'

После этого повторный запуск прошёл успешно.

Скрипт поднимает апстрим tproxy_relay, доступный извне по адресу: https://<hostname>/<base_path>. При этом необходимо указать действующий апстрим вашего сайта, куда по умолчанию будет направляться трафик.

В случае успешного развёртывания вы увидите примерно следующий результат:

=== inside the existing server {} for your-host.ru  ===
TLS certificates, HSTS and security headers stay as they are; add only the
lines this block lacks.

gzip on;
# Carrier uplink batches reach 2 MiB; nginx defaults to 1 MiB and would break
# the transport with 413.
client_max_body_size 4m;

location ^~ /base_path/ {
    # No URI part after the upstream name: the original path, including the
    # prefix, is forwarded unchanged. Do NOT write `.../;` here.
    proxy_pass http://tproxy_relay;
    proxy_http_version 1.1;

    proxy_set_header Host $host;
    # Exactly one address. $proxy_add_x_forwarded_for appends and can produce a
    # list, which the relay rejects.
    proxy_set_header X-Forwarded-For $remote_addr;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;

    # Parked long polls must not be cut or held back (relay long_poll is 25s).
    proxy_read_timeout 90s;
    proxy_send_timeout 90s;
    proxy_buffering off;
    proxy_request_buffering off;

    access_log off;     # never log carrier URIs or Authorization
}

Apply:   sudo nginx -t && sudo systemctl reload nginx

If nginx serves the site from static files rather than an upstream, give the
relay a loopback mirror of the same root and keep --site-upstream pointed at
it (BASE_PATH.md section 4); in-prefix 404s must match the site's own:

server {
    listen 127.0.0.1:3000;
    server_name _;
    root /srv/www/site;
    index index.html;
    error_page 404 /404.html;
}
WEB-proxy: схема работы веб-сервера с tproxy_relay

WEB-proxy: схема работы веб-сервера с tproxy_relay

После добавления в приложение Telegram, WEB-proxy успешно подключается. Сервер отдаёт страницу по base_path, при этом для страницы также доступен API, используемый Telegram-клиентом. Красивое решение, не правда ли?

Отмечу, что в официальной документации не рекомендуют использовать дополнительные прокси перед развёрнутым WEB-proxy (!). Хотя теоретически это возможно, и в некоторых случаях применяется.

Суть в том, как происходит процесс рукопожатия на стороне tproxy_relay. Сервис проверяет два момента:

  1. HTTP-заголовок Host в запросе

  2. Так называемый Capability

Вот как это описывает LLM:

Как реально сопоставляется capability

1. Capability считает клиент — не сервер. Он берёт hostname, который вы вписали в поле server, и локально выводит
   HMAC-SHA256(secret, "tdesktop-web-proxy-bridge-v2\n" + host + "\n" + base) (internal/config/config.go:444-452). Готовое 43-символьное значение уходит в запрос как ?bridge=….
2. Релей пересчитывает ожидаемый HMAC из своей конфигурации — DeriveCapability(host = PublicHostname, basePath, secret) в config.go:574, а не из r.Host.
3. Сравнение идёт с значением в query: bridgeProfile достаёт bridge= и зовёт MatchCapability, который побайтово сравнивает с Profiles[i].Capability (internal/server/secrets.go:11-22, internal/session/manager.go:143-160).

То есть приложение-клиент формирует хеш и передаёт его в параметре запроса, где имя хоста берётся из https://t.me/webproxy?server=your-host.ru/base_path&secret=. Если попытаться предоставить доступ к прокси через https://t.me/webproxy?server=your-proxy.ru/base_path&secret=, то проверку по хосту мы не пройдём, так как клиент будет рассчитывать хеш, исходя из имени хоста your-proxy.ru.

Чтобы такая конфигурация работала, при настройке WEB-proxy нужно указать в качестве hostname your-proxy.ru:

sudo ./deploy/install‑nginx.sh ‑hostname your-proxy.ru ‑base‑path base_path ‑secret dd1234567890aсdf1234567890aсdf12 ‑site‑upstream http://127.0.0.1:3001

Затем следует запретить перезапись заголовка Host в конфигурации веб-сервера your-proxy.ru для Location base_path. Например, для nginx необходимо указать:

location ^~ /base_path/ {
  ...
  proxy_set_header Host your-proxy.ru;
}

То есть сохранить в заголовке исходный хост. При такой настройке подключение будет возможно и через прокси.

WEB-proxy: схема работы веб-сервера с tproxy_relay, с использованием edge-proxy

WEB-proxy: схема работы веб-сервера с tproxy_relay, с использованием edge-proxy

Если вам тоже было интересно узнать больше о внутреннем устройстве WEB-proxy или есть чем поделиться по этой теме, приглашаю в комментарии!

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.