Что вы ставите и что нужно до установки
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/.
Два нюанса, которые обычно узнают на практике:
- Сертификат для сервиса подписок задаётся отдельно, в разделе Subscription. Файл можно указать тот же, но домен подписок обязан быть в SAN сертификата — иначе мобильные клиенты откажутся тянуть подписку.
--reloadcmd "x-ui restart"перезапускает не только панель, но и Xray: в момент продления сертификата (раз в ~60 дней, ночью по крону acme.sh) все клиенты переподключатся. Разрыв секундный, но если вам не нужен даже он — переводите выпуск на DNS-01 и перезагружайте панель вручную в окно обслуживания.
Первый инбаунд: VLESS + Reality на 443
Inbounds → Add Inbound. Рабочий минимум:
| Поле | Значение | Почему |
|---|---|---|
| Protocol | vless | Легче VMess, без лишней криптографии поверх TLS |
| Listen IP | пусто | 127.0.0.1 — только если перед Xray стоит nginx или CDN-фронт |
| Port | 443 | Инбаунд на экзотическом порту заметнее для ТСПУ |
| Transport | tcp (в свежих Xray переименован в raw — это одно и то же) | Reality работает только с ним |
| Security | reality | Домен и сертификат на инбаунд не нужны |
| 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):
| Поле | Значение |
|---|---|
user001 — это идентификатор клиента, а не почта | |
| ID | UUID: кнопка в панели или /usr/local/x-ui/bin/xray-linux-amd64 uuid |
| Flow | xtls-rprx-vision для tcp/raw + Reality; пусто для ws/grpc/xhttp |
| Expire / Total GB | дата и лимит трафика, 0 = без лимита |
| IP Limit | число одновременных IP |
| Subscription ID | subid-user001 — по нему собирается ссылка подписки |
Два подводных камня:
- Email уникален глобально, по всем инбаундам сразу, а не внутри одного. Дубликат — и Xray не стартует вообще, уронив всех клиентов. Симптом характерный: панель открывается нормально,
x-ui statusпоказывает Xray как stopped, в логе — жалоба на duplicate. - IP Limit не работает без лога доступа. Панель считает адреса по
access.logXray; если в 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 в URL | x-ui settings → взять путь оттуда | |
| Xray stopped после правки | Дубликат email, занятый порт, битый JSON в extra | x-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, а не переустановки всего.