Блог Clearway

3x-ui настройка с нуля: от установки до первой рабочей ссылки

Коротко

3x ui настройка с нуля — это пять шагов. Поставить панель: bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh). Сразу закрыть её: /usr/local/x-ui/x-ui setting -port 54321 -username braidadm -password '...' -webBasePath /a7f3c1d9e2/. Повесить TLS — отдельно на панель и отдельно на сервис подписок. Создать инбаунд VLESS + Reality на 443. Добавить клиента (уникальный email, UUID, flow: xtls-rprx-vision, свой subId) и отдать ссылку https://sub.example.com:2096/sub/<subId>. Вся конфигурация лежит в одном файле /etc/x-ui/x-ui.db — бэкапить нужно только его. Важная оговорка на будущее: Reality прячет протокол, но не адрес. Когда IP ноды режут по адресу, ни смена порта, ни Reality не помогают — нужен либо новый IP, либо фронт (CDN/реверс) на домене, который не блокируют.

Что вы ставите и что нужно до установки

3x-ui — веб-панель поверх Xray-core. Она не транспорт и не VPN: она собирает config.json для Xray, хранит его в SQLite и рисует UI поверх. Всё, чего не умеет ваша версия Xray, панель тоже не умеет — это правило объясняет половину будущих вопросов «почему поля нет в интерфейсе».

ЧтоТребованиеКомментарий
ОСDebian 11/12, Ubuntu 22.04/24.04, AlmaLinux/Rocky 9На Debian/Ubuntu меньше сюрпризов с systemd и acme.sh
Ресурсы1 vCPU / 1 GB RAMПо наблюдениям до ~200–300 активных сессий VLESS этого хватает; упирается обычно канал и pps, а не CPU
Домен2 A-записиpanel.example.com и sub.example.com. Reality домена не требует, но панель и подписки без HTTPS делать нельзя
Порты443 — инбаунд, 80 — только на время выпуска сертификата, произвольный >1024 — панель, 2096 — подпискиДефолтный 2053 меняем в первые же минуты
СерверчистыйЕсли на 443 уже висит nginx/apache — решите заранее, кто там главный: Xray или веб-сервер

Подготовка:

apt update && apt -y upgrade
apt -y install curl socat ufw sqlite3 tzdata
timedatectl set-timezone Europe/Moscow

socat нужен acme.sh в standalone-режиме, sqlite3 — чтобы в аварийной ситуации залезть в базу руками. Про часы честно: VLESS и Reality к расхождению времени нечувствительны, в отличие от VMess — если у вас останутся VMess-инбаунды, расхождение больше ~90 секунд убьёт хендшейк.

Установка и что куда легло

Официальный установщик (репозиторий MHSanaei/3x-ui):

bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)

Свежие сборки в конце спрашивают логин, пароль, порт и webBasePath; если жать Enter — скрипт сгенерирует случайные значения и выведет их одним блоком. Скопируйте вывод сразу: пароль хранится хэшем, посмотреть его потом нельзя — только задать новый.

ПутьЧто это
/usr/local/x-ui/x-uiБинарь панели (он же принимает подкоманду setting)
/usr/local/x-ui/bin/Xray-core (xray-linux-amd64), geoip.dat, geosite.dat
/etc/x-ui/x-ui.dbВся конфигурация: инбаунды, клиенты, настройки, ключи Reality, статистика
/usr/bin/x-uiСкрипт-обёртка: интерактивное меню и короткие команды
/etc/systemd/system/x-ui.serviceЮнит systemd

Базовая диагностика:

x-ui status # состояние панели и Xray
x-ui settings # текущие логин, порт, webBasePath
x-ui log # логи панели
journalctl -u x-ui -e --no-pager # если x-ui log молчит
/usr/local/x-ui/bin/xray-linux-amd64 -version

Если что-то не открывается, а команды выше врут — смотрите базу напрямую, она одна и та же для UI и для CLI:

sqlite3 /etc/x-ui/x-ui.db "select * from settings;"
sqlite3 /etc/x-ui/x-ui.db "select id,remark,port,protocol,enable from inbounds;"

Имена ключей в settings между версиями слегка плавают, поэтому смотрим таблицу целиком, а не угадываем ключ.

Первый вход — http://IP:ПОРТ/webBasePath/. 404 на входе почти всегда означает, что webBasePath забыт в URL: он обязателен.

Первым делом закрываем панель: порт, учётка, webBasePath

Порт 2053 и связка admin/admin из старых сборок сканируются массово — открытую панель находят ботами за часы. Поэтому первое, что делает оператор, — закрывает панель, и только потом создаёт инбаунды.

Всё меняется без веб-интерфейса, подкомандой бинаря (у обёртки x-ui набор команд отличается от версии к версии, а бинарь принимает флаги стабильно):

/usr/local/x-ui/x-ui setting -username braidadm -password 'СильныйПароль' \
 -port 54321 -webBasePath /a7f3c1d9e2/
x-ui restart
x-ui settings

То же есть в интерактивном меню x-ui — пункты про порт, учётку и web base path; номера пунктов между версиями сдвигаются, ориентируйтесь по названию, а не по цифре.

Где спотыкаются:

  • webBasePath должен быть в формате /что-то/ — со слэшами с обеих сторон. Без них панель отдаст 404 на всё.
  • Порт панели не должен пересекаться с инбаундом и подписками: ss -lntp | grep -E ':(443|2096|54321)'.
  • Пароль — в одинарных кавычках, если в нём есть $, !, пробелы или бэктик. Иначе bash его покалечит, и вы получите пароль, которого сами не знаете.

Дальше файрвол — и лучше не всему интернету:

ufw allow 22/tcp
ufw allow from ВАШ.IP.АДМИНА to any port 54321 proto tcp
ufw allow 443/tcp
ufw allow 2096/tcp
ufw --force enable
ufw status numbered

Важная оговорка: это работает для установки из скрипта (обычный процесс на хосте). Если вы поставили 3x-ui в Docker, ufw порты контейнера не закроет — публикация портов Docker обходит цепочку INPUT, правила нужно писать в DOCKER-USER либо не публиковать порт наружу вовсе.

Если админский IP динамический — оставьте порт открытым, но включите в панели двухфакторку (Panel Settings → Security) и не отключайте webBasePath.

TLS: сертификат на панель и отдельно на подписки

Панель по HTTP — это учётка, конфигурация всех инбаундов и приватные ключи Reality открытым текстом. Так что настрой 3x-ui с TLS до того, как заведёшь первого платного клиента.

Быстрый путь — пункт меню «SSL Certificate Management» в x-ui: указываете домен, скрипт сам поднимет acme.sh в standalone и положит файлы в /root/cert/<домен>/.

Ручной вариант, если нужен контроль:

curl https://get.acme.sh | sh -s [email protected]
~/.acme.sh/acme.sh --set-default-ca --server letsencrypt

ufw allow 80/tcp # на время валидации
~/.acme.sh/acme.sh --issue -d panel.example.com -d sub.example.com --standalone --keylength ec-256

mkdir -p /root/cert/panel.example.com
~/.acme.sh/acme.sh --install-cert -d panel.example.com --ecc \
 --key-file /root/cert/panel.example.com/privkey.pem \
 --fullchain-file /root/cert/panel.example.com/fullchain.pem \
 --reloadcmd "x-ui restart"

Два -d в одном сертификате — чтобы не выпускать второй под подписки. Дальше в панели, Panel Settings → General, прописываете пути:

/root/cert/panel.example.com/fullchain.pem
/root/cert/panel.example.com/privkey.pem

Сохранить → Restart Panel. Панель становится доступна только по https://panel.example.com:54321/a7f3c1d9e2/.

Два нюанса, которые обычно узнают на практике:

  1. Сертификат для сервиса подписок задаётся отдельно, в разделе Subscription. Файл можно указать тот же, но домен подписок обязан быть в SAN сертификата — иначе мобильные клиенты откажутся тянуть подписку.
  2. --reloadcmd "x-ui restart" перезапускает не только панель, но и Xray: в момент продления сертификата (раз в ~60 дней, ночью по крону acme.sh) все клиенты переподключатся. Разрыв секундный, но если вам не нужен даже он — переводите выпуск на DNS-01 и перезагружайте панель вручную в окно обслуживания.

Первый инбаунд: VLESS + Reality на 443

Inbounds → Add Inbound. Рабочий минимум:

ПолеЗначениеПочему
ProtocolvlessЛегче VMess, без лишней криптографии поверх TLS
Listen IPпусто127.0.0.1 — только если перед Xray стоит nginx или CDN-фронт
Port443Инбаунд на экзотическом порту заметнее для ТСПУ
Transporttcp (в свежих Xray переименован в raw — это одно и то же)Reality работает только с ним
SecurityrealityДомен и сертификат на инбаунд не нужны
Dest / SNIодин и тот же хост, например www.microsoft.com:443 / www.microsoft.comТребования: TLS 1.3, ALPN h2, без редиректа, не заблокирован в РФ
Public/Private Keyкнопка «Get New Cert» в панелиРуками: /usr/local/x-ui/bin/xray-linux-amd64 x25519 (в свежих версиях подписи полей в выводе переименованы, значения те же)
shortIdкнопка генерацииHex чётной длины, до 16 символов; можно завести несколько

Перед тем как ставить dest, проверьте его — сайт должен реально отдавать TLS 1.3 и h2:

openssl s_client -connect www.microsoft.com:443 -servername www.microsoft.com \
 -tls1_3 -alpn h2 </dev/null 2>/dev/null | grep -E 'Protocol|ALPN|Verify return'

После сохранения Xray должен подняться: x-ui status и ss -lntp | grep ':443'. Тонкости подбора dest, отпечатков uTLS и разбор ошибок ядра — в отдельных материалах про Reality и Xray-core, здесь не повторяем.

Когда Reality перестаёт спасать. Reality маскирует протокол под чужой сайт, но не прячет IP вашего сервера. Если адрес ноды попал под блокировку по IP, пакеты до него просто не доходят — и неважно, что внутри. Тогда нода уводится за фронт: XHTTP + CDN на «белом» домене, где клиент коннектится к CDN-edge, а тот проксирует на origin. Это отдельный сценарий (у нас есть разборы «XHTTP через CDN» и «3x-ui за CDN»), но три факта стоит знать заранее, ещё на этапе первой настройки:

  • У Yandex CDN нет POST, поэтому XHTTP-аплинк уходит GET-ом, а GET разрешён только при mode: packet-up. Оставите auto — часть клиентов встанет намертво.
  • Поле extra на инбаунде и в клиентском конфиге должно совпадать буква в букву. Рассинхрон extra — самая частая причина «в одном приложении работает, в другом нет».
  • Экзотические obfuscation-поля в extra ломают совместимость между версиями Xray: нода на одной версии, клиент на другой — и соединение не поднимается. Держите extra минимальным.

И ещё одно, что относится уже к первой настройке: flow: xtls-rprx-vision применим только к tcp/raw. При ws, grpc и xhttp поле должно быть пустым.

Клиенты и подписка

В инбаунде жмём «+» (Add Client):

ПолеЗначение
Emailuser001 — это идентификатор клиента, а не почта
IDUUID: кнопка в панели или /usr/local/x-ui/bin/xray-linux-amd64 uuid
Flowxtls-rprx-vision для tcp/raw + Reality; пусто для ws/grpc/xhttp
Expire / Total GBдата и лимит трафика, 0 = без лимита
IP Limitчисло одновременных IP
Subscription IDsubid-user001 — по нему собирается ссылка подписки

Два подводных камня:

  1. Email уникален глобально, по всем инбаундам сразу, а не внутри одного. Дубликат — и Xray не стартует вообще, уронив всех клиентов. Симптом характерный: панель открывается нормально, x-ui status показывает Xray как stopped, в логе — жалоба на duplicate.
  2. IP Limit не работает без лога доступа. Панель считает адреса по access.log Xray; если в Xray Settings уровень лога стоит none или лог не пишется, лимит молча игнорируется.

Одиночную ссылку vless://... и QR можно забрать прямо в списке клиентов, но для продажи нужен sub-сервис. Panel Settings → Subscription:

Enable Subscription: ✔
Subscription Port: 2096
Subscription Path: /sub/
Subscription JSON Path: /json/
Subscription URI: https://sub.example.com:2096/sub/
Certificate / Key Path: /root/cert/panel.example.com/{fullchain,privkey}.pem

После рестарта ссылка клиента — https://sub.example.com:2096/sub/subid-user001. Клиенты с одинаковым Subscription ID в разных инбаундах склеятся в одну подписку с несколькими конфигами — так делается «одна ссылка, несколько локаций».

Проверка снаружи, без браузера:

curl -sI https://sub.example.com:2096/sub/subid-user001 | grep -iE 'http/|subscription-userinfo|profile-update-interval'
curl -s https://sub.example.com:2096/sub/subid-user001 | base64 -d | head

В заголовке subscription-userinfo панель отдаёт upload/download/total/expire — по нему приложение рисует остаток трафика. Если base64 -d ругается на мусор, значит отдалась не подписка, а HTML-ошибка: смотрите первую команду.

По наблюдениям: часть мобильных клиентов (заметнее всего Happ на iOS) кэширует тело подписки и не подхватывает правку сразу. Если снаружи curl уже отдаёт новый конфиг, а в приложении старый — это кэш, лечится ручным обновлением подписки или её удалением и повторным добавлением, а не перенастройкой сервера.

Эксплуатация: бэкап, обновление, типовые ошибки

Бэкап. Вся установка — один файл, и снимать его можно на живой панели:

mkdir -p /root/backups
sqlite3 /etc/x-ui/x-ui.db ".backup '/root/backups/x-ui-$(date +%F).db'"

.backup берёт консистентный снимок без остановки сервиса — в отличие от cp, который на активной записи может дать битый файл. В крон раз в сутки плюс отправка копии наружу; в самой панели есть Telegram-бот, умеющий присылать .db по расписанию — для одной ноды этого достаточно.

Восстановление на новом сервере: поставить 3x-ui той же или более свежей версии, systemctl stop x-ui, положить .db на место, x-ui restart. Переезжают инбаунды, UUID, ключи Reality, настройки подписки и статистика. Править после переезда придётся адрес сервера в инбаундах, Subscription URI и пути к сертификатам.

Обновление. Сначала бэкап, потом x-ui update для панели. Версия Xray-core переключается отдельным пунктом меню x-ui — им же откатывается на предыдущую, если после обновления ядра отвалились клиенты. Не обновляйте панель и ядро одной ночью с новым инбаундом: потом не поймёте, что именно сломало.

Типовые ошибки:

СимптомПричинаЧто делать
Панель не открываетсяПорт закрыт в ufw / панель упалаx-ui status, `ss -lntp \grep x-ui, ufw status`
404 на панелиЗабыт webBasePath в URLx-ui settings → взять путь оттуда
Xray stopped после правкиДубликат email, занятый порт, битый JSON в extrax-ui log, journalctl -u x-ui -e
Клиент коннектится, интернета нетSNI ≠ dest, либо flow задан там, где не нуженСверить SNI с dest; убрать flow для ws/grpc/xhttp
Инбаунд не поднимается на 443Порт занят nginx/apache`ss -lntp \grep:443` → освободить порт
Подписка отдаёт пустое телоУ клиентов не проставлен Subscription IDЗадать subId каждому клиенту
Работает в одном приложении, не в другомРассинхрон mode/extra при XHTTPЯвный packet-up, extra идентичен на ноде и в клиенте
Клиенты «не видят» новый конфигКэш подписки на стороне приложенияПроверить curl снаружи; обновить/переподключить подписку

Что дальше. Настройка заканчивается там, где начинается работа оператора. IP ноды рано или поздно попадёт под блокировку по адресу, и ни Reality, ни смена порта этого не лечат. К этому моменту у вас должен быть заранее готов запасной путь — резервная нода или фронт на домене, который не режут, — чтобы переключение стоило одной правки Subscription URI, а не переустановки всего.

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

Чем 3x-ui отличается от Marzban и Remnawave — что выбирать оператору?

3x-ui — панель для одного сервера: одна нода, один Xray, одна SQLite-база. Ставится за десять минут, мультинодовость делается обходными путями. Marzban и Remnawave изначально мультинодовые: центральная панель плюс агенты на нодах, общая база пользователей, подписка на несколько локаций из коробки. Практический критерий: одна нода и до пары сотен клиентов — 3x-ui закрывает задачу полностью; планируете две локации и больше с единым биллингом — сразу берите Remnawave или Marzban, миграция потом болезненная (клиентские UUID переносятся, а логика лимитов и подписок — нет).

Как настроить 3x-ui, если сервер уже занят сайтом на 443?

Вариантов два. Первый: увести Xray на 127.0.0.1 и терминировать TLS на nginx, а инбаунд сделать ws или xhttp с path — тогда сайт и VPN живут на одном 443, но Reality в этой схеме недоступен (он требует прямого доступа к сокету). Второй, более простой: оставить 443 за Xray с Reality, а сайт увести на другой порт или другой сервер. Промежуточный вариант — Xray на 443 с fallback на локальный веб-сервер, но это уже ручная правка конфига мимо панели, и следующее обновление её может затереть.

Можно ли оставить панель на дефолтном порту 2053 и без TLS?

Технически да, работать будет. Практически нет: 2053 вместе с типовыми путями сканируется ботами постоянно, а без TLS по сети открытым текстом идут и логин, и вся конфигурация нод, включая приватные ключи Reality при просмотре инбаунда. Минимальный набор — нестандартный порт, случайный webBasePath, сертификат на панель, правило ufw только на ваш IP и двухфакторка, если IP динамический.

Почему после добавления клиента Xray не стартует, хотя панель работает?

Чаще всего — неуникальный email. В 3x-ui это поле-идентификатор, и уникален он должен быть по всем инбаундам сразу, а не внутри одного. При дубликате Xray отказывается разбирать конфиг и не поднимается, при этом сама панель продолжает открываться — что и путает. Диагностика: x-ui log или journalctl -u x-ui -e --no-pager, искать строку про duplicate. Реже причина — занятый порт нового инбаунда или невалидный JSON в поле extra.

Нужен ли домен, если я использую Reality?

Для самого инбаунда — нет: Reality заимствует чужой домен через dest/SNI и работает по голому IP. Домен нужен для двух других вещей — TLS на панель и TLS на сервис подписок. Раздавать ссылку вида http://IP:2096/sub/... не стоит: подписка идёт по сети открытым текстом, а часть мобильных приложений вообще откажется её тянуть без HTTPS. Так что два поддомена завести придётся в любом случае, и удобнее сразу выпустить один сертификат с двумя -d.

Мой IP заблокировали — поможет ли смена порта или переключение на Reality?

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

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

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

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