Multi-protocol TCP/HTTP(S) proxy with web UI: forward/SNI/Host, ports added live, no restart
10K+
Многоцелевой TCP-прокси с веб-настройкой. Без внешних зависимостей (чистый Python stdlib).
Один процесс проксирует много таргетов, правила добавляются на лету через веб-панель — без перезапуска контейнера. Конфиг хранится в JSON-томе.
| Режим | Для чего | Как маршрутизирует |
|---|---|---|
forward | любой TCP-протокол: ldap/ldaps, ssh, БД, http(s) на нестандартном порту | один порт → один host:port, сырой passthrough |
tls | много HTTPS-сайтов на одном порту (напр. 443) | по имени из TLS ClientHello (SNI) |
http | много HTTP-сайтов на одном порту (напр. 80) | по заголовку Host |
connect | HTTP forward-прокси: ставится в HTTPS_PROXY клиента | принимает CONNECT на любой хост, уводит через via |
forward протокол-независим — прокси не разбирает содержимое, просто гоняет байты. tls/http нужны только чтобы посадить много сайтов одного протокола на один порт.
services:
proxy:
image: edwardfuchs/webproxy:latest
# host-сеть обязательна: порты добавляются динамически через панель,
# пробросить их через -p заранее нельзя. Все открытые порты сразу на хосте.
network_mode: host
restart: unless-stopped
environment:
ADMIN_HOST: "0.0.0.0" # адрес веб-панели
ADMIN_PORT: "8088" # порт веб-панели
ADMIN_PASSWORD: "" # если задан — включается Basic Auth. Обязательно для хоста в интернете!
BIND_HOST: "0.0.0.0" # на каком адресе слушать проксируемые порты
volumes:
- ./data:/data # config.json переживает пересоздание контейнера
docker compose up -d
# панель: http://<хост>:8088
Или напрямую:
docker run -d --network host -v "$PWD/data:/data" \
-e ADMIN_PASSWORD=секрет edwardfuchs/webproxy:latest
| Переменная | По умолчанию | Назначение |
|---|---|---|
ADMIN_HOST | 0.0.0.0 | Адрес, на котором слушает веб-панель. Поставь 127.0.0.1, чтобы спрятать её и ходить через SSH-туннель. |
ADMIN_PORT | 8088 | Порт веб-панели. |
ADMIN_PASSWORD | (пусто) | Если задан — включается HTTP Basic Auth на панель. Обязательно, если хост смотрит в интернет: с host-сетью панель торчит наружу. |
BIND_HOST | 0.0.0.0 | Адрес, на котором открываются проксируемые порты. |
CONFIG_PATH | /data/config.json | Путь к файлу конфигурации внутри контейнера. |
tls / http)В поле «Маршруты» веб-панели — по строке домен = host:port:
site1.com = 10.0.0.1:443
*.site2.com = 10.0.0.2:443
_ = 10.0.0.9:443 # строка с _ — маршрут по умолчанию
У правила есть необязательное поле via — тогда трафик идёт к цели не напрямую, а через вышестоящий прокси. TLS остаётся сквозным (сертификат приходит настоящий от цели):
via: socks5://user:pass@upstream:1080 # SOCKS5 (+ socks5h / socks4 / socks4a)
via: http://user:pass@proxy:8080 # HTTP CONNECT (+ https:// — TLS до прокси)
Работает во всех режимах. Пример «сайт через SOCKS5» (клиент резолвит имя в прокси, а до цели идём через upstream):
порт: 443, режим: tls
маршрут: huggingface.co = $sni:443 # $sni — форвардить на имя из SNI
via: socks5://user:pass@upstream:1080
$sni (и $host для http-режима) в таргете = имя из SNI/Host: одно правило обслуживает любой сайт без перечисления, а адрес цели резолвит upstream.
Режим connect делает из webproxy обычный HTTP-прокси, который ловит любой хост (включая редиректы huggingface на CDN/Xet) и уводит через via:
правило: режим connect, порт 8888, via: socks5://user:pass@upstream:1080
export HTTPS_PROXY=http://<proxy-host>:8888
export HTTP_PROXY=http://<proxy-host>:8888
export HF_HUB_ENABLE_HF_TRANSFER=0 # иначе Rust-загрузчик игнорит прокси и виснет на 0B
export HF_HUB_DISABLE_XET=1
huggingface-cli download <repo> <file>
HF_ENDPOINT для этого не нужен: он меняет только хост API, а файлы качаются по 302-редиректу с CDN-хоста — его ловит именно транспортный прокси (HTTPS_PROXY / режим connect), а не смена endpoint.
Каждое соединение пишется строкой — видно, куда прокси его отправил и на какой реальный IP приконнектился (полезно для диагностики маршрутизации по SNI/Host):
[conn] :636 tls SNI='dc01.my.home' client=10.0.0.5 -> target=dc01.my.home:636 peer=10.0.0.1:636
[proxy] :636 нет маршрута для SNI='dcXX.my.home'
нет маршрута ... означает, что клиент прислал SNI/Host, не совпавший ни с одним маршрутом (или не прислал вовсе) — соединение закрывается, клиент видит SSL: UNEXPECTED_EOF_WHILE_READING. Добавь совпадающий маршрут или дефолт _ = host:port.
Content type
Image
Digest
sha256:e9f900bda…
Size
41.2 MB
Last updated
3 months ago
docker pull edwardfuchs/webproxy