WEB Proxy для Telegram Desktop на своём VPS

В моей инфраструктуре давно крутится Telegram-трафик через SOCKS5-туннель на VPS — но у этого подхода есть минусы: SOCKS5 легко режется DPI, а клиент надо настраивать отдельно. Альтернатива от самих разработчиков Telegram Desktop — WEB Proxy: прокси, который маскируется под обычный HTTPS-сайт на 443-м порту. Такой трафик не отличить от обычного захода на сайт, а подключение настраивается в два поля в настройках клиента.

В этой статье — как я развернул tproxy-server на свежем VPS (Debian 13, Hostkey), какие проблемы встретил в установщике и как их обошёл. Полезно всем, кто решит поднять свой WEB Proxy.

Зачем WEB Proxy, а не SOCKS5

КритерийSOCKS5-туннельWEB Proxy (tproxy-server)
Внешний вид трафикаSOCKS5 (легко детектится)Обычный HTTPS на 443
Маскировка под сайтнетда (свой статический сайт)
Настройка в Telegram Desktopтип прокси + адрес + портhostname + secret
Держит сам Telegramдада (через MTProxy на бэкенде)

Схема работы tproxy-server:

Telegram Desktop → HTTPS:443 → Caddy → tproxy-server:8080 → MTProxy:2398 → Telegram
                                        └ admin :8081 (/readyz, /healthz)

Caddy отдаёт Let’s Encrypt сертификат и проксирует весь трафик на tproxy-server, который сам решает: обычные запросы отдаёт сайт-маскировку, а запросы WEB Proxy — гонит в MTProxy. Снаружи это просто HTTPS-сайт.

Установка: что делает install.sh

У upstream отличный установщик — deploy/install.sh делает почти всё сам:

  • ставит пакеты (ca-certificates, curl, nftables);
  • ставит Caddy 2.11.4 и Go 1.26.5 (если нет);
  • собирает tproxy-server из исходников;
  • собирает MTProxy (pinned commit f36d8af) в /opt/MTProxy;
  • создаёт systemd-сервисы: tproxy-server, mtproxy, tproxy-firewall, caddy;
  • настраивает таймер refresh-mtproxy-config (обновление списка серверов Telegram раз в сутки);
  • поднимает nft-правило, закрывающее порты MTProxy (2398, 8888) от внешнего мира.

Запуск:

1
2
3
./deploy/install.sh --hostname vps03.nixg.ru \
  --email me@example.com --site-dir /tmp/tproxy-site \
  --secret "$(cat /tmp/tproxy-secret.txt)"

--secret — 32 hex-символа (openssl rand -hex 16), --site-dir — каталог со статическим сайтом-маскировкой.

Грабли №1: mtproxy.service падает с 203/EXEC, readyz 503

После первого прогона установщика tproxy-server был active, Caddy слушал 80/443, но прокси не работал:

1
● mtproxy.service - failed (Result: exit-code), status=203/EXEC

Порт 2398 не слушался, а /readyz административного эндпоинта отдавал 503 (readyz — это «вся цепочка готова», healthz при этом был 200).

Причина оказалась в правах на бинарь после сборки. Установщик собирает MTProxy в /opt/MTProxy/objs/bin/mtproto-proxy, но из-за umask бинарь и каталоги получают права 700 root:root. Юнит mtproxy.service запускается от пользователя mtproxyNoNewPrivileges=true и ProtectSystem=strict) — процесс не может прочитать бинарь и умирает с 203/EXEC.

Лечится одной серией команд:

1
2
3
4
chmod 755 /opt/MTProxy /opt/MTProxy/objs /opt/MTProxy/objs/bin
chown root:mtproxy /opt/MTProxy/objs/bin/mtproto-proxy
chmod 750 /opt/MTProxy/objs/bin/mtproto-proxy
systemctl restart mtproxy

После этого mtproxy — active (running), слушает 0.0.0.0:2398, readyz → 200.

Грабли №2: флейк go test в установщике

Первый прогон install.sh (холодный кэш Go, сборка всех пакетов параллельно) упал на тестах:

1
2
FAIL: internal/config — TestLoadAcceptsSystemdCredentialReadPermissions:
group/other-readable profiles file outside a credential directory was accepted

А повторный go test ./... (тёплый кэш) — PASS. Это классическая гонка при холодном параллельном прогоне тестов, а не баг кода. Лечение простое: перезапустить install.sh — дальше идёт нормально.

Грабли №3: «не открыты порты 80/443» — ложная тревога

Снаружи curl на 80/443 не отвечал, и я заподозрил firewall провайдера (Hostkey). Пошёл в панель — а там вообще нет настроек firewall/прокси. Полез в документацию, искал API…

На самом деле всё проще: порты у провайдера открыты по умолчанию, просто на 80/443 ничего не слушало — curl получал connection refused. Проверка: поднимаю временный python3 -m http.server 80 → TCP connect снаружи OK. После установки Caddy сам слушает 80/443.

Так что если у вас после установки снаружи не открывается сайт — сначала проверьте, что порт слушается (ss -tlnp), а не ищите firewall в панели провайдера.

Проверка после установки

1
2
3
4
5
6
systemctl status tproxy-server mtproxy tproxy-firewall caddy   # все active
curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8081/readyz    # 200
curl -s -o /dev/null -w '%{http_code}' https://vps03.nixg.ru/          # 200 (сайт)
openssl s_client -connect vps03.nixg.ru:443 -servername vps03.nixg.ru  # LE-серт
ss -tlnp | grep -E ':80 |:443|:2398|:8080|:8081'
nft list ruleset | grep -E '2398|8888'   # закрыто от внешнего мира

Наружу видны только 80/443. MTProxy-бэкенд (2398, 8888) закрыт nft — iifname != "lo" ... drop, то есть бэкенд не светится и не принимает подключения напрямую.

Настройка клиента

В Telegram Desktop: Settings → Advanced → Connection Type → Use custom proxy → NEW PROXY → WEB Proxy:

  • hostname: vps03.nixg.ru
  • secret: (32 hex-символа, которые вы сгенерировали)

Итог

WEB Proxy — отличная замена SOCKS5-туннелю, если нужна маскировка под обычный HTTPS: настраивается в два поля, трафик неотличим от сайта, а готовый установщик upstream делает почти всю работу. Главные грабли — права на бинарь MTProxy после сборки (203/EXEC) и ложная тревога с «закрытыми портами». Оба решаются за пару минут, если знать, куда смотреть.

Полный набор файлов (заметки, сайт-маскировку, команду установки) я сложил в репозиторий tproxy-web — точнее, в свой tproxy-web на gitverse, а INSTALL_NOTES.md с деталями лежит там же.