В августе появилась информация о новом варианте проксирования в 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. Выяснилось, что у меня этот порт уже был занят: порты 8 080 и 8 081 используются для работы прокси, и на моём хосте они тоже были заняты. В итоге я сделал глобальную замену портов для директории 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>. При этом необходимо указать действующий апстрим вашего сайта, куда по умолчанию будет направляться трафик.

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

nginx still owns 80/443; only the prefix below is routed to the relay.

=== http context (e.g. /etc/nginx/conf.d/tproxy-relay.conf) ===
If your http{} already defines $connection_upgrade, drop the map and reuse
the existing name.

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      "";
}

upstream tproxy_relay {
    server 127.0.0.1:8083;
    keepalive 32;
}

=== 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 или есть чем поделиться по этой теме, приглашаю в комментарии!

Спасибо, что дочитали до конца!

Комментарии (9)


  1. 0ka
    20.10.2026 08:44

    Проверьте плодит ли клиент соединения к серверу как раньше или нет. Т.е. есть ли http2 или как в мтпрото: множество соединений к серверу (большинство сайтов себя так не ведут), ркн заметили это и поставили ограничение тлс сессий (сибирскую блокировку) на многие хостинг провайдеры чтобы заблочить мтпрото

    https://habr.com/ru/articles/1044396/


    1. gmixo Автор
      20.10.2026 08:44

      http2 поддерживается, его можно включить в nginx, с точки зрения настроек это обычный веб сервер


      1. 0ka
        20.10.2026 08:44

        поддерживается нгинксом? я про клиент, и не просто про alpn h2, а настоящее количество открытых соединений


        1. gmixo Автор
          20.10.2026 08:44

          да, я писал про конфиг nginx,

          количество соединений с клиента я не замерял, используется совместно https и websocket, соответственно под каждый ws будет отдельное соединение, сколько их реально используется не отслеживал


          1. Nemoumbra
            20.10.2026 08:44

            Ну так проверьте!


          1. 0ka
            20.10.2026 08:44

            проверил у себя:
            carrier https открывает 2 соединения.
            carrier https-lanes открывает 6 соединений, дальше тг просто зависает и бесконечно пытается переподключиться.
            carrier websocket открывает 1 соединение.
            carrier websocket-lanes открывает по соединению для каждой картинки\видео, за 30 сек листаний фото\видео\файлов на канале открылось 16 соединений.

            как по мне единственный нормальный carrier это websocket.

            у меня на компе для подключений к прокси используется edge webview из винды, но при фейле подключения предлагает подключиться через браузер

            кстати в статье в конфиге nginx есть строчка

            proxy_set_header Connection $connection_upgrade;

            у меня на неё нгинкс ругается, заменил на

            proxy_set_header Connection "upgrade";


            1. gmixo Автор
              20.10.2026 08:44

              Спасибо за замечание, дополнил в статье вывод скрипта, там было предложено использовать маппинг

              map $http_upgrade $connection_upgrade {
                  default upgrade;
                  ''      "";
              }


            1. Dreams_and_magic
              20.10.2026 08:44

              У меня всё 4 конфигурации отлично работают на виндовом клиенте и на андроиде, а на линуксе https-lanes падает.


  1. CorruptotronicPervulator
    20.10.2026 08:44

    Ставим спокойно MTProxyL и не выдумываем себе геморрой.