Hysteria 2 каскад: различия между версиями
Владимир (обсуждение | вклад) |
Владимир (обсуждение | вклад) |
||
| Строка 875: | Строка 875: | ||
<div class="mw-collapsible-content"> | <div class="mw-collapsible-content"> | ||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
# --- | #!/bin/bash | ||
# ( | # ============================================================= | ||
# hy2-users — управление пользователями Hysteria2 на RU сервере | |||
# users.txt: /opt/hysteria-ru/users/users.txt | |||
# Формат: username password (по одной строке) | |||
# Перезапуск контейнера НЕ требуется — изменения применяются мгновенно | |||
# ============================================================= | |||
USERS_FILE="/opt/hysteria-ru/users/users.txt" | |||
DOMAIN="your-hy2-ru-domain.example.com" | |||
PORT="2053" | |||
RED='\033[0;31m' | |||
GREEN='\033[0;32m' | |||
YELLOW='\033[1;33m' | |||
CYAN='\033[0;36m' | |||
BOLD='\033[1m' | |||
NC='\033[0m' | |||
# --- Вспомогательные функции --- | |||
check_root() { | |||
if [[ $EUID -ne 0 ]]; then | |||
echo -e "${RED}Ошибка: запусти скрипт от root (sudo bash hy2-users.sh)${NC}" | |||
exit 1 | |||
fi | |||
} | |||
check_users_file() { | |||
if [[ ! -f "$USERS_FILE" ]]; then | |||
echo -e "${RED}Ошибка: файл пользователей не найден: $USERS_FILE${NC}" | |||
exit 1 | |||
fi | |||
} | |||
get_usernames() { | |||
awk '{print $1}' "$USERS_FILE" | |||
} | |||
get_password() { | |||
local user="$1" | |||
awk -v u="$user" '$1 == u {print $2}' "$USERS_FILE" | |||
} | |||
press_enter() { | |||
echo "" | |||
read -rp "Нажми Enter для возврата в меню..." | |||
} | |||
print_config_for() { | |||
local user="$1" | |||
local pass="$2" | |||
echo "" | |||
echo -e "${BOLD}┌─────────────────────────────────────────────┐${NC}" | |||
echo -e "${BOLD}│ Hysteria2 URI для клиента │${NC}" | |||
echo -e "${BOLD}└─────────────────────────────────────────────┘${NC}" | |||
echo "" | |||
echo -e "${BOLD} hysteria2://${pass}@${DOMAIN}:${PORT}${NC}" | |||
echo "" | |||
echo -e "${CYAN}Для podkop (OpenWrt):${NC} вставить URI как hy2 outbound" | |||
echo -e "${CYAN}Для мобильных клиентов:${NC} NekoBox, husi, Exclave — импорт URI" | |||
echo -e "${CYAN}Пользователь:${NC} ${user}" | |||
} | |||
# --- Функции меню --- | |||
list_users() { | |||
echo "" | |||
echo -e "${BOLD}${CYAN}=== Список пользователей ===${NC}" | |||
mapfile -t users < <(get_usernames) | |||
if [[ ${#users[@]} -eq 0 ]]; then | |||
echo -e "${YELLOW}Пользователей нет.${NC}" | |||
else | |||
for i in "${!users[@]}"; do | |||
echo -e " ${BOLD}$((i+1)).${NC} ${users[$i]}" | |||
done | |||
echo "" | |||
echo -e " Всего: ${#users[@]} пользователь(ей)" | |||
fi | |||
press_enter | |||
} | |||
add_user() { | |||
echo "" | |||
echo -e "${BOLD}${CYAN}=== Добавить пользователя ===${NC}" | |||
read -rp "Имя пользователя: " username | |||
username=$(echo "$username" | tr -d ' ') | |||
if [[ -z "$username" ]]; then | |||
echo -e "${RED}Имя не может быть пустым.${NC}" | |||
press_enter | |||
return | |||
fi | |||
if grep -q "^${username} " "$USERS_FILE" 2>/dev/null; then | |||
echo -e "${RED}Пользователь '${username}' уже существует.${NC}" | |||
press_enter | |||
return | |||
fi | |||
read -rp "Пароль (Enter = сгенерировать автоматически): " password | |||
if [[ -z "$password" ]]; then | |||
password=$(openssl rand -base64 18 | tr -d '=/+' | head -c 22) | |||
echo -e " Сгенерирован пароль: ${BOLD}${password}${NC}" | |||
fi | |||
echo "${username} ${password}" >> "$USERS_FILE" | |||
echo -e "${GREEN}Пользователь '${username}' добавлен.${NC}" | |||
echo -e "${YELLOW}Изменения применяются мгновенно — перезапуск не нужен.${NC}" | |||
print_config_for "$username" "$password" | |||
press_enter | |||
} | |||
delete_user() { | |||
echo "" | |||
echo -e "${BOLD}${CYAN}=== Удалить пользователя ===${NC}" | |||
mapfile -t users < <(get_usernames) | |||
if [[ ${#users[@]} -eq 0 ]]; then | |||
echo -e "${YELLOW}Нет пользователей для удаления.${NC}" | |||
press_enter | |||
return | |||
fi | |||
for i in "${!users[@]}"; do | |||
echo -e " ${BOLD}$((i+1)).${NC} ${users[$i]}" | |||
done | |||
echo " 0. Отмена" | |||
echo "" | |||
read -rp "Выбери номер для удаления: " choice | |||
if [[ "$choice" == "0" || -z "$choice" ]]; then | |||
return | |||
fi | |||
if ! [[ "$choice" =~ ^[0-9]+$ ]] || (( choice < 1 || choice > ${#users[@]} )); then | |||
echo -e "${RED}Неверный номер.${NC}" | |||
press_enter | |||
return | |||
fi | |||
target="${users[$((choice-1))]}" | |||
read -rp "Удалить пользователя '${target}'? (y/N): " confirm | |||
if [[ "$confirm" =~ ^[Yy]$ ]]; then | |||
sed -i "/^${target} /d" "$USERS_FILE" | |||
echo -e "${GREEN}Пользователь '${target}' удалён.${NC}" | |||
echo -e "${YELLOW}Изменения применяются мгновенно — перезапуск не нужен.${NC}" | |||
else | |||
echo "Отменено." | |||
fi | |||
press_enter | |||
} | |||
show_config() { | |||
echo "" | |||
echo -e "${BOLD}${CYAN}=== URI клиента ===${NC}" | |||
mapfile -t users < <(get_usernames) | |||
if [[ ${#users[@]} -eq 0 ]]; then | |||
echo -e "${YELLOW}Нет пользователей.${NC}" | |||
press_enter | |||
return | |||
fi | |||
for i in "${!users[@]}"; do | |||
echo -e " ${BOLD}$((i+1)).${NC} ${users[$i]}" | |||
done | |||
echo " 0. Отмена" | |||
echo "" | |||
read -rp "Выбери номер пользователя: " choice | |||
if [[ "$choice" == "0" || -z "$choice" ]]; then | |||
return | |||
fi | |||
if ! [[ "$choice" =~ ^[0-9]+$ ]] || (( choice < 1 || choice > ${#users[@]} )); then | |||
echo -e "${RED}Неверный номер.${NC}" | |||
press_enter | |||
return | |||
fi | |||
local user="${users[$((choice-1))]}" | |||
local pass | |||
pass=$(get_password "$user") | |||
print_config_for "$user" "$pass" | |||
press_enter | |||
} | |||
# --- Главное меню --- | |||
main_menu() { | |||
check_root | |||
check_users_file | |||
while true; do | |||
clear | |||
echo -e "${BOLD}${CYAN}" | |||
echo "╔══════════════════════════════════════╗" | |||
echo "║ Hysteria2 User Manager (RU) ║" | |||
echo "║ Домен: ${DOMAIN} ║" | |||
echo "╚══════════════════════════════════════╝" | |||
echo -e "${NC}" | |||
mapfile -t users < <(get_usernames) | |||
echo -e " Активных пользователей: ${BOLD}${#users[@]}${NC}" | |||
echo "" | |||
echo -e " ${BOLD}1.${NC} Список пользователей" | |||
echo -e " ${BOLD}2.${NC} Добавить пользователя" | |||
echo -e " ${BOLD}3.${NC} Удалить пользователя" | |||
echo -e " ${BOLD}4.${NC} Показать URI клиента" | |||
echo -e " ${BOLD}0.${NC} Выход" | |||
echo "" | |||
read -rp "Выбор: " option | |||
case "$option" in | |||
1) list_users ;; | |||
2) add_user ;; | |||
3) delete_user ;; | |||
4) show_config ;; | |||
0) echo "Выход."; exit 0 ;; | |||
*) echo -e "${RED}Неверный выбор.${NC}"; sleep 1 ;; | |||
esac | |||
done | |||
} | |||
main_menu | |||
</syntaxhighlight> | </syntaxhighlight> | ||
</div> | </div> | ||
Текущая версия от 10:14, 26 апреля 2026
Hysteria2: установка каскадной цепочки Entry Node → Exit Node → WARP
Что такое Hysteria2 и зачем он нужен
Проблема: ограничения традиционных прокси-протоколов
Современные системы глубокой инспекции пакетов (DPI — Deep Packet Inspection), применяемые интернет-провайдерами и регуляторами по всему миру, умеют анализировать не только адреса назначения, но и характер самого трафика. Даже зашифрованное соединение можно идентифицировать — по поведению, по паузам между пакетами, по размерам передаваемых блоков данных, по TLS-fingerprint.
Большинство прокси-протоколов работают поверх TCP. Это означает:
- Все потери пакетов обрабатываются дважды — сначала на уровне транспорта, затем внутри туннеля (head-of-line blocking).
- При нестабильном канале (мобильная сеть, спутник, VPN на слабом железе) скорость резко падает.
- TCP-fingerprint легче распознаётся и блокируется.
Hysteria2 решает эту проблему фундаментально иначе: он работает поверх QUIC — транспортного протокола на базе UDP, разработанного в Google и стандартизированного в RFC 9000.
Решение: QUIC/UDP с маскировкой под HTTPS
Hysteria2 строит зашифрованный туннель поверх UDP, используя QUIC:
- Нет head-of-line blocking: потеря одного пакета не блокирует остальные потоки.
- Быстрое восстановление соединения: QUIC переподключается за 0-RTT (без дополнительного round-trip).
- Маскировка под HTTPS: снаружи трафик Hysteria2 выглядит как обычный QUIC/HTTP3 браузерный трафик — сервер «притворяется» обычным HTTPS-сайтом и возвращает реальный веб-контент при неавторизованных запросах.
- Congestion control: поддерживает несколько алгоритмов управления перегрузкой, включая BBR.
| Протокол | Транспорт | Как выглядит для DPI | Устойчивость к блокировке | Скорость при потерях |
|---|---|---|---|---|
| Обычный VPN (WireGuard, OpenVPN) | UDP / TCP | Характерный шаблон | Низкая — fingerprint легко детектируется | Высокая (WG) / Низкая (OpenVPN) |
| Shadowsocks | TCP | Случайный зашифрованный поток | Средняя — выявляется по энтропии | Средняя |
| VLESS + REALITY | TCP (TLS) | TLS с реальным сертификатом стороннего сайта | Высокая | Средняя |
| NaïveProxy | TCP (HTTP/2 TLS) | Неотличим от Chrome HTTPS | Очень высокая | Средняя (TCP Head-of-Line) |
| Hysteria2 | UDP (QUIC/TLS) | QUIC/HTTP3, маскировка под HTTPS | Очень высокая | Высокая — устойчив к потерям |
Как работает Hysteria2 технически
Hysteria2 использует протокол QUIC в качестве транспорта:
- Клиент устанавливает QUIC-соединение с сервером — это TLS 1.3 поверх UDP.
- Внутри QUIC-соединения открываются независимые потоки (streams) для каждого проксируемого соединения.
- Сервер обрабатывает входящий трафик: авторизует клиента (по паролю или через внешний скрипт), затем перенаправляет трафик через указанный outbound.
- Если к серверу обращаются без правильного пароля — сервер ведёт себя как обычный HTTPS-сайт (masquerade) и возвращает реальный контент с заданного URL.
Особенности аутентификации в данной схеме:
- EU сервер: использует одиночный пароль — только для входящих соединений от RU клиента (не клиентских устройств).
- RU сервер: использует command-аутентификацию — при каждом подключении вызывается внешний bash-скрипт, который проверяет пароль по файлу
users.txt. Это позволяет добавлять и удалять пользователей без перезапуска сервера.
Каскадная цепочка: зачем два сервера
В данном решении используется каскад из двух серверов: Entry Node (ближайший к клиентам сервер-вход) и Exit Node (сервер-выход в интернет). Это архитектурное решение принято намеренно по нескольким причинам:
- Снижение риска для клиента: клиентские устройства подключаются только к ближайшему серверу. Соединение с соседним узлом менее заметно, чем прямое соединение с зарубежным сервером.
- Раздельная аутентификация: пароль «клиент → Entry Node» знают только конечные пользователи. Пароль «Entry Node → Exit Node» знают только серверы. Компрометация клиентского пароля не раскрывает пароль соединения между серверами.
- Анонимизация выхода: Exit Node передаёт трафик через Cloudflare WARP. Конечные сайты видят IP Cloudflare, а не IP ваших серверов.
- Параллельные независимые каналы: Hysteria2 работает рядом с NaïveProxy и 3x-ui на тех же серверах, не конфликтуя с ними. Падение одного канала не блокирует другие.
Схема конкретного решения
Компоненты инфраструктуры
| Компонент | Роль | Адрес / Домен | ОС |
|---|---|---|---|
| Клиентские устройства | Конечные пользователи | — | iOS, Android, Windows, macOS, Linux |
| OpenWrt роутер | Прозрачный обход для всей домашней сети | — | OpenWrt 24.10 (podkop нативно поддерживает hy2 URI) |
| RU сервер (Entry Node) | Точка входа клиентов | YOUR_RU_SERVER_IP / your-hy2-ru-domain.example.com |
Ubuntu 24.04 |
| EU сервер (Exit Node) | Точка выхода в интернет | YOUR_EU_SERVER_IP / your-hy2-eu-domain.example.com |
Ubuntu 24.04 |
| Cloudflare WARP | Финальный выход в интернет | IP Cloudflare | — |
Полная схема прохождения трафика

Протоколы на каждом участке цепочки
| Участок | Протокол | Шифрование | Что видит наблюдатель |
|---|---|---|---|
| Устройство → Роутер | Обычный TCP/UDP | Нет (локальная сеть) | Локальный трафик |
| Роутер / ПК → RU сервер | QUIC / UDP (Hysteria2) | TLS 1.3 (Let's Encrypt) | HTTPS/HTTP3 трафик к обычному сайту |
| RU клиент → EU сервер | QUIC / UDP (Hysteria2) | TLS 1.3 (Let's Encrypt) | HTTPS/HTTP3 трафик к обычному сайту |
| EU сервер → 3x-ui inbound | SOCKS5 (localhost) | — (только на EU сервере) | Только локально |
| EU 3x-ui → WARP | WireGuard (gVisor TUN) | WireGuard | Зашифрованный WireGuard к Cloudflare |
| WARP → Интернет | Обычный TCP/UDP | — | IP Cloudflare |
Дополнительные каналы (параллельно Hysteria2)
Hysteria2 работает независимо от NaïveProxy и 3x-ui. Все три канала активны одновременно:
| Канал | Протокол | Маршрут | Статус |
|---|---|---|---|
| 3x-ui каскад (основной) | VLESS / REALITY | Клиенты → RU 3x-ui → EU 3x-ui → Интернет | ✅ Активен |
| NaïveProxy каскад | HTTP/2 naïve | Роутер/ПК → RU Caddy → EU Caddy → WARP | ✅ Активен |
| Hysteria2 каскад (этот гайд) | QUIC/UDP hy2 | Клиенты → RU hy2 → EU hy2 → WARP | ✅ Активен |
| MTProto (Telegram) | MTProto | Клиенты Telegram → EU :8443, :2443 | ✅ Активен |
Предварительные требования
Инфраструктура
| Компонент | Требования |
|---|---|
| EU сервер | Ubuntu 24.04, публичный IP, домен с A-записью → IP сервера |
| RU сервер | Ubuntu 24.04, публичный IP, домен с A-записью → IP сервера |
| 3x-ui на EU сервере | Уже установлен и работает; настроен outbound WARP |
| Docker | Установлен на обоих серверах |
| Порт 2053 UDP | Должен быть свободен на обоих серверах |
| TLS-сертификат | Получается через Caddy (caddy-naive), либо через certbot — описано в разделе ниже |
DNS-записи (создать заранее, до установки)
Let's Encrypt проверяет доступность домена по HTTP (порт 80) перед выдачей сертификата. DNS-записи должны быть созданы и распространены (propagated) до первого запуска Caddy.
| Запись | Тип | Значение | Примечание |
|---|---|---|---|
your-hy2-ru-domain.example.com |
A | IP RU сервера | Используется Hysteria2 сервером на RU |
your-hy2-eu-domain.example.com |
A | IP EU сервера | Используется Hysteria2 сервером на EU |
Примечание: если NaïveProxy (Caddy) уже настроен на тех же серверах с теми же доменами, отдельные DNS-записи для Hysteria2 не нужны — используется тот же домен и тот же сертификат.
Используемые заглушки
В данном руководстве применяются следующие заглушки. Замени их реальными значениями перед использованием:
| Заглушка | Описание |
|---|---|
YOUR_RU_SERVER_IP |
Публичный IP RU сервера |
YOUR_EU_SERVER_IP |
Публичный IP EU сервера |
your-hy2-ru-domain.example.com |
Домен для RU сервера (для TLS-сертификата) |
your-hy2-eu-domain.example.com |
Домен для EU сервера (для TLS-сертификата) |
YOUR_EMAIL@example.com |
Email для Let's Encrypt (уведомления об истечении сертификата) |
EU_HY2_PASSWORD |
Пароль соединения RU клиент → EU сервер (генерируется один раз) |
Для генерации пароля EU_HY2_PASSWORD используй:
openssl rand -base64 18
Сохрани результат — он понадобится в конфигах обоих серверов.
Часть 1: EU сервер (Exit Node)
Цель: развернуть Hysteria2 сервер в Docker, который принимает соединения от RU клиента и перенаправляет трафик через 3x-ui → Cloudflare WARP.
Все команды в этом разделе выполняются на EU сервере под root.
1.1 Установка Docker
Если Docker уже установлен (проверить: docker --version) — пропусти этот шаг.
# Обновить пакеты
apt-get update && apt-get upgrade -y
# Установить зависимости
apt-get install -y ca-certificates curl gnupg lsb-release
# Добавить GPG-ключ Docker
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
-o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
# Добавить репозиторий Docker
echo "deb [arch=$(dpkg --print-architecture) \
signed-by=/etc/apt/keyrings/docker.asc] \
https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
> /etc/apt/sources.list.d/docker.list
# Установить Docker Engine
apt-get update
apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
# Включить автозапуск
systemctl enable docker
systemctl start docker
# Проверить
docker --version
docker compose version
Ожидаемый вывод:
Docker version 27.x.x, build ... Docker Compose version v2.x.x
1.2 Настройка 3x-ui: создание inbound hy2-out → WARP
Hysteria2 Exit Node направляет трафик через 3x-ui SOCKS5 inbound, который в свою очередь маршрутизирует его в Cloudflare WARP. Этот шаг выполняется в веб-интерфейсе 3x-ui до запуска Hysteria2.
Условие: в 3x-ui должен быть настроен outbound WARP (WireGuard/gVisor).
1.2.1 Создать новый inbound в 3x-ui
В веб-интерфейсе 3x-ui (EU сервер) перейди в Inbounds → Add Inbound и заполни следующие поля:
| Параметр | Значение | Пояснение |
|---|---|---|
| Примечание (Remark) | hy2-out |
Произвольное имя для идентификации в правилах маршрутизации |
| Протокол | mixed |
Поддерживает SOCKS5 и HTTP одновременно |
| Listen IP | 127.0.0.1 |
Только localhost — недоступен извне |
| Порт | 24364 |
grep 24364 |
| Аутентификация | Выключена | Hysteria2 сам отвечает за авторизацию клиентов |
| UDP | Включён | Требуется для DNS-запросов через туннель |
Нажми Save — inbound немедленно активируется.
1.2.2 Создать правило маршрутизации
В 3x-ui перейди в Routing → Add Rule (или Settings → Routing Rules):
| Параметр | Значение |
|---|---|
| Inbound Tag | hy2-out (тег созданного выше inbound)
|
| Outbound Tag | warp (или название твоего WARP outbound в 3x-ui)
|
Сохрани и перезапусти Xray внутри 3x-ui.
1.2.3 Проверить, что порт 24364 слушает
ss -tlnp | grep 24364
Ожидаемый вывод:
LISTEN 0 128 127.0.0.1:24364 0.0.0.0:* users:(("xray",...))
1.3 TLS-сертификат для Hysteria2
Hysteria2 требует действующий TLS-сертификат (самоподписанный не рекомендуется — клиентам потребуется флаг insecure: true). В нашей схеме используется Let's Encrypt сертификат, получаемый и автоматически обновляемый через Caddy (caddy-naive стек).
Caddy хранит сертификаты в именованном Docker volume caddy-naive_caddy_data, и Hysteria2 подключает их через bind mount (read-only) — никаких лишних сервисов, никакого дублирования.
Вариант А: Caddy (caddy-naive) уже установлен — рекомендуется
Если NaïveProxy (caddy-naive) уже настроен и работает, сертификат уже получен. Проверь:
# Найти путь к сертификату в Docker volume
DOMAIN="your-hy2-eu-domain.example.com"
CERT_DIR="/var/lib/docker/volumes/caddy-naive_caddy_data/_data/caddy/certificates/acme-v02.api.letsencrypt.org-directory/${DOMAIN}"
ls -la "${CERT_DIR}/"
Ожидаемый вывод — два файла:
your-hy2-eu-domain.example.com.crt your-hy2-eu-domain.example.com.key
Если файлы есть — переходи к разделу 1.4. Если volume или папка не существуют — сначала установи caddy-naive (NaïveProxy) или воспользуйся Вариантом Б.
Вариант Б: получить сертификат через certbot (если Caddy не установлен)
Этот вариант используется, если caddy-naive не установлен и на серверах нет другого HTTP-сервера на портах 80/443.
# Установить certbot
apt-get install -y certbot
# Получить сертификат (standalone — Certbot запустит временный HTTP-сервер на :80)
certbot certonly --standalone \
-d your-hy2-eu-domain.example.com \
--email YOUR_EMAIL@example.com \
--agree-tos --non-interactive
# Проверить
ls /etc/letsencrypt/live/your-hy2-eu-domain.example.com/
Сертификаты будут по пути /etc/letsencrypt/live/your-hy2-eu-domain.example.com/. В этом случае в docker-compose.yml (раздел 1.6) замени volume на:
- /etc/letsencrypt/live/your-hy2-eu-domain.example.com:/etc/hysteria/certs:ro
- /etc/letsencrypt/archive/your-hy2-eu-domain.example.com:/etc/letsencrypt/archive/your-hy2-eu-domain.example.com:ro
И в server.yaml пути к файлам:
tls:
cert: /etc/hysteria/certs/fullchain.pem
key: /etc/hysteria/certs/privkey.pem
1.4 Создание структуры проекта
mkdir -p /opt/hysteria-eu
cd /opt/hysteria-eu
Итоговая структура:
/opt/hysteria-eu/ ├── docker-compose.yml └── server.yaml
1.5 Конфигурация сервера: server.yaml
Создай файл конфигурации. Замени EU_HY2_PASSWORD сгенерированным паролем:
cat > /opt/hysteria-eu/server.yaml << 'EOF'
listen: :2053
tls:
cert: /etc/hysteria/certs/your-hy2-eu-domain.example.com.crt
key: /etc/hysteria/certs/your-hy2-eu-domain.example.com.key
auth:
type: password
password: EU_HY2_PASSWORD
ignoreClientBandwidth: true
masquerade:
type: proxy
proxy:
url: https://news.ycombinator.com/
rewriteHost: true
outbounds:
- name: warp
type: socks5
socks5:
addr: 127.0.0.1:24364
acl:
inline:
- "warp(all)"
EOF
Описание ключевых параметров:
| Параметр | Значение | Пояснение |
|---|---|---|
listen: :2053 |
UDP порт 2053 | Hysteria2 слушает на всех интерфейсах |
tls.cert / tls.key |
Пути к сертификату | Берутся из caddy-naive Docker volume (bind mount) |
auth.type: password |
Одиночный пароль | Для соединения RU клиент → EU сервер (клиентские устройства этот пароль не знают) |
ignoreClientBandwidth: true |
Игнорировать лимит клиента | Серверная сторона управляет congestion control самостоятельно |
masquerade.type: proxy |
Проксировать реальный сайт | Неавторизованные запросы получают реальный контент ycombinator.com — сервер выглядит как обычный HTTPS-сайт |
outbounds.warp |
SOCKS5 на 127.0.0.1:24364 | Исходящий трафик → 3x-ui inbound "hy2-out" → WARP |
acl: warp(all) |
Направить весь трафик в warp | Синтаксис: имя outbound в скобках, адрес/маска после |
1.6 Конфигурация Docker Compose: docker-compose.yml
cat > /opt/hysteria-eu/docker-compose.yml << 'EOF'
services:
hysteria-eu:
image: tobyxdd/hysteria:latest
container_name: hysteria-eu
restart: unless-stopped
network_mode: host
command: server --config /etc/hysteria/server.yaml
volumes:
- ./server.yaml:/etc/hysteria/server.yaml:ro
- /var/lib/docker/volumes/caddy-naive_caddy_data/_data/caddy/certificates/acme-v02.api.letsencrypt.org-directory/your-hy2-eu-domain.example.com:/etc/hysteria/certs:ro
EOF
Важные моменты:
network_mode: host— обязателен, чтобы контейнер видел127.0.0.1:24364(3x-ui inbound).:ro— сертификаты подключены только для чтения, Hysteria2 их не изменяет.- Путь к сертификату — bind mount из Docker volume caddy-naive. Если используется certbot (Вариант Б) — замени путь согласно инструкции раздела 1.3.
1.7 Первый запуск
cd /opt/hysteria-eu
# Скачать образ и запустить
docker compose pull
docker compose up -d
# Подождать 3 секунды и проверить логи
sleep 3 && docker compose logs
Ожидаемый вывод в логах:
INFO server mode
INFO server up and running {"listen": ":2053"}
Если в логах ошибка — см. раздел «Диагностика проблем» (Часть 5).
1.8 Проверка: порт и соединение
# Проверить, что Hysteria2 слушает на UDP :2053
ss -ulnp | grep ':2053'
Ожидаемый вывод:
UNCONN 0 0 0.0.0.0:2053 0.0.0.0:* users:(("hysteria",...))
# Статус контейнера
docker compose ps
NAME IMAGE STATUS hysteria-eu tobyxdd/hysteria:latest Up X minutes
Часть 2: RU сервер (Entry Node)
Цель: развернуть два Docker-контейнера:
hysteria-ru-server— принимает соединения от клиентов, аутентифицирует поusers.txt.hysteria-ru-client— подключается к EU серверу, слушает SOCKS5 на127.0.0.1:10810.
Между ними: hysteria-ru-server направляет трафик через SOCKS5 → hysteria-ru-client → EU.
Все команды в этом разделе выполняются на RU сервере под root.
2.1 Установка Docker
Аналогично EU серверу (раздел 1.1). Если Docker уже установлен — пропусти.
apt-get update && apt-get upgrade -y
apt-get install -y ca-certificates curl gnupg lsb-release
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
-o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) \
signed-by=/etc/apt/keyrings/docker.asc] \
https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
> /etc/apt/sources.list.d/docker.list
apt-get update
apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
systemctl enable docker && systemctl start docker
docker --version && docker compose version
2.2 TLS-сертификат для RU сервера
Аналогично EU серверу. На RU сервере также используется сертификат от Caddy (caddy-naive), если NaïveProxy настроен.
Вариант А: через Caddy (caddy-naive) — рекомендуется
DOMAIN="your-hy2-ru-domain.example.com"
CERT_DIR="/var/lib/docker/volumes/caddy-naive_caddy_data/_data/caddy/certificates/acme-v02.api.letsencrypt.org-directory/${DOMAIN}"
ls -la "${CERT_DIR}/"
Если файлы .crt и .key присутствуют — всё готово, переходи к разделу 2.3.
Вариант Б: через certbot (если Caddy не установлен)
apt-get install -y certbot
certbot certonly --standalone \
-d your-hy2-ru-domain.example.com \
--email YOUR_EMAIL@example.com \
--agree-tos --non-interactive
ls /etc/letsencrypt/live/your-hy2-ru-domain.example.com/
2.3 Создание структуры проекта
mkdir -p /opt/hysteria-ru/users
cd /opt/hysteria-ru
Итоговая структура:
/opt/hysteria-ru/
├── docker-compose.yml
├── server.yaml
├── client.yaml
└── users/
├── users.txt ← список пользователей (username password)
└── check-auth.sh ← скрипт авторизации, вызывается при каждом подключении
2.4 Система аутентификации пользователей
RU сервер использует auth.type: command — при каждом входящем подключении Hysteria2 вызывает внешний скрипт и передаёт ему пароль клиента в аргументах. Скрипт ищет пароль в файле users.txt: если совпадение найдено — выводит имя пользователя (оно попадает в логи) и завершается с кодом 0. Если нет — завершается с кодом 1 (отказ в подключении).
Преимущество этого подхода: users.txt можно редактировать в любой момент без перезапуска контейнера. Изменения применяются немедленно.
2.4.1 Файл пользователей: users/users.txt
Формат: одна строка на пользователя — имя пользователя и пароль, разделённые пробелом.
# Сгенерировать первого пользователя (пример: "default")
PASS=$(openssl rand -base64 18)
echo "default $PASS" > /opt/hysteria-ru/users/users.txt
echo "Пароль пользователя default: $PASS"
Формат файла:
username1 пароль1 username2 пароль2
Важно: не используй пробелы внутри пароля — пароль считывается по второму полю строки.
2.4.2 Скрипт авторизации: users/check-auth.sh
Hysteria2 вызывает скрипт с тремя аргументами: $1 — адрес клиента, $2 — пароль, $3 — tx bandwidth.
cat > /opt/hysteria-ru/users/check-auth.sh << 'EOF'
#!/bin/sh
AUTH_PAYLOAD="$2"
USERS_FILE="/etc/hysteria/users/users.txt"
USERNAME=$(awk -v pwd="$AUTH_PAYLOAD" '$2 == pwd {print $1; exit}' "$USERS_FILE" 2>/dev/null)
if [ -n "$USERNAME" ]; then
echo "$USERNAME"
exit 0
fi
exit 1
EOF
chmod +x /opt/hysteria-ru/users/check-auth.sh
Почему используется awk, а не grep:
- Hysteria2 использует Docker-образ на базе Alpine Linux с BusyBox.
- BusyBox
grepне поддерживает флаг-P(Perl-совместимые регулярки). awkдоступен во всех Unix/Linux окружениях и работает стабильно.
2.5 Конфигурация сервера: server.yaml
cat > /opt/hysteria-ru/server.yaml << 'EOF'
listen: :2053
tls:
cert: /etc/hysteria/certs/your-hy2-ru-domain.example.com.crt
key: /etc/hysteria/certs/your-hy2-ru-domain.example.com.key
auth:
type: command
command: /etc/hysteria/users/check-auth.sh
ignoreClientBandwidth: true
masquerade:
type: proxy
proxy:
url: https://news.ycombinator.com/
rewriteHost: true
outbounds:
- name: chain
type: socks5
socks5:
addr: 127.0.0.1:10810
acl:
inline:
- "chain(all)"
EOF
Описание ключевых параметров:
| Параметр | Значение | Пояснение |
|---|---|---|
auth.type: command |
Внешний скрипт | При каждом подключении вызывается check-auth.sh
|
auth.command |
Путь внутри контейнера | Скрипт смонтирован через volume: ./users:/etc/hysteria/users
|
outbounds.chain |
SOCKS5 на 127.0.0.1:10810 | Трафик → hysteria-ru-client → EU |
acl: chain(all) |
Весь трафик через chain | Синтаксис: имя outbound, затем адрес в скобках |
2.6 Конфигурация клиента: client.yaml
RU клиент подключается к EU серверу и поднимает SOCKS5 на 127.0.0.1:10810.
cat > /opt/hysteria-ru/client.yaml << 'EOF'
server: your-hy2-eu-domain.example.com:2053
auth: EU_HY2_PASSWORD
socks5:
listen: 127.0.0.1:10810
EOF
Замени EU_HY2_PASSWORD на тот же пароль, который указан в server.yaml EU сервера.
2.7 Конфигурация Docker Compose: docker-compose.yml
cat > /opt/hysteria-ru/docker-compose.yml << 'EOF'
services:
hysteria-ru-server:
image: tobyxdd/hysteria:latest
container_name: hysteria-ru-server
restart: unless-stopped
network_mode: host
command: server --config /etc/hysteria/server.yaml
volumes:
- ./server.yaml:/etc/hysteria/server.yaml:ro
- ./users:/etc/hysteria/users:ro
- /var/lib/docker/volumes/caddy-naive_caddy_data/_data/caddy/certificates/acme-v02.api.letsencrypt.org-directory/your-hy2-ru-domain.example.com:/etc/hysteria/certs:ro
hysteria-ru-client:
image: tobyxdd/hysteria:latest
container_name: hysteria-ru-client
restart: unless-stopped
network_mode: host
command: client --config /etc/hysteria/client.yaml
volumes:
- ./client.yaml:/etc/hysteria/client.yaml:ro
EOF
Критически важные моменты:
- Директория
./usersмонтируется в оба контейнера... нет, только вhysteria-ru-server. Клиентский контейнер видит только свойclient.yaml. - При изменении
docker-compose.yml(добавлении volume) нужен пересоздать контейнер:docker compose down && docker compose up -d. Простойrestartне применяет изменения volumes.
2.8 Первый запуск
cd /opt/hysteria-ru
# Скачать образ
docker compose pull
# Запустить оба контейнера
docker compose up -d
# Подождать и проверить логи
sleep 3 && docker compose logs
Ожидаемый вывод:
hysteria-ru-server | INFO server mode
hysteria-ru-server | INFO server up and running {"listen": ":2053"}
hysteria-ru-client | INFO client mode
hysteria-ru-client | INFO connected to server {"udpEnabled": true, "count": 1}
hysteria-ru-client | INFO SOCKS5 server listening {"addr": "127.0.0.1:10810"}
2.9 Проверка цепочки с RU сервера
Тест: запустить временный Hysteria2 клиент на RU сервере и проверить, что трафик выходит через WARP (IP Cloudflare):
# Создать временный конфиг тест-клиента
cat > /tmp/hy2-test.yaml << EOF
server: 127.0.0.1:2053
auth: $(awk 'NR==1{print $2}' /opt/hysteria-ru/users/users.txt)
tls:
insecure: true
sni: your-hy2-ru-domain.example.com
socks5:
listen: 127.0.0.1:11080
EOF
# Запустить тест-клиент
docker run -d --name hy2-test --network host \
-v /tmp/hy2-test.yaml:/etc/hysteria/client.yaml:ro \
tobyxdd/hysteria:latest client --config /etc/hysteria/client.yaml
# Ждать 3 секунды
sleep 3
# Проверить IP выхода — должен быть Cloudflare WARP IP
echo "IP через цепочку:"
curl -s --max-time 15 -x socks5h://127.0.0.1:11080 https://ifconfig.me
# Очистить
docker rm -f hy2-test
rm -f /tmp/hy2-test.yaml
Если IP вывода принадлежит Cloudflare (начинается с 2a09: для IPv6 или относится к диапазонам WARP) — цепочка RU → EU → WARP работает корректно.
Примечание: параметр insecure: true и sni нужны в тест-клиенте, потому что он подключается по IP (127.0.0.1), а сертификат выдан на домен. Реальные клиенты подключаются по домену и такие параметры не требуются.
Часть 3: Управление пользователями (hy2-users.sh)
3.1 Что это и зачем
Для управления пользователями Hysteria2 на RU сервере предназначен скрипт hy2-users.sh. Он предоставляет интерактивное меню с набором операций над файлом users.txt.
Скрипт решает следующие задачи:
| Операция | Описание |
|---|---|
| Список пользователей | Показывает всех пользователей из users.txt с маскированными паролями |
| Добавить пользователя | Запрашивает имя, автоматически генерирует криптостойкий пароль, сохраняет в users.txt, показывает готовый Hysteria2 URI |
| Удалить пользователя | Интерактивный выбор из списка, удаление строки из users.txt |
| Показать конфиг | Генерирует и показывает Hysteria2 URI для выбранного пользователя |
Ключевые особенности:
- Нет перезапуска контейнера. Hysteria2 с
auth.type: commandчитает файл при каждом подключении — изменения вusers.txtприменяются мгновенно. - Пароли генерируются автоматически через
openssl rand— случайные, криптостойкие. - URI формат соответствует стандарту Hysteria2 и принимается всеми клиентами: NekoBox, husi, Exclave, podkop.
3.2 Установка скрипта
Скрипт хранится в репозитории по пути scripts/hy2-users.sh. Установка на сервер:
# Скопировать скрипт на сервер
scp scripts/hy2-users.sh root@YOUR_RU_SERVER_IP:/usr/local/bin/hy2-users.sh
# Сделать исполняемым
ssh root@YOUR_RU_SERVER_IP "chmod +x /usr/local/bin/hy2-users.sh"
Или выполнить непосредственно на RU сервере (если скрипт уже там):
chmod +x /usr/local/bin/hy2-users.sh
Проверка переменных в начале скрипта. Открой скрипт и убедись, что следующие переменные указывают на правильные значения:
head -15 /usr/local/bin/hy2-users.sh
Должны быть:
USERS_FILE="/opt/hysteria-ru/users/users.txt"DOMAIN="your-hy2-ru-domain.example.com"PORT="2053"
Если домен указан неверно — исправь:
sed -i 's|DOMAIN=.*|DOMAIN="your-hy2-ru-domain.example.com"|' /usr/local/bin/hy2-users.sh
3.3 Использование
sudo bash /usr/local/bin/hy2-users.sh
Скрипт показывает меню:
┌─────────────────────────────────────────────┐ │ Hysteria2 — управление пользователями RU │ └─────────────────────────────────────────────┘ 1. Список пользователей 2. Добавить пользователя 3. Удалить пользователя 4. Показать конфиг клиента 0. Выход Выбери действие:
При добавлении нового пользователя скрипт выводит готовый URI:
hysteria2://СГЕНЕРИРОВАННЫЙ_ПАРОЛЬ@your-hy2-ru-domain.example.com:2053
Этот URI вставляется напрямую в клиентское приложение (см. Часть 4).
3.4 Исходный код скрипта
#!/bin/bash
# =============================================================
# hy2-users — управление пользователями Hysteria2 на RU сервере
# users.txt: /opt/hysteria-ru/users/users.txt
# Формат: username password (по одной строке)
# Перезапуск контейнера НЕ требуется — изменения применяются мгновенно
# =============================================================
USERS_FILE="/opt/hysteria-ru/users/users.txt"
DOMAIN="your-hy2-ru-domain.example.com"
PORT="2053"
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
BOLD='\033[1m'
NC='\033[0m'
# --- Вспомогательные функции ---
check_root() {
if [[ $EUID -ne 0 ]]; then
echo -e "${RED}Ошибка: запусти скрипт от root (sudo bash hy2-users.sh)${NC}"
exit 1
fi
}
check_users_file() {
if [[ ! -f "$USERS_FILE" ]]; then
echo -e "${RED}Ошибка: файл пользователей не найден: $USERS_FILE${NC}"
exit 1
fi
}
get_usernames() {
awk '{print $1}' "$USERS_FILE"
}
get_password() {
local user="$1"
awk -v u="$user" '$1 == u {print $2}' "$USERS_FILE"
}
press_enter() {
echo ""
read -rp "Нажми Enter для возврата в меню..."
}
print_config_for() {
local user="$1"
local pass="$2"
echo ""
echo -e "${BOLD}┌─────────────────────────────────────────────┐${NC}"
echo -e "${BOLD}│ Hysteria2 URI для клиента │${NC}"
echo -e "${BOLD}└─────────────────────────────────────────────┘${NC}"
echo ""
echo -e "${BOLD} hysteria2://${pass}@${DOMAIN}:${PORT}${NC}"
echo ""
echo -e "${CYAN}Для podkop (OpenWrt):${NC} вставить URI как hy2 outbound"
echo -e "${CYAN}Для мобильных клиентов:${NC} NekoBox, husi, Exclave — импорт URI"
echo -e "${CYAN}Пользователь:${NC} ${user}"
}
# --- Функции меню ---
list_users() {
echo ""
echo -e "${BOLD}${CYAN}=== Список пользователей ===${NC}"
mapfile -t users < <(get_usernames)
if [[ ${#users[@]} -eq 0 ]]; then
echo -e "${YELLOW}Пользователей нет.${NC}"
else
for i in "${!users[@]}"; do
echo -e " ${BOLD}$((i+1)).${NC} ${users[$i]}"
done
echo ""
echo -e " Всего: ${#users[@]} пользователь(ей)"
fi
press_enter
}
add_user() {
echo ""
echo -e "${BOLD}${CYAN}=== Добавить пользователя ===${NC}"
read -rp "Имя пользователя: " username
username=$(echo "$username" | tr -d ' ')
if [[ -z "$username" ]]; then
echo -e "${RED}Имя не может быть пустым.${NC}"
press_enter
return
fi
if grep -q "^${username} " "$USERS_FILE" 2>/dev/null; then
echo -e "${RED}Пользователь '${username}' уже существует.${NC}"
press_enter
return
fi
read -rp "Пароль (Enter = сгенерировать автоматически): " password
if [[ -z "$password" ]]; then
password=$(openssl rand -base64 18 | tr -d '=/+' | head -c 22)
echo -e " Сгенерирован пароль: ${BOLD}${password}${NC}"
fi
echo "${username} ${password}" >> "$USERS_FILE"
echo -e "${GREEN}Пользователь '${username}' добавлен.${NC}"
echo -e "${YELLOW}Изменения применяются мгновенно — перезапуск не нужен.${NC}"
print_config_for "$username" "$password"
press_enter
}
delete_user() {
echo ""
echo -e "${BOLD}${CYAN}=== Удалить пользователя ===${NC}"
mapfile -t users < <(get_usernames)
if [[ ${#users[@]} -eq 0 ]]; then
echo -e "${YELLOW}Нет пользователей для удаления.${NC}"
press_enter
return
fi
for i in "${!users[@]}"; do
echo -e " ${BOLD}$((i+1)).${NC} ${users[$i]}"
done
echo " 0. Отмена"
echo ""
read -rp "Выбери номер для удаления: " choice
if [[ "$choice" == "0" || -z "$choice" ]]; then
return
fi
if ! [[ "$choice" =~ ^[0-9]+$ ]] || (( choice < 1 || choice > ${#users[@]} )); then
echo -e "${RED}Неверный номер.${NC}"
press_enter
return
fi
target="${users[$((choice-1))]}"
read -rp "Удалить пользователя '${target}'? (y/N): " confirm
if [[ "$confirm" =~ ^[Yy]$ ]]; then
sed -i "/^${target} /d" "$USERS_FILE"
echo -e "${GREEN}Пользователь '${target}' удалён.${NC}"
echo -e "${YELLOW}Изменения применяются мгновенно — перезапуск не нужен.${NC}"
else
echo "Отменено."
fi
press_enter
}
show_config() {
echo ""
echo -e "${BOLD}${CYAN}=== URI клиента ===${NC}"
mapfile -t users < <(get_usernames)
if [[ ${#users[@]} -eq 0 ]]; then
echo -e "${YELLOW}Нет пользователей.${NC}"
press_enter
return
fi
for i in "${!users[@]}"; do
echo -e " ${BOLD}$((i+1)).${NC} ${users[$i]}"
done
echo " 0. Отмена"
echo ""
read -rp "Выбери номер пользователя: " choice
if [[ "$choice" == "0" || -z "$choice" ]]; then
return
fi
if ! [[ "$choice" =~ ^[0-9]+$ ]] || (( choice < 1 || choice > ${#users[@]} )); then
echo -e "${RED}Неверный номер.${NC}"
press_enter
return
fi
local user="${users[$((choice-1))]}"
local pass
pass=$(get_password "$user")
print_config_for "$user" "$pass"
press_enter
}
# --- Главное меню ---
main_menu() {
check_root
check_users_file
while true; do
clear
echo -e "${BOLD}${CYAN}"
echo "╔══════════════════════════════════════╗"
echo "║ Hysteria2 User Manager (RU) ║"
echo "║ Домен: ${DOMAIN} ║"
echo "╚══════════════════════════════════════╝"
echo -e "${NC}"
mapfile -t users < <(get_usernames)
echo -e " Активных пользователей: ${BOLD}${#users[@]}${NC}"
echo ""
echo -e " ${BOLD}1.${NC} Список пользователей"
echo -e " ${BOLD}2.${NC} Добавить пользователя"
echo -e " ${BOLD}3.${NC} Удалить пользователя"
echo -e " ${BOLD}4.${NC} Показать URI клиента"
echo -e " ${BOLD}0.${NC} Выход"
echo ""
read -rp "Выбор: " option
case "$option" in
1) list_users ;;
2) add_user ;;
3) delete_user ;;
4) show_config ;;
0) echo "Выход."; exit 0 ;;
*) echo -e "${RED}Неверный выбор.${NC}"; sleep 1 ;;
esac
done
}
main_menu
Часть 4: Настройка клиентов
4.1 Формат URI Hysteria2
Hysteria2 использует стандартизированный URI для передачи параметров подключения клиентам:
hysteria2://ПАРОЛЬ@ДОМЕН:ПОРТ
Пример:
hysteria2://aBcDeFgHiJkLmNoPqRsT@your-hy2-ru-domain.example.com:2053
URI можно получить через скрипт hy2-users.sh (пункт 4 меню — «Показать конфиг клиента»).
4.2 Мобильные клиенты (iOS / Android)
Рекомендуемые приложения с нативной поддержкой Hysteria2:
| Приложение | Платформа | Как импортировать URI |
|---|---|---|
| NekoBox | Android | Главный экран → + → Add → Hysteria2 → вставить URI |
| husi | Android | Профили → + → Hysteria2 → вставить URI |
| Exclave | iOS | Конфигурации → + → Hysteria2 → вставить URI |
| Shadowrocket | iOS | Главная → + → Тип: Hysteria2 → вставить URI |
Все эти приложения принимают URI в формате hysteria2://... напрямую — через кнопку импорта, QR-код или буфер обмена.
4.3 ПК-клиенты (Windows / macOS / Linux)
Вариант А: официальный бинарь Hysteria2
Скачать бинарь с официального репозитория: https://github.com/apernet/hysteria/releases
Создать файл конфигурации config.yaml:
server: your-hy2-ru-domain.example.com:2053
auth: ВАШ_ПАРОЛЬ
socks5:
listen: 127.0.0.1:1080
http:
listen: 127.0.0.1:8080
Запустить:
# Linux / macOS
./hysteria client --config config.yaml
# Windows (PowerShell)
.\hysteria.exe client --config config.yaml
После запуска:
- SOCKS5 прокси доступен на
127.0.0.1:1080 - HTTP прокси доступен на
127.0.0.1:8080
Вариант Б: через NekoRay / Hiddify Next (Windows / Linux)
Приложения NekoRay и Hiddify Next принимают Hysteria2 URI — импорт через меню или буфер обмена. Автоматически управляют запуском клиентского процесса.
4.4 OpenWrt / podkop
podkop (реализация на базе sing-box) нативно поддерживает Hysteria2 URI. Никакой дополнительной установки бинарей не требуется.
В интерфейсе podkop (LuCI → Network → podkop → Outbound):
- Тип:
Hysteria2 - URI:
hysteria2://ПАРОЛЬ@your-hy2-ru-domain.example.com:2053
Или через UCI:
uci set podkop.main.proxy='hysteria2://ПАРОЛЬ@your-hy2-ru-domain.example.com:2053'
uci commit podkop
/etc/init.d/podkop restart
После сохранения все устройства домашней сети автоматически получают обход без дополнительной настройки.
Часть 5: Проверка всей цепочки
5.1 Тест RU сервера: цепочка RU → EU → WARP
Выполнить на RU сервере — проверяет, что трафик выходит через WARP:
# SOCKS5 порт 10810 — это точка выхода RU клиента (= вход в EU сервер → WARP)
curl -s --max-time 20 -x socks5h://127.0.0.1:10810 https://ifconfig.me
echo ""
Результат должен быть IP Cloudflare WARP — например, начинается с 2a09: (IPv6) или принадлежит AS13335 (Cloudflare).
Проверить AS:
IP=$(curl -s --max-time 20 -x socks5h://127.0.0.1:10810 https://ifconfig.me)
curl -s "https://ipinfo.io/${IP}/org"
echo ""
Ожидаемый вывод: строка содержит Cloudflare.
5.2 Тест с реального клиента
После добавления пользователя через hy2-users.sh и получения URI:
- Вставь URI в клиентское приложение (NekoBox, husi, Shadowrocket).
- Активируй профиль.
- Открой
https://ifconfig.meв браузере — должен отображаться IP Cloudflare WARP. - Открой
https://www.whatismybrowser.com/detect/what-is-my-ip-addressдля проверки отсутствия WebRTC утечки.
5.3 Диагностика проблем
Проблема: hysteria-eu не запускается — ошибка TLS
docker compose -f /opt/hysteria-eu/docker-compose.yml logs hysteria-eu
Возможные причины:
- Путь к сертификату неверен → проверь
lsпо пути, указанному в volumes. - Сертификат ещё не был получен Caddy → проверь, что caddy-naive запущен и прошёл ACME-проверку.
- Caddy хранит сертификат под другим именем → выполни
find /var/lib/docker/volumes/caddy-naive_caddy_data/_data -name "*.crt".
Проблема: hysteria-ru-server выдаёт auth error
docker compose -f /opt/hysteria-ru/docker-compose.yml logs hysteria-ru-server
Проверить вручную (на RU сервере):
# Проверить, что скрипт находится на месте внутри контейнера
docker exec hysteria-ru-server ls -la /etc/hysteria/users/
# Запустить скрипт вручную с тестовым паролем
docker exec hysteria-ru-server /etc/hysteria/users/check-auth.sh addr ТЕСТОВЫЙ_ПАРОЛЬ 0
echo "Exit code: $?"
Если Exit code: 0 — скрипт работает. Если 1 — пароль не найден в users.txt.
Проблема: hysteria-ru-client не подключается к EU
docker compose -f /opt/hysteria-ru/docker-compose.yml logs hysteria-ru-client
Возможные причины:
EU_HY2_PASSWORDвclient.yamlне совпадает с паролем в EUserver.yaml.- UDP порт 2053 заблокирован на EU сервере (firewall) → проверь
ss -ulnp | grep 2053на EU сервере. - EU сервер ещё не запущен.
Проблема: ACL ошибка при запуске
FATAL failed to load server config: invalid config: acl.inline: invalid syntax at line 1
Неверный синтаксис ACL. Правильный формат: "outbound_name(address)":
- ✅
"warp(all)"— направить всё в outbound "warp" - ✅
"chain(all)"— направить всё в outbound "chain" - ❌
"outbound(warp) all"— неверно
Часть 6: Обслуживание
6.1 Управление контейнерами
# EU сервер
cd /opt/hysteria-eu
docker compose up -d # запустить
docker compose down # остановить
docker compose restart # перезапустить
docker compose logs -f # следить за логами в реальном времени
docker compose ps # статус
# RU сервер (оба контейнера)
cd /opt/hysteria-ru
docker compose up -d
docker compose down
docker compose restart
docker compose logs -f
# RU сервер (по отдельности)
docker compose restart hysteria-ru-server # только сервер
docker compose restart hysteria-ru-client # только клиент
docker compose logs -f hysteria-ru-server # логи только сервера
docker compose logs -f hysteria-ru-client # логи только клиента
6.2 Обновление сертификатов
TLS-сертификаты обновляются Caddy автоматически (за 30 дней до истечения). После автообновления Hysteria2 начнёт использовать новый сертификат при следующем перезапуске.
Для принудительного применения нового сертификата:
# EU сервер
cd /opt/hysteria-eu && docker compose restart
# RU сервер
cd /opt/hysteria-ru && docker compose restart hysteria-ru-server
Проверить дату истечения текущего сертификата:
# EU сервер
DOMAIN="your-hy2-eu-domain.example.com"
CERT="/var/lib/docker/volumes/caddy-naive_caddy_data/_data/caddy/certificates/acme-v02.api.letsencrypt.org-directory/${DOMAIN}/${DOMAIN}.crt"
openssl x509 -in "${CERT}" -noout -enddate
6.3 Обновление Docker-образа
Hysteria2 активно развивается. Для обновления до последней версии:
# EU сервер
cd /opt/hysteria-eu
docker compose pull # скачать новый образ
docker compose up -d # перезапустить с новым образом
docker compose logs --tail=5 # проверить старт
# RU сервер
cd /opt/hysteria-ru
docker compose pull
docker compose up -d
docker compose logs --tail=5
Старые неиспользуемые образы можно удалить:
docker image prune -f
6.4 Мониторинг логов
Hysteria2 пишет структурированные JSON-логи. Полезные паттерны для мониторинга:
# Смотреть все подключения на RU сервере
docker logs hysteria-ru-server 2>&1 | grep -E "connected|disconnected|auth"
# Смотреть ошибки
docker logs hysteria-ru-server 2>&1 | grep -i "error\|fatal\|warn"
# Считать количество уникальных пользователей (поле "id")
docker logs hysteria-ru-server 2>&1 | grep '"id"' | grep -oP '"id": "\K[^"]+' | sort -u
# Следить за логами EU сервера в реальном времени
docker logs -f hysteria-eu 2>&1
Пример нормального лога RU сервера при входящем подключении:
INFO client connected {"addr": "CLIENT_IP:PORT", "id": "username"}
INFO TCP request {"addr": "CLIENT_IP:PORT", "id": "username", "reqAddr": "example.com:443"}
INFO client disconnected {"addr": "CLIENT_IP:PORT", "id": "username", "error": "..."}
Поле "id" — это имя пользователя, возвращённое скриптом check-auth.sh. Это позволяет отслеживать активность конкретных пользователей в логах без хранения паролей.