Блог Clearway

Настройка Xray Reality пошагово: от xray x25519 до проверки активным пробингом

Коротко

Настройка Xray Reality — это пять шагов. Поставить Xray-core (актуальна ветка 25.x), сгенерировать три секрета (xray x25519, xray uuid, openssl rand -hex 8), проверить SNI-донора командой openssl s_client (нужны TLS 1.3, Server Temp Key: X25519, ALPN h2), собрать inbound с security: reality и зеркально повторить восемь полей в клиенте, а затем проверить результат активным пробингом с посторонней машины: curl --resolve на ваш IP должен вернуть ответ и сертификат настоящего донора. Большинство отказов xray vless reality — это рассинхрон четырёх полей (sni, pbk, sid, flow) между сервером и клиентом либо донор, переставший отдавать TLS 1.3 на X25519. Граница: Reality прячет факт VPN, но не меняет IP ноды — если адрес режут по IP, помогает только фронт на «белом» адресе.

Шаг 0. Пре-флайт: ядро, часы, порт 443

Чистый VPS, root, свободный 443/tcp. Ставим официальным скриптом XTLS:

bash -c "$(curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh)" @ install
xray version

Reality есть в Xray-core с 1.8.0, но ориентируйтесь на ветку 25.x: в старых ядрах отличается вывод генератора ключей и часть имён полей. Что появилось после установки:

ПутьЧто это
/usr/local/bin/xrayбинарь ядра
/usr/local/etc/xray/config.jsonосновной конфиг (его и правим)
/var/log/xray/access.log, error.logлоги
/etc/systemd/system/xray.serviceюнит, управление через systemctl

Три вещи до конфига — иначе потом ловите «плавающие» симптомы, которые не лечатся правкой JSON:

# 1. Время. В аутентификацию Reality входит временная метка;
# при заметном расхождении часов часть клиентов получает отказ.
timedatectl set-ntp true && timedatectl status | grep -E "synchronized|NTP"

# 2. Файрвол: наружу только SSH и 443.
ufw allow 22/tcp && ufw allow 443/tcp && ufw --force enable

# 3. BBR — по наблюдениям ровнее скорость на дальних плечах.
printf 'net.core.default_qdisc=fq\nnet.ipv4.tcp_congestion_control=bbr\n' >> /etc/sysctl.conf
sysctl -p && sysctl net.ipv4.tcp_congestion_control

Отдельно проверьте исходящий доступ с ноды на 443 наружу: Reality проксирует каждый неопознанный хендшейк на донора, и если egress режет хостер, вы получите работающий туннель у «своих» и висящее соединение у всех остальных — то есть провал теста на пробинг.

Если нода живёт под панелью (Remnawave, 3x-ui, Marzban), править config.json руками бессмысленно: панель генерирует тот же JSON и перезаписывает файл при рестарте. Поля ниже вводятся в её UI один в один; про сами панели у нас отдельные материалы, здесь — чистый Xray.

Шаг 1. Три секрета: xray x25519, UUID, shortId

Пара ключей X25519 генерируется самим ядром:

xray x25519

Вывод отличается между версиями, и это регулярный источник путаницы:

# Xray-core до 25.x:
Private key: iJ2h... -> в конфиг сервера, поле privateKey
Public key: 6Dpm... -> клиенту, поле publicKey / pbk

# Xray-core 25.x:
PrivateKey: iJ2h...
Password: 6Dpm... <- это и есть публичный ключ, идёт клиенту в pbk

Слово Password пугает новичков — это не пароль пользователя, а тот же публичный ключ. Оба значения — base64url длиной 43 символа; если в поле pbk оказалась строка другой длины, вы почти наверняка скопировали не ту строку. Публичный ключ выводится из приватного (xray x25519 -i <PRIVATE_KEY>), но имя флага между сборками менялось — сверьтесь с xray help x25519, а не с чужим гайдом.

В свежих сборках есть и постквантовый вариант (xray mlkem768). Он работает, но по наблюдениям мобильные клиенты подтягивают поддержку неравномерно, и «на ПК работает, в телефоне нет» получается на ровном месте. Для прода берите классический x25519.

Остальные два секрета:

xray uuid # UUID клиента
openssl rand -hex 8 # shortId: 16 hex-символов (8 байт), максимум для поля

Таблица соответствия сервер ↔ клиент — по ней потом чинится большинство ошибок:

Сервер, realitySettings / clientsКлиент (JSON / параметр ссылки)Формат
privateKeyнигде, никогда не отдаватьbase64url, 43 символа
публичный ключ (Public key / Password)publicKey / pbkbase64url, 43 символа
serverNames[]serverName / sniдомен донора
dest (target)host:443
shortIds[]shortId / sidhex, чётная длина, до 16 символов
clients[].ididUUID
clients[].flowflowxtls-rprx-vision
fingerprint / fpchrome

Сложите значения в файл, чтобы не выковыривать их потом из истории шелла:

cat > /root/reality.env <<'EOF'
UUID=00000000-0000-0000-0000-000000000000
PRIV=iJ2h...
PBK=6Dpm...
SID=a1b2c3d4e5f60718
SNI=www.samsung.com
EOF
chmod 600 /root/reality.env

Шаг 2. SNI-донор: процедура проверки за 10 секунд

Критерии выбора донора мы разбирали в материалах про Reality и SNI-маскировку, здесь — только процедура проверки перед боем. Худшее, что можно сделать, — взять домен из чужого туториала не глядя (включая этот: www.samsung.com ниже — плейсхолдер, а не рекомендация).

D=www.samsung.com
openssl s_client -connect $D:443 -servername $D -tls1_3 -alpn h2 </dev/null 2>/dev/null \
 | grep -E "Protocol|Server Temp Key|ALPN protocol|issuer"

Годный кандидат выглядит так:

ALPN protocol: h2
Protocol: TLSv1.3
Server Temp Key: X25519, 253 bits

Три жёстких условия: TLS 1.3, X25519 в Server Temp Key (увидели ECDH, P-256 — кандидат не подходит) и h2 в ALPN. Прогнать список пачкой:

for D in www.samsung.com www.lovelive-anime.jp cdn.jsdelivr.net www.icloud.com; do
 R=$(openssl s_client -connect $D:443 -servername $D -tls1_3 -alpn h2 </dev/null 2>/dev/null \
 | grep -E "Protocol:|Server Temp Key|ALPN protocol" | tr '\n' ' ')
 printf "%-28s %s\n" "$D" "${R:-FAIL}"
done

У Xray есть и собственная проверка: xray tls ping www.samsung.com — показывает цепочку сертификатов глазами ядра.

Два момента, о которых забывают. Первый: запускать проверку надо с самого сервера. Часть сайтов и часть хостеров режут доступ по географии — из Москвы всё зелёно, а с VPS во Франкфурте таймаут, и Reality на таком доноре будет молча ронять чужие соединения. Второй: это не «настроил и забыл». Сайты переезжают за общий CDN и меняют TLS-профиль без предупреждения, поэтому держите двух-трёх проверенных запасных и умейте переключить dest и serverNames за минуту — вместе с рассылкой новой подписки клиентам.

Шаг 3. Xray reality config: конфиг сервера целиком

Кладём в /usr/local/etc/xray/config.json. Это полный рабочий файл, а не фрагмент — подставьте свои значения из /root/reality.env.

{
 "log": {
 "loglevel": "warning",
 "access": "/var/log/xray/access.log",
 "error": "/var/log/xray/error.log"
 },
 "inbounds": [
 {
 "tag": "vless-reality",
 "listen": "0.0.0.0",
 "port": 443,
 "protocol": "vless",
 "settings": {
 "clients": [
 {
 "id": "00000000-0000-0000-0000-000000000000",
 "email": "user1@node1",
 "flow": "xtls-rprx-vision"
 }
 ],
 "decryption": "none"
 },
 "streamSettings": {
 "network": "tcp",
 "security": "reality",
 "realitySettings": {
 "show": false,
 "dest": "www.samsung.com:443",
 "xver": 0,
 "serverNames": ["www.samsung.com"],
 "privateKey": "iJ2h...",
 "shortIds": ["a1b2c3d4e5f60718"]
 }
 },
 "sniffing": {
 "enabled": true,
 "destOverride": ["http", "tls", "quic"],
 "routeOnly": true
 }
 }
 ],
 "outbounds": [
 { "tag": "direct", "protocol": "freedom" },
 { "tag": "block", "protocol": "blackhole" }
 ],
 "routing": {
 "domainStrategy": "IPIfNonMatch",
 "rules": [
 { "type": "field", "ip": ["geoip:private"], "outboundTag": "block" },
 { "type": "field", "protocol": ["bittorrent"], "outboundTag": "block" }
 ]
 }
}

Что здесь неочевидно:

  • email в clients[] — не почта, а метка в access.log. Без неё вы не поймёте, чей аккаунт качает трафик и чей ключ разошёлся по чатам: shortIds — плоский список, он не привязан к конкретному клиенту, и по логу sid не читается.
  • shortIds без пустой строки. Пустой элемент "" разрешает подключение вообще без sid — удобно на отладке, вредно в проде.
  • dest в свежих сборках называется target, старое имя работает как алиас. Если конфиг переезжает между версиями ядра — оставляйте dest, его понимают обе.
  • geoip:private в block закрывает клиентам путь во внутреннюю сеть хостера. Без этого правила из вашего же туннеля видно соседей по подсети и метадата-сервис облака.
  • routeOnly: true в sniffing: домен берётся только для маршрутизации и не подменяет адрес назначения — меньше сюрпризов с сайтами за CDN.
  • xver: 0 — PROXY protocol выключен; включать только если перед Xray стоит свой фронт-прокси.

Применяем и проверяем синтаксис, а не «рестарт и посмотрим»:

xray run -test -c /usr/local/etc/xray/config.json # ждём: Configuration OK
systemctl restart xray && systemctl --no-pager status xray
journalctl -u xray -n 30 --no-pager

address already in use означает, что на 443 уже сидит nginx или панель: разводите их по портам или по разным IP. Повесить Reality на нестандартный порт технически можно, но визит «на samsung.com:8443» выглядит аномально и первым попадает под эвристики.

Шаг 4. Клиент: полный JSON и ссылка vless://

Клиент повторяет серверные значения дословно. Конфиг для десктопного Xray — SOCKS на 10808, HTTP на 10809:

{
 "log": { "loglevel": "warning" },
 "inbounds": [
 {
 "tag": "socks",
 "listen": "127.0.0.1",
 "port": 10808,
 "protocol": "socks",
 "settings": { "udp": true },
 "sniffing": { "enabled": true, "destOverride": ["http", "tls", "quic"] }
 },
 {
 "tag": "http",
 "listen": "127.0.0.1",
 "port": 10809,
 "protocol": "http"
 }
 ],
 "outbounds": [
 {
 "tag": "proxy",
 "protocol": "vless",
 "settings": {
 "vnext": [
 {
 "address": "203.0.113.10",
 "port": 443,
 "users": [
 {
 "id": "00000000-0000-0000-0000-000000000000",
 "encryption": "none",
 "flow": "xtls-rprx-vision"
 }
 ]
 }
 ]
 },
 "streamSettings": {
 "network": "tcp",
 "security": "reality",
 "realitySettings": {
 "serverName": "www.samsung.com",
 "fingerprint": "chrome",
 "publicKey": "6Dpm...",
 "shortId": "a1b2c3d4e5f60718",
 "spiderX": "/"
 }
 }
 },
 { "tag": "direct", "protocol": "freedom" }
 ],
 "routing": {
 "domainStrategy": "IPIfNonMatch",
 "rules": [
 { "type": "field", "ip": ["geoip:private", "geoip:ru"], "outboundTag": "direct" }
 ]
 }
}

Правило с geoip:ru пускает российские адреса мимо туннеля: меньше расход трафика на ноде и меньше жалоб на банковские приложения. Доменные списки (geosite:*) намеренно не добавлены — их состав зависит от того, чей geosite.dat лежит рядом с бинарём, универсального совета тут нет.

Та же конфигурация одной ссылкой — для Happ, v2rayNG, Streisand, Nekobox и подписок:

vless://[email protected]:443?type=tcp&security=reality&encryption=none&flow=xtls-rprx-vision&sni=www.samsung.com&fp=chrome&pbk=6Dpm...&sid=a1b2c3d4e5f60718&spx=%2F#reality-node1

Две детали: spx=%2F — слэш обязан быть URL-кодирован, иначе часть парсеров обрежет строку; текст после # — только метка в списке серверов, на подключение не влияет.

И предупреждение из практики: не досыпайте в ссылку поля «на всякий случай» — ручной alpn, экзотические параметры обфускации, хвосты от чужих конфигов. Набор полей Reality менялся между версиями ядра, и поле, безобидное у вас на ПК, у части пользователей превращается в «конфиг не импортируется» или молчаливый обрыв. Восьми параметров выше достаточно. Отдельно запомните: flow: xtls-rprx-vision действует только для TCP+TLS/Reality — если этот же пользователь получит второй профиль поверх XHTTP, там flow должен быть пустым.

Шаг 5. Проверка: пять тестов, включая активный пробинг

«В клиенте загорелось зелёное» — не проверка. Прогоните по порядку, каждый тест отсекает свой класс проблем.

#Что проверяемКомандаЧто должно быть
1Синтаксис конфигаxray run -test -c /usr/local/etc/xray/config.jsonConfiguration OK
2Порт слушается`ss -lntp \grep ':443'`строка с xray
3Донор доступен с ноды`curl -sI --max-time 5 https://www.samsung.com \head -1`HTTP/2 200 или редирект
4Маскировка (пробинг)см. нижесертификат и ответ настоящего донора
5Туннель работаетcurl -x socks5h://127.0.0.1:10808 -s https://api.ipify.orgIP вашего сервера

Четвёртый тест — главный и самый недооценённый. Запускайте его не с сервера, а с посторонней машины: вы имитируете ровно то, что делает цензор, стучась на ваш IP.

IP=203.0.113.10; D=www.samsung.com

# Что увидит активный пробинг: должен вернуться реальный ответ донора
curl -sI --resolve $D:443:$IP https://$D/ | head -3

# Чей сертификат отдаёт ваш сервер
openssl s_client -connect $IP:443 -servername $D </dev/null 2>/dev/null \
 | openssl x509 -noout -subject -issuer

В выводе должны быть subject и issuer настоящего донора — CN=www.samsung.com и его удостоверяющий центр. Нет сертификата, соединение рвётся или висит — либо dest недоступен с сервера (egress-файрвол хостера, см. шаг 0), либо в serverNames нет того имени, что вы подставили в --resolve.

Пятый тест дополните измерением и негативной проверкой:

curl -x socks5h://127.0.0.1:10808 -s https://ifconfig.co/json
curl -x socks5h://127.0.0.1:10808 -o /dev/null -s -w "%{speed_download}\n" \
 https://speed.cloudflare.com/__down?bytes=25000000

Теперь испортите один символ в publicKey на клиенте и переподключитесь: соединение подняться не должно. Если оно всё равно работает — вы правите файл, который ядро не читает (типовая ситуация на ноде под панелью), и все предыдущие тесты ничего не доказывают. На сервере в этот момент при "show": true в логе появится запись об отклонённом хендшейке; после отладки верните false, иначе лог забьётся мусором от чужих сканеров.

Частые ошибки, эксплуатация и граница Reality

Сводка по симптомам, по убыванию частоты в обращениях операторов:

СимптомПричинаЛечение
Клиент «подключен», сайты не открываютсяflow: xtls-rprx-vision прописан только на одной стороневыставить одинаково у клиента и в clients[]
Обрыв сразу после хендшейкаsni вне serverNames, sid не из shortIds или клиенту отдали privateKey вместо pbkсверить четыре поля по таблице из шага 1
Всё сломалось после смены донораserverNames поменяли, у клиентов остался старый sniменять на обеих сторонах, через перевыпуск подписки
Работает на ПК, не работает на телефонепустой fp, устаревший клиент, лишние поля в ссылкеfp=chrome, убрать всё сверх восьми полей, обновить клиент
Xray не стартует: address already in useна 443 висит nginx или панельразвести по портам или по IP
Плавающие разрывы у части клиентовушли часы сервераtimedatectl set-ntp true
Пробинг стал видеть ошибку TLSдонор уехал за CDN или отключил TLS 1.3/X25519перепроверить openssl s_client, переключиться на запасного
Configuration OK, но снаружи порт закрытsecurity group хостера, а не ufwоткрыть 443/tcp в панели хостера
Правки в конфиге не применяютсянода под панелью, файл перезаписываетсяправить в UI панели

Про эксплуатацию. Приватный ключ меняют только при компрометации: его ротация — это обновление pbk у всех клиентов сразу, то есть перевыпуск всей подписки. Точечный отзыв дешевле делать через UUID: удалили запись из clients[], перезапустили ядро — остальные не пострадали. С shortIds так не выйдет: список общий и не привязан к пользователю, удаление значения отрубит всех, кто его использует. Если хотите per-user sid — ведите сопоставление sid→пользователь у себя, ядро вам его не покажет. Смена донора ключей не касается вообще.

И честная граница. Есть класс симптомов, который не чинится ни одной строкой таблицы: конфиг верный, пробинг проходит, с домашнего интернета летает — а с мобильного оператора не подключается никто. Это не ошибка настройки. Reality маскирует что вы делаете, но не меняет откуда: клиент по-прежнему идёт напрямую на IP ноды, и если дата-центровый адрес режется по IP или не входит в белый список оператора, маскировать уже нечего.

Лечится это точкой входа, а не транспортом: перед нодой ставится фронт на «белом» IP (идея whitelist-CDN вроде Clearway), и клиент подключается к CDN. Транспорт там уже другой — XHTTP, и ломается он обычно в двух местах. Первое: у Yandex CDN нет POST, поэтому аплинк идёт через GET, а GET разрешён только при явно заданном mode: packet-up — без него получается классическое «в одном приложении работает, в другом нет». Второе: поле extra на ноде и в клиенте должно совпадать символ в символ. Подробности — в материалах про XHTTP и панели за CDN. Практичная схема для оператора: Reality основным профилем, CDN-фронт резервным, оба в одной подписке.

Частые вопросы

Почему xray x25519 выводит Password вместо Public key?

Так выглядит вывод в Xray-core ветки 25.x: строка PrivateKey идёт в конфиг сервера, а строка Password — это и есть публичный ключ, который клиент указывает в publicKey (pbk). В сборках до 25.x те же значения назывались Private key и Public key. Оба — base64url длиной 43 символа; строка другой длины означает, что скопировали не то. Вывести публичный ключ из приватного можно командой xray x25519 с флагом ввода, но имя флага между версиями менялось — сверьтесь с xray help x25519.

Можно ли выдать один shortId всем пользователям?

Технически да: Reality проверяет только вхождение sid в список shortIds. Но shortIds — плоский список, не привязанный к записи клиента, поэтому удаление значения отрубит всех, кто его использует, а по access.log вы sid не увидите — там метка email из clients[]. Точечный отзыв доступа делают через UUID, а если нужен персональный sid, сопоставление sid→пользователь придётся вести у себя. Пустую строку в shortIds в проде не оставляйте: она разрешает подключение вообще без sid.

Обязателен ли flow xtls-rprx-vision?

Для VLESS + Reality поверх TCP это стандартная рабочая база, и правило одно: flow либо прописан на обеих сторонах, либо ни на одной. Типовой симптом рассинхрона — клиент показывает «подключено», но ни один сайт не открывается. Пустой flow допустим, однако Vision лучше ведёт себя против анализа TLS-in-TLS. Важная оговорка: для транспорта XHTTP (профиль через CDN) flow должен быть пустым — Vision работает только с TCP+TLS/Reality.

Как одной командой понять, что SNI-донор подходит?

openssl s_client -connect домен:443 -servername домен -tls1_3 -alpn h2 </dev/null | grep -E "Protocol|Server Temp Key|ALPN protocol". Нужны три вещи: Protocol: TLSv1.3, Server Temp Key: X25519 (P-256 не подходит) и ALPN protocol: h2. Запускать проверку надо именно с вашего сервера, а не с домашнего компьютера: часть сайтов и хостеров режут доступ по географии, и донор, живой из Москвы, может быть недоступен с VPS во Франкфурте.

Как проверить, что маскировка реально работает?

Тестом активного пробинга с посторонней машины: curl -sI --resolve донор:443:ВАШ_IP https://донор/ и openssl s_client -connect ВАШ_IP:443 -servername донор с выводом subject/issuer. Вы должны получить ответ и сертификат настоящего донора — именно это увидит цензор. Дополните негативной проверкой: испортите один символ в publicKey у клиента — соединение подняться не должно. Если оно всё равно поднимается, вы правите конфиг, который ядро не читает (типично на ноде под панелью).

Конфиг работает на ПК, но не импортируется в мобильный клиент — почему?

Обычно из-за лишних полей в ссылке vless:// или устаревшего клиента. Набор параметров Reality менялся между версиями ядра, и экзотические поля обфускации или вручную дописанный alpn ломают совместимость между разными Xray и приложениями. Оставьте минимум: type, security, encryption, flow, sni, fp, pbk, sid и spx=%2F с обязательным URL-кодированием слэша — и обновите клиент.

Спасёт ли Reality от блокировки IP ноды и белых списков?

Нет. Reality скрывает факт VPN от DPI, переживает SNI-фильтрацию и активный пробинг, но клиент всё равно идёт напрямую на IP вашего сервера. Если адрес дата-центра режется по IP или не входит в белый список оператора, соединение не установится независимо от качества маскировки. Диагностический признак: у всех пользователей одного оператора не работает, а с домашнего интернета работает. Решение — фронт на «белом» IP (whitelist-CDN) с транспортом XHTTP, а не смена донора.

Whitelisted-вход для вашего VPN-сервиса

Clearway даёт «белый» CDN-вход перед вашей нодой — устойчивый к троттлингу операторов. Первые 10 ГБ бесплатно, без карты.

Попробовать →