Поднял сервер, раздал людям ключи — и началось. Один говорит «не работает», другой на том же провайдере, в том же городе — подключается нормально. Третий работает дома, но не с телефона. Знакомо?

Я разбирался с этим достаточно долго. В статье — конкретная схема, которая у меня работает, с объяснением, почему именно так, и с граблями, на которые я наступил.

Статья не для тех, кто только начинает. Предполагается, что 3x‑ui уже стоит, пользователи есть, и хочется, чтобы работало стабильно, а не «вроде ок».

Почему у двух людей на одном провайдере разный результат

ТСПУ давно работает не по сигнатурам, а по поведению трафика. Блокировка сейчас выглядит не как «сайт недоступен» — выглядит как деградация скорости до 200–300 Кбит/с. Пользователь думает, что сервер лёг. На самом деле, сессию просто зашейпили.

Почему разный результат у разных людей — потому что шейпинг разворачивается неравномерно по узлам сети оператора. Одна точка присутствия провайдера уже получила обновление правил, другая — ещё нет. Один тарифный план идёт через один пул оборудования, другой — через другой. Отсюда и расхождения.

Практический вывод один: не надо искать один идеальный транспорт. Надо держать несколько инбаундов, чтобы клиент мог переключиться.

Схема которую я использую

Клиент
  │
  ├ VLESS + REALITY (порт 8443) ─ ► Xray напрямую
  │                              (nginx не участвует)
  │
  └ HTTPS 443 ─ ► nginx
         │
         ├─ /ws-[случайный суффикс]  ─ ► Xray WS    (порт 8002, 127.0.0.1)
         └─ /xh-[случайный суффикс]  ─ ► Xray xHTTP (порт 8001, 127.0.0.1)

Три инбаунда — три разных сценария.

REALITY — TLS клиент видит как реальный трастовый сайт. Максимальная маскировка, DPI технически не может отличить от легитимного трафика. Но чувствителен к конфигурации: неправильный dest, несовпадение fingerprint — и блокируется быстрее остальных. Требует чистого IP без плохой репутации.

WebSocket — оставил для совместимости. Часть клиентских приложений старых версий работает только с ним, плюс некоторые iOS‑конфигурации.

xHTTP — основной рабочий транспорт. Это не SplitHTTP, путаница частая — разные транспорты. xHTTP работает поверх HTTP/2, создаёт внутри него мультиплексированные подпотоки. DPI видит обычный HTTPS к вашему домену.

Оба WS и xHTTP слушают на 127.0.0.1, наружу не торчат — снаружи только nginx.

nginx

Два момента, которые я выяснил опытным путём.

Для xHTTP обязательно нужны proxy_buffering off и proxy_read_timeout 5d. Без первого nginx буферизует тело запроса — xHTTP это не переживает, соединение рвётся. Без второго nginx закрывает соединение по таймауту раньше, чем надо.

location / возвращает 404 — чтобы сканеры не видели ничего полезного на корне.

server {
    listen 80;
    server_name your.domain.example;
    return 301 https://$host$request\_uri;
}

server {
    listen 443 ssl http2;
    server_name your.domain.example;

    ssl_certificate /path/to/fullchain.pem;
    ssl_certificate_key /path/to/privkey.pem;

    location /ws-ваш-путь {
        proxy_pass http://127.0.0.1:8002;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    location /xh-ваш-путь {
        proxy_pass http://127.0.0.1:8001;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_buffering off;
        proxy_read_timeout 5d;
    }

    location / {
        return 404;
    }
}

Грабли с xHTTP и flow

Это стоило мне нескольких часов. В 3x‑ui при создании xHTTP‑инбаунда поле flow надо оставить пустым.

xtls-rprx-vision (Vision Flow) требует прямого TLS‑соединения клиента с Xray. В нашей схеме TLS заканчивается на nginx, дальше идёт plain HTTP до Xray на 127.0.0.1. Vision в такой схеме не работает.

Симптом коварный: клиент подключается, nginx показывает 200 OK в логах, трафик идёт, но соединение обрывается через 10–15 секунд. Убираешь flow — всё работает.

Параметры xHTTP‑инбаунда в 3x‑ui:

Параметр

Значение

Протокол

VLESS

Listen

127.0.0.1

Network

xhttp

Mode

packet‑up

Security

none

Flow

пусто

packet-up выбрал потому, что он лучше работает в нестандартных условиях — CDN, провайдеры с частичной поддержкой HTTP/2. stream-up быстрее, но требует полноценного H2 на всём пути.

WARP в исходящих

ChatGPT и часть других сервисов режут IP дата‑центров на уровне сети — смена протокола тут не поможет никак, проблема не в протоколе.

В 3x‑ui это решается прямо из панели: в разделе «Исходящие» есть тип WARP. Никакого wgcf, никакой ручной настройки WireGuard — создаётся в несколько кликов. WARP даёт IP близкий к геолокации VPS, для большинства сервисов этого хватает.

После создания исходящего WARP в разделе «Маршрутизация» добавьте правило:

Параметр

Значение

Domain

geosite:openai и всё что нужно

Outbound

warp

Важный момент с DNS: убедитесь что DNS‑запросы к этим доменам тоже идут через WARP. Иначе резолвер хостера отдаёт ваш дата‑центровый адрес ещё до установки соединения.

Маршрутизация на сервере

Российский трафик лучше резать на клиенте (geoip:ru → direct) — меньше задержка, меньше нагрузка на сервер. На сервере остаются инфраструктурные правила.

Минимум, который должен быть:

Что

Куда

Зачем

api‑тег

api outbound

Системное правило 3x‑ui, не трогать.

geoip:private

blocked

Блокируем попытки достучаться до внутренней сети через туннель.

bittorrent

blocked

Торренты портят репутацию IP.

geosite:openai и др.

warp

Сервисы, блокирующие дата‑центры.

остальное

direct

Обычный выход.

Чтобы правила geosite:* работали, Xray должен знать имя домена, а не только IP. Включайте Sniffing на инбаундах — Xray достанет SNI из пакета сам. Небольшая нагрузка на CPU, но на практике незаметна.

BBR

Сразу после базовой настройки включите BBR — алгоритм управления перегрузкой TCP. Скорость становится стабильнее, меньше просадок под нагрузкой. Особенно заметно на xHTTP.

echo "net.core.default_qdisc=fq" >> /etc/sysctl.conf
echo "net.ipv4.tcp_congestion_control=bbr" >> /etc/sysctl.conf
sysctl -p

Проверить что применилось: sysctl net.ipv4.tcp_congestion_control должно вернуть bbr.

Мониторинг

ICMP‑пинг и TCP‑проверка порта 443 бесполезны. ТСПУ спокойно пропускает SYN‑пакеты — в мониторинге всё зелёное, а TLS handshake тихо дропается на эвристике. Пользователи уже не могут подключиться, а у вас все индикаторы ОК.

Нужно проверять реальное прохождение трафика — через сам Xray до внешнего ресурса.

На слабых серверах проще всего — cron каждые 5 минут:

#!/bin/bash
# Замените порт на ваш локальный SOCKS или HTTP прокси из 3x-ui

RESULT=$(curl -s -m 10 \
    --proxy socks5://127.0.0.1:10809 \
    -o /dev/null \
    -w "%{http_code}" \
    https://www.google.com 2>/dev/null)

if [ "$RESULT" != "200" ]; then
    echo "$(date): туннель не отвечает, код: $RESULT" >> /var/log/xray-check.log
    # сюда можно добавить curl к Telegram webhook
fi

Если сервер потянет — Uptime Kuma умеет пускать проверки через SOCKS5/HTTP прокси и шлёт в Telegram при деградации. Удобнее cron‑скрипта.

xray stats API для постоянного мониторинга на слабом VPS не стоит использовать — сбор метрик по многим клиентам в реальном времени сам создаёт нагрузку.

Безопасность панели

Панель 3x‑ui не должна быть доступна снаружи напрямую. Панель на 127.0.0.1, наружу — nginx с отдельным location. Пути панели и пути туннелей не должны пересекаться.

fail2ban ставится автоматически при установке через официальный скрипт.

x-ui.db — SQLite‑файл со всеми ключами и конфигами. Бэкап должен лежать не на этом же сервере.

Max IPs на пользователя — рекомендую 3. Мобильный клиент постоянно переключается между LTE и Wi‑Fi, при лимите 1–2 получаются ложные блокировки.

Обновление ядра без риска

Кнопка обновления в панели — это лотерея. Если в новом релизе сломали совместимость конфигов, сервис не поднимется и все пользователи отвалятся.

Скрипт с проверкой конфига и автооткатом:

#!/bin/bash
# Запускать от root.
# Новое ядро должно лежать в /tmp/xray-new.

XRAY_BIN="/usr/local/x-ui/bin/xray-linux-amd64"
TIMESTAMP=$(date +%Y%m%d_%H%M)
BACKUP="$XRAY_BIN.bak.$TIMESTAMP"

echo "[1/4] Проверяем конфиг новым бинарником..."
if ! /tmp/xray-new -test -config /usr/local/x-ui/bin/config.json; then
    echo "Конфиг не прошёл проверку. Ничего не меняем."
    exit 1
fi

echo "[2/4] Бэкап текущего бинарника → $BACKUP"
cp "$XRAY_BIN" "$BACKUP"

# Оставляем последние 3 бэкапа
ls -t "$XRAY_BIN".bak.* 2>/dev/null | tail -n +4 | xargs -r rm -f

echo "[3/4] Заменяем бинарник..."
chmod +x /tmp/xray-new
cp /tmp/xray-new "$XRAY_BIN"

echo "[4/4] Перезапуск..."
pkill -f "xray-linux-amd64" 2>/dev/null || true
sleep 2
systemctl restart x-ui

sleep 10
if ! pgrep -f "xray-linux-amd64" > /dev/null; then
    echo "$(date): не запустился, откат" >> /var/log/xray-update.log
    cp "$BACKUP" "$XRAY_BIN"
    systemctl restart x-ui
    echo "Откат выполнен."
    exit 1
fi

echo "Готово."

Версии на которых проверено: 3x‑ui 3.6.0, Xray‑core v26.7.28, nginx 1.26.

Клиентские приложения

Из того что пробовал — лучше всего работают Happ и v2rayTUN. Хватает ссылки на подписку или ключа, без ручных настроек. Остальные либо не обновляются, либо требуют возни с каждым инбаундом отдельно.

Один момент: один и тот же конфиг не всегда работает одинаково у всех пользователей. Это не значит, что что‑то сломано — разные клиентские приложения, разные провайдеры, разные тарифы. Иногда помогает переключить транспорт в подписке.

VPS: на что смотреть

Из локаций, которые проверял — Польша стабильнее Нидерландов. У нидерландских хостингов периодически бывают конфликты с провайдерами на стороне хоста. Германия тоже нормальная.

Критерии при выборе:

  • возможность смены IP если попадёт в блэклисты;

  • без ограничений и лимитов;

  • чистая репутация IP хостера или реселлера.

Если что‑то из описанного не работает или работает, но по‑другому: пишите в комментарии, интересно сравнить.