Первый шаг: читаем лог ядра, а не клиента
Типовая ошибка диагностики — верить экрану клиента («нет соединения», «timeout») вместо лога Xray-core. Клиент показывает симптом; причину знает только ядро.
Включите подробный лог в config.json:
"log": {
"loglevel": "warning",
"access": "/var/log/xray/access.log",
"error": "/var/log/xray/error.log"
}
loglevel: debug ставьте только на время отладки — он шумный и пишет чувствительные данные. Где смотреть на ноде:
- systemd:
journalctl -u xray -f(живой поток) илиjournalctl -u xray -e --no-pager(хвост); - ручной запуск:
xray run -c /usr/local/etc/xray/config.json— ядро сразу печатает, на какой строке упало и почему; - Docker (Remnawave/Marzban):
docker logs -f <container>илиdocker logs --tail 100 <container>.
Развилка, которая экономит время: если xray run -c не поднялся вообще — проблема в конфиге ядра (раздел ниже). Если ядро стартовало и слушает порт, а клиент ловит 400/404/unexpected status — проблема в стыковке инбаунда с клиентом или во фронте, а сам Xray исправен.
Конфиг не стартует: битый JSON и типовые ошибки структуры
Если xray run завершается мгновенно с Failed to read config:... или invalid character... after... — это ошибка Xray core на этапе парсинга, до сети ядро не дошло.
Проверка перед перезапуском:
xray run -test -c /usr/local/etc/xray/config.json # валидирует конфиг без запуска
jq. /usr/local/etc/xray/config.json > /dev/null # jq укажет строку с ошибкой JSON
Частые причины, почему Xray не работает на этапе конфига:
- Хвостовая запятая после последнего элемента массива/объекта — строгий JSON её не допускает (в отличие от JSON5).
- **Комментарии
//или/* */** — Xray читает конфиг строгим JSON-ридером, комментарии ломают парсинг. - Порт занят:
failed to listen... bind: address already in use. Найдите держателя порта:ss -tlnp | grep:443. Обычно это второй экземпляр Xray, nginx или не убитый старый контейнер. - Дубликат
tagу инбаундов/аутбаундов — теги обязаны быть уникальны, иначе роутинг не соберётся. no inbound/outbound found— пустой или неверно вложенный массив.
На проде правьте так: cp config.json config.json.bak → правка → xray run -test -c config.json → только при OK systemctl restart xray. Откат в одну строку: cp config.json.bak config.json && systemctl restart xray. Держите эту команму под рукой до перезапуска, а не после.
400/404 на инбаунде: рассинхрон path, Host и UUID
Ядро поднялось, туннель не идёт, в access.log или на стороне CDN — 400 Bad Request / 404 Not Found. Это почти всегда несовпадение параметров инбаунда и того, что реально шлёт клиент. Для VLESS поверх WS/XHTTP/gRPC совпадение критично до символа.
Сверьте построчно инбаунд на ноде и подписку в клиенте:
path—/api≠/api/≠/API. Слэш и регистр значимы. Для gRPC сверяйтеserviceName.Host/ SNI — если перед нодой стоит nginx или CDN, он маршрутизирует поHost. Неверный host → 404 от фронта, ядро запрос вообще не увидит.- UUID (
id) — лишний пробел при копировании или старый UUID после ротации ключей рвут соединение уже после рукопожатия. network/security—wsна ноде противxhttpв клиенте несовместимы; транспорт должен совпадать с обеих сторон.
Локализация «кто отдал 400/404»: постучитесь напрямую в порт ядра в обход фронта. Например, привяжите домен к локальному апстриму и проверьте:
curl -vk --resolve ваш-домен:443:127.0.0.1 https://ваш-домен/ваш-path
Если напрямую в порт ядра ответ идёт, а через публичный адрес — 404, виноват реверс-прокси или CDN. Для WebSocket в nginx обязательны proxy_set_header Upgrade $http_upgrade; и proxy_set_header Connection "upgrade"; — без них upgrade не проходит и приходит 400. Тонкости фронтинга Remnawave/панелей за CDN и специфику XHTTP (mode: packet-up, когда CDN отдаёт GET вместо POST) мы разбирали в отдельных материалах — здесь важно лишь: если параметры и заголовки совпали, а 404 остаётся, копайте во фронт, а не в config.json.
Несовпадение версий клиента и ноды: unsupported и молчаливые обрывы
Xray-core развивается быстро: XHTTP, обновления Reality, flow вроде xtls-rprx-vision появлялись в конкретных версиях. Если на ноде свежее ядро, а в клиенте старое (или наоборот) — получаете unknown flow, unsupported, rejected или просто обрыв без внятной строки в логе.
Сверьте версии:
xray version # на ноде
docker exec <ctr> xray version # в контейнере
В клиентах (v2rayN, v2rayNG, Happ, NekoBox, Streisand) версия встроенного ядра видна в «О программе»/настройках core. По наблюдениям у части клиентов ядро отстаёт от последнего релиза на несколько версий, и это нормально.
Типовые проявления рассинхрона:
unknown flow: xtls-rprx-vision— клиентское ядро старее ноды. Обновите приложение или временно уберитеflowв подписке.- XHTTP не подключается у части клиентов — приложение собрано на ядре без поддержки xhttp. Лечится обновлением клиента, а не правкой ноды.
- Reality не проходит — расхождение по
shortId/publicKeyлибо клиент не умеет актуальную версию Reality.
Эксплуатационное правило: держите на ноде стабильную, не bleeding-edge версию, и зафиксируйте в поддержке минимально поддерживаемую версию клиента. Не обновляйте Xray-core на проде «вслепую» в пятницу — сначала прогоните тестовую подписку на 2–3 популярных клиентах и убедитесь, что подключение и flow живы.
unexpected status и TLS/Reality handshake
unexpected status 400/403/502 в логе клиента при живом ядре — это ответ промежуточного узла (CDN, балансировщик, nginx), а не Xray. Ядро ждёт свой протокол внутри TLS, а получает HTML-страницу ошибки или редирект.
Разбор по коду:
- 403 — CDN/WAF отбил запрос (гео, rate-limit, не тот
Host, режим «под атакой»). Проверьте, что домен реально проксируется и не включена защита с JS-челленджем. - 502/504 — фронт не достучался до апстрима: нода лежит, неверный upstream-порт или режет firewall.
curl -I https://ваш-домен/pathпокажет, кто отвечает. - 400 — чаще всего не прошёл WebSocket-upgrade через прокси (см. раздел про 400/404).
TLS/Reality-специфика:
tls: handshake failure/certificate is not valid— истёк или не тот сертификат, SNI не совпадает с сертификатом. Проверка цепочки:openssl s_client -connect host:443 -servername ваш-sni </dev/null 2>/dev/null | openssl x509 -noout -dates.REALITY: processed invalid connection—dest/serverNamesв инбаунде не соответствуют реальному сайту-маскировке, либоpublicKeyв клиенте не от текущегоprivateKey.
Операторский момент, который часто принимают за ошибку ядра: когда unexpected status и обрывы приходят массово и внезапно у всех, а конфиг вы не трогали — это, как правило, блокировка самой ноды, а не Xray. При белых списках сервер в дата-центре блокируется первым; ядро исправно, порт слушает, но трафик до него не доходит. Правкой config.json это не лечится. Устойчивость даёт фронт через whitelisted-CDN: клиент ходит на «белый» адрес CDN, а тот проксирует на ноду, убирая из-под удара конкретный IP дата-центра. Отличить один случай от другого просто: если хоть у кого-то из другой сети всё работает, а падает по региону — это блок канала до ноды, а не ошибка Xray-core.
Чек-лист диагностики: от симптома к причине
Короткий алгоритм, чтобы не гадать, почему Xray не работает:
xray run -test -c config.json— конфиг валиден? Нет → чините JSON/порт/дубликат тегов.- Ядро стартовало?
systemctl status xrayиjournalctl -u xray -f. Естьstarted/listening— идём дальше. curl -I https://домен/pathснаружи — кто отвечает? 403/404/50x от фронта → рассинхрон path/Host или блок CDN.- Прямой стук в порт ядра (в обход CDN, через
--resolve) работает? Да → проблема в реверс-прокси/CDN, а не в Xray. - Версии ядра ноды и клиента бьются по фичам (
flow, XHTTP, Reality)? Нет → обновить клиент. - Массовый внезапный обрыв у всех без ваших изменений и с рабочим ядром → почти наверняка блокировка IP ноды, а не ошибка ядра.
Перед любой правкой на проде: cp config.json config.json.bak, затем -test, и держите наготове строку отката. Диагностику всегда ведите от лога Xray-core — он единственный честный источник о том, что реально происходит внутри ядра; экран клиента лишь пересказывает симптом.