3x-ui-dual-domain
Автор: Shepard86rus Разработка ведётся совместно с ИИ-ассистентом (ZCode AI / Claude).
Установщик и healthcheck для VPN-панели на одном сервере: два домена, nginx SNI-роутинг на 443, режимы TLS/Reality, Hysteria2, AmneziaWG, подписки, decoy-сайты, ufw + fail2ban. Опционально — Telegram-бот управления нодой с карточками-кнопками.
Disclaimer. Инструмент для управления собственным сервером. Используйте в соответствии с законодательством вашей страны.
Как устроена схема (коротко)
- Снаружи слушает только nginx на 443 (TCP): по SNI из ClientHello он маршрутизирует трафик на xray, слушающий на loopback (proxy protocol), а «чужие» подключения и браузеры получают decoy-сайты с валидным сертификатом.
- Панель и подписки доступны только через nginx по своим домену/пути, на публичных портах не висят.
- Hysteria2 работает на UDP/443, AmneziaWG — на отдельном UDP-порту (генерируется случайно при установке).
- ufw закрыт наглухо, кроме 22/80/443 TCP + 443 UDP + AWG-порта; fail2ban следит за SSH и панелью.
Быстрый старт
-
Приготовь два домена с A-записями на IP сервера (зачем именно два — в блоке «Подготовка» ниже).
-
Скачай и запусти:
curl -fsSL https://raw.githubusercontent.com/Shepard86rus/3x-ui-dual-domain/main/3x-ui-dual-domain.sh -o 3x-ui-dual-domain.sh bash 3x-ui-dual-domain.sh install
-
Заполни клиентов в панели (2 шага — см. блок «Установка»).
-
bash 3x-ui-dual-domain.sh healthcheck— автопроверка всего сервера: сервисы, порты, firewall, сертификаты, decoy-сайты, БД, подписки, AWG, бот, открытые порты. Запускай после настройки и после любых изменений — в ответ получишь «N проверок ОК, M провалено» и список проблем, если есть.
Остальное — в свёрнутых блоках ниже: разверни то, что нужно сейчас.
⚠️ Панель после установки: что можно, а что нельзя (прочитай до ручных правок)
Панель — полноценная, но схема держится на соглашениях, которые прописал
установщик. Краткое правило: клиентов трогать можно; инбаунды, их режимы
и настройки ядра Xray в панели — не трогать. Само обновление панели
и ядра Xray через x-ui update — можно и нужно: ядро приезжает вместе
с панелью (флот обновлялся так — сейчас на 26.9.30).
✅ Безопасно
- добавлять / удалять / переименовывать клиентов через панель;
- включать и настраивать подписки (
subURI,subAwgEnable); - настраивать Инси (автоопределение, Sort-Order, Hide-Url, DoH);
- менять порт/путь панели (штатный переезд, healthcheck это допускает);
- обновлять панель (
x-ui update) — патч-релизы своей минорной линии.
❌ Ломает схему (проверено на живых серверах)
- Редактировать боевые инбаунды через форму и «Сохранить». Форма
перезаписывает
streamSettingsцеликом: теряютсяsockopt.acceptProxyProtocolи массивsettings.clients— инбаунд остаётся без единого клиента, весь трафик уходит в fallback. Правка инбаунда — только через «Расширенный шаблон» (JSON) или скрипт. - Переключать режим 443 (TLS ⇄ Reality) руками в панели — сломаешь сразу три
вещи: 1) decoy №2 (fallback уедет — сайт отдаст обрыв вместо страницы),
2) proxy protocol (реальные IP клиентов превратятся в 127.0.0.1),
3) клиентов инбаунда (
settings.clientsперезапишутся — все ссылки перестанут работать). И Reality-обвязку nginx сам не создаст. Перенос схемы целиком (fallback, decoy-вхост, SNI-маршрут, профиль healthcheck) — только командамиadd-vls/add-reality. - Включать Masking (caddy-layer4) на 443 — конкурирует с nginx SNI-роутингом. Healthcheck увидит перехват и выдаст WARN.
- Вешать инбаунды/сайдкары на порт 8443 — он занят внутренним вхостом панели (nginx на 127.0.0.1:8443); сайдкар на 8443 уходит в краш-луп. Свободные альтернативы: 2087, 2083, 9443.
- Делать «двойной vless» (TLS + Reality) за одним 443 — БД панели хранит один порт на инбаунд, отдельного внешнего порта в схеме нет; клиентская ссылка сгенерируется с внутренним портом. Разные режимы — на разных серверах.
- Править «Исходящие»/«Маршрутизацию» без кнопки «Сохранить». Диалоги панели держат изменения только в превью: без «Сохранить» слева вверху и перезапуска панели всё исчезает (грабля дважды, обе — с болью).
➕ Хочешь добавить новый протокол? Правила выживания
В 3x-ui десятки протоколов (VMess, Trojan, Shadowsocks, TUIC + сайдкары) — и соблазн понаделать инбаундов велик. Скрипт настраивает и проверяет только связку vless+TLS / Hysteria2 / AmneziaWG — всё остальное добавляй по правилам:
- не трогай существующие инбаунды — они настроены и проверены установщиком; правка через форму ломает их (см. выше);
- новый протокол — только на свободный порт: не 443 (там SNI-роутинг), не 8443 (там панель) и не занятые схемой;
- помни: xray один на все инбаунды — один кривой новый инбаунд роняет ВСЕ подключения разом, а не только новый;
- после каждого изменения —
healthcheck: проверки поймают перехват 443 и неработающие инбаунды; - кнопка Masking включает caddy-layer4, который перехватывает 443 у nginx — SNI-роутинг, панель и клиенты лягут. Healthcheck это увидит ([2] WARN), но лучше просто не включать.
Если протокол нужен, а уверенности нет — опиши кейс в ишуи репозитория.
Подготовка: два домена, TLS или Reality, получение скрипта
Два домена — зачем именно два
Скрипту нужны ровно два имени с A-записями на IP сервера — и это должны быть разные имена:
| Домен | Для чего служит | Пример |
|---|---|---|
| Панельный | доступ к панели (только ты), decoy-сайт №1, подписки клиентов (subURI) |
panel.example.com |
| Основной | клиентские подключения vless/hy2, decoy-сайт №2 | example.com |
Почему нельзя обойтись одним: nginx на 443 маршрутизирует трафик по SNI — каждому сервису нужно собственное имя. Панели вдобавок нужно отдельное имя для опсеки: адрес панели не светится на домене, куда ходят клиенты.
Домены — любые (регистратор любой). Главное: A-записи обеих имён → IP сервера и рабочий резолвинг до установки — скрипт проверяет и без DNS не продолжит.
Выбор режима 443: TLS или Reality
При install скрипт спросит режим работы порта 443.
Оба режима рабочие из России — выбор по вкусу: TLS — исторический выбор флота и проще в диагностике, Reality — более сильная маскировка. Решение не окончательное: переключается в любой момент без переустановки.
| TLS | Reality | |
|---|---|---|
| Как работает | TLS терминирует nginx на 443, xray внутри на loopback, «чужие» подключения уходят на decoy №2 | xray сам терминирует Reality-протокол, «чужие» подключения — на decoy №2 |
| Настройка клиента | vless+TLS, raw, fp=firefox, flow=none | vless+Reality, fp=firefox |
| Из России | ✅ работает | ✅ работает |
Передумать — не проблема: add-vls (→ TLS) или add-reality (→ Reality)
переключает режим в обе стороны без переустановки, клиенты и ключи сохраняются.
Получение
curl -fsSL https://raw.githubusercontent.com/Shepard86rus/3x-ui-dual-domain/main/3x-ui-dual-domain.sh -o 3x-ui-dual-domain.sh
Скрипт скачивается на сервер и остаётся там — все команды дальше
(install, healthcheck, add-vls, upgrade) выполняются из этого
локального файла, интернет для них не нужен. Файл лежит в каталоге,
откуда запускали curl (обычно /root).
Версии вида
v0.4.xв тексте — номера из changelog'а самого скрипта (шапка файла). GitHub-релизы репо начинаются сv0.4.6— репо молодое; более ранние версии скрипта существуют, но публиковались только в виде файла, без релизов.
Установка (install + 2 шага настройки панели)
Требования: Ubuntu 22.04/24.04, root, два домена (см. «Подготовка») с A-записями на IP. DNS должно уже резолвиться — скрипт проверяет.
bash 3x-ui-dual-domain.sh install
Если скрипт лежит в другом месте — указывайте полный путь,
например: bash /root/3x-ui-dual-domain.sh install.
Скрипт интерактивный: спросит домены и режим 443 (TLS/Reality), предложит
выбор версии панели (Enter = последняя стабильная),
в конце сохранит креды в /root/.lucx-credentials (mode 600) и напечатает итог.
База данных фиксируется как SQLite (скрипт выставляет это сам и проверяет
после установки; PostgreSQL скриптом не поддерживается).
В конце предложит опционального Telegram-бота (см. его блок ниже) — Enter пропускает.
После install — ручная настройка в панели (2 шага)
Инсталлер создаёт инбаунды и клиентов с пустыми полями — их заполняют руками.
Шаг 1 — адрес для ссылок в инбаундах (иначе ссылки клиентов сгенерируются с внутренним адресом и работать не будут): Панель → Инбаунды → «Изменить подключение» → вкладка «Основное»:
- Стратегия адреса для ссылок → Пользовательская
- Пользовательский адрес для ссылок → основной домен (пример:
example.com) - «Сохранить изменения» → повторить для каждого инбаунда
Шаг 2 — клиенты: Панель → Клиенты → «Изменить клиента» → вкладка «Учетные данные»:
| Поле | Что делать |
|---|---|
| UUID | сгенерировать кнопкой ↻ |
| Пароль | сгенерировать кнопкой ↻ |
| ID подписки | сгенерировать кнопкой ↻ — по нему строится ссылка подписки |
| Hysteria Auth | сгенерировать кнопкой ↻ |
| Flow | Нет |
Сохранить → повторить для каждого клиента.
⚠️ healthcheck — только после этого шага и полной настройки панели. До заполнения полей проверка покажет FAIL'ы (ключей ещё нет) — это ожидаемо, а не поломка. Порядок:
install→ заполнение клиентов → остальная настройка панели (AWG и т.д.) →healthcheck.
Команды
| Команда | Что делает |
|---|---|
bash 3x-ui-dual-domain.sh install |
полная установка с нуля |
bash 3x-ui-dual-domain.sh healthcheck |
60+ проверок: сервисы, порты, ufw, nginx, серты, decoys, БД, подписки, AWG, бот, скан открытых портов |
bash 3x-ui-dual-domain.sh add-vls |
перевод схемы на TLS (из Reality), с fallback-вхостом |
bash 3x-ui-dual-domain.sh add-reality |
перевод схемы на Reality (из TLS) |
bash 3x-ui-dual-domain.sh add-hy2 |
добавление Hysteria2-инбаунда (с preflight-проверкой порта) |
bash 3x-ui-dual-domain.sh add-bot |
поставить/обновить Telegram-бота управления (env не затирает) |
bash 3x-ui-dual-domain.sh remove-bot |
удалить Telegram-бота |
bash 3x-ui-dual-domain.sh rebind-ip |
сервер сменил IP — перепрописать конфиги |
bash 3x-ui-dual-domain.sh upgrade |
обновить сам этот скрипт до новейшей версии с репо |
🤖 Telegram-бот управления (опция)
В конце install скрипт спросит, поставить ли Telegram-бота для управления этой
нодой. По умолчанию — нет: Enter просто пропускает шаг. Панель прекрасно живёт
и без бота.
Один бот = одна нода. В Telegram вы получаете карточку с кнопками:
- 📊 Статус / 🔢 Версии / 💾 Диск / 📜 Лог x-ui
- 🩺 Healthcheck / 🔐 Бэкап БД
- ♻ Рестарт панели / ♻ Рестарт xray
- ⬆ Обновление скрипта (upgrade) / ⬆ Обновление панели (x-ui update)
Опасные кнопки работают только после подтверждения «✅ Да» (живёт 120 секунд). На каждое действие бот отвечает сообщением с префиксом имени ноды — удобно, если ботов несколько на разных серверах.
Что ставится: /etc/tgbot/env (права 600) — TOKEN, CHAT_ID,
NODE_NAME, SCRIPT_PATH; тело бота /opt/tgbot/tgbot.sh (~200 строк bash,
зависимости curl + jq); systemd-юнит tgbot — включается только если токен
вписан. Ни одного нового открытого порта: бот сам ходит в api.telegram.org
(long polling), ufw не меняется.
Как получить токен:
- В Telegram: @BotFather →
/newbot→ имя → username (должен кончаться наbot). - BotFather пришлёт токен вида
1234567890:AAH7...— его и вписываете в install. Ввод скрыт (как пароль): вставили — на экране пусто — Enter. - Свой Telegram ID подскажет @userinfobot. Можно оставить пустым — тогда
бот работает в режиме обучения: на
/startпришлётACCESS_CODE: chat_id=..., число вписываете в/etc/tgbot/env(CHAT_ID=...) иsystemctl restart tgbot.
Имя ноды (шапка карточки и префикс ответов) — по умолчанию hostname; Enter при повторных запусках оставляет как есть.
Безопасность — прочитайте:
- Токен бота = root-доступ к серверу (кнопка upgrade исполняет код из main).
Храните как пароль: утёк — @BotFather →
/mybots→ API Token → Revoke, новый токен в env иsystemctl restart tgbot. - Whitelist по chat_id: чужие молча игнорируются. Вписывайте личный ID, а не ID группы — в группе бот выполнял бы команды для любого участника.
- Словарь команд фиксированный, произвольного
/execнет, аргументы из чата в shell не попадают. - Токен невидим при вводе, но проходит в аргументах curl (виден в
psв момент запроса) — на одиночном root-сервере это норма.
add-bot ставит бота на живую установку и обновляет его тело после upgrade
скрипта: пустые ответы сохраняют существующий env, непустые — заменяют (так
ротируется токен); живой сервис перезапускается сам. remove-bot сносит всё.
Проверка — healthcheck, пункт [12]: сервис, автозапуск, токен, whitelist,
версия тела. Если бот не ставился — строка-пропуск, не FAIL.
Обновление панели
x-ui update bash 3x-ui-dual-domain.sh healthcheck
Неинтерактивно (cron/скрипты, без терминала) — обновление задаёт два вопроса:
подтверждение обновления (y) и обязательное SSL-меню (4 = Skip SSL — панель за
вашим nginx). Без ответов: подтверждение = тихая отмена, а пустой ответ в SSL-меню
ставит Let's Encrypt-сертификат через порт 80 и зависает. Правильно:
printf 'y\n4\n' | x-ui update
После обновления проверьте факт, а не exit-код — версия должна смениться:
grep -aoE 'v3\.[0-9]+\.[0-9]+-lucx\.[0-9]+' /usr/local/x-ui/x-ui | tail -1
Начиная с панели lucx.291 SSL-меню при обновлении исчезло — достаточно y
(старый вариант y\n4\n тоже работает: лишняя «4» безвредна).
Про песочницу и AWG: скрипт v0.4.9+ ставит drop-in песочницы
(/etc/systemd/system/x-ui.service.d/20-awg-amnezia.conf). На панели lucx.291
он обязателен — без него панель не может писать конфиги AWG. С lucx.292 панель
пишет их нативно — дропин больше не нужен, можно удалить:
rm /etc/systemd/system/x-ui.service.d/20-awg-amnezia.conf
systemctl daemon-reload && systemctl restart x-uiНаличие дропина на lucx.292+ безвредно.
Патч-релизы своей минорной линии безопасны. Перед мажорными релизами (смена схемы БД) сверьтесь с release notes панели — миграции необратимы.
Эксплуатация: переезд на новый IP, типичные проблемы
Переезд на новый IP
У регистратора поменяйте A-записи, затем:
bash 3x-ui-dual-domain.sh rebind-ip
Типичные проблемы
| Симптом | Причина / лечение |
|---|---|
| Decoy №2 отдаёт 301-цикл в TLS-режиме | fallback уехал на :80 — чинится в v0.4.0+; обновите скрипт |
xray в краш-лупе, в логе invalid geodata asset file |
зеркало отдало 404 на гео-файл — гео-страж (v0.4.2+) докачивает сам; проверьте hc |
AWG-инбаунд не поднимается, interface already up |
баг панелей до lucx.289 — обновите панель (x-ui update), она поднимет осиротевший интерфейс за ≤10 с |
| hc FAIL «слушает и разрешён ufw, но НЕ из схемы» | что-то слушает публичный порт вне белого списка (22/80/443) — сайдкар/тестовый инбаунд; уберите или внесите в /root/.lucx-public-tcp-ports.txt осознанно |
| Пропали клиенты инбаунда после правки через форму | та самая перезапись settings.clients — клиенты восстанавливаются из бэкапа БД (/etc/x-ui/x-ui.db), инбаунд пересоздать |
Поддержать проект
Если инструмент оказался полезным — можно поддержать разработку:
- Boosty (разовый донат): boosty.to/shepard86rus/donate
- ЮMoney: yoomoney.ru/to/4100175017397
Спасибо! Вопросы по проекту — в ишуи или в комментарии на Boosty.
Лицензия
GPL-3.0 — © Shepard86rus.
Сторонние компоненты (устанавливаются скриптом)
Скрипт сам не содержит их код — устанавливает как внешние пакеты, с благодарностью авторам:
- lucx-ui — панель управления, автор AlexeyLCP (форк 3x-ui от MHSanaei) — GPL-3.0
- AmneziaWG — ядро Amnezia WireGuard — GPL-2.0/MIT
- Xray-core — транспортное ядро (пин lucx-ui)
Это неофициальный инструмент, не связан с авторами перечисленных проектов.