VPN·HUB·CRACK Почему ноды перестают работать: память, логи, дескрипторы · VPN HUB CRACK
Все статьи
Инфраструктура и защита FREE

Почему ноды перестают работать: память, логи, дескрипторы

Обновлено 25 августа 2026 Проверено

Почему ноды перестают работать: память, логи, дескрипторы

Нода отвалилась ночью. В панели красная, клиенты жалуются, по SSH зайти удаётся не с первого раза. Перезагрузили — заработало. Через неделю повторилось.

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

Память

Xray-core растёт по RAM и не всегда отдаёт её обратно. Это свойство ядра, а не дефект конкретной панели — в апстриме обсуждают годами.

Свежий повод: начиная с Xray-core v25.10.15 поменялись значения xmux для транспорта XHTTP. Параметр maxConcurrency был 32–16, стал 1. Каждый запрос теперь открывает своё соединение, и на активной ноде это тысячи одновременных сессий вместо десятков. Память растёт вместе с ними.

Что происходит дальше, зависит от одной строчки в конфигурации.

Без лимита ядро зовёт OOM killer, и тот выбирает жертву по «badness» — грубо говоря, по объёму потребления. Обычно это Xray. Но перед убийством система успевает поболтаться в свопе, и скорость проседает у всех клиентов сразу. А жертвой может оказаться dockerd или sshd, и тогда вы теряете не сервис, а доступ к серверу.

С лимитом контейнер живёт в своей cgroup со своим потолком. Xray упирается в него, ядро убивает только его, restart: always поднимает обратно за секунду. SSH и Docker целы, а счётчик показывает, сколько раз это случилось.

Лимит не лечит утечку. Он превращает «сервер отвалился, непонятно почему» в «сервис перезапустился за секунду, вот счётчик».

Как проверить за минуту

Счётчик убийств по памяти лежит в cgroup процесса:

PID=$(docker inspect -f '{{.State.Pid}}' remnanode)
grep oom_kill /sys/fs/cgroup$(awk -F: '$1=="0"{print $3}' /proc/$PID/cgroup)/memory.events

Ненулевое число означает, что сервис уже убивали. Рядом полезно посмотреть, сколько раз контейнер перезапускался:

docker inspect -f '{{.RestartCount}} {{.State.OOMKilled}}' remnanode

Тридцать четыре перезапуска и true во втором поле закрывают вопрос.

У 3x-ui выигрыш от лимита больше, чем кажется. Панель сама читает cgroup-лимит и выставляет Go-шный GOMEMLIMIT в 90% от него, то есть сборщик мусора начинает работать до того, как сработает OOM. Без лимита этого механизма нет: Go считает, что памяти бесконечно.

Сколько давать

Системе нужно примерно фиксированное количество памяти: dockerd, sshd, systemd, немного page cache. На 512 МиБ это около 190 МиБ. Отсюда формула вместо лестницы из процентов:

запас = ограничить(192 MiB … 20% RAM … 1024 MiB)
лимит = RAM − запас
RAM хоста Лимит сервиса Остаётся системе
512 MiB 320 MiB 192 MiB
1 GiB 820 MiB 204 MiB
2 GiB 1.6 GiB 409 MiB
8 GiB 7.0 GiB 1 GiB

Лимит ниже рабочего пика опаснее отсутствия лимита: сервис уйдёт в цикл перезапусков. Сначала посмотрите пик потребления, потом берите значение с запасом над ним.

Ставить лимит правкой docker-compose.yml не надо. Установщики панелей регенерируют этот файл при обновлении, и настройка исчезнет молча. Кладите отдельный docker-compose.override.yml рядом — Compose подхватит его сам, а откат сведётся к удалению одного файла.

Логи

Docker с драйвером json-file пишет без ротации. Лог контейнера растёт, пока не кончится диск.

У 3x-ui лог самой панели ротируется встроенным lumberjack: 10 МБ, пять файлов, семь дней. Тут порядок. А вот access.log самого Xray пишется ядром напрямую, и не ротируется ничем — ни Xray, ни панелью. В трекере это issue #1234, «Free disk space is constantly decreasing».

Дальше цепочка неочевидная. Диск кончается, SQLite отвечает database is locked, панель перестаёт работать. Человек ищет проблему в базе, а она на диске.

Проверить:

du -h $(docker inspect -f '{{.LogPath}}' remnanode)
df -h /

Четыре гигабайта в первой строке встречаются чаще, чем хотелось бы.

Лечится ротацией на уровне сервиса:

logging:
  driver: json-file
  options:
    max-size: "10m"
    max-file: "3"

Для нативной установки 3x-ui нужен logrotate, причём с copytruncate. Xray держит файл открытым и не переоткрывает его по сигналу, поэтому обычный create отправит запись в никуда. Если IP-limit не используется, access log проще выключить совсем: в свежих версиях 3x-ui механизм лимита его больше не читает.

Дескрипторы

Тут разница между панелями заметная.

Remnawave в официальном compose уже прописывает nofile: soft 1048576 / hard 1048576. Делать нечего.

3x-ui не задаёт ничего: ни install.sh, ни x-ui.service, ни Dockerfile. Xray запускается дочерним процессом внутри x-ui и наследует то, что дал systemd по умолчанию. Для сравнения, официальный установщик XTLS/Xray-install ставит своему юниту LimitNOFILE=1000000.

Под нагрузкой это выглядит как too many open files и обрывы у части клиентов при живом и здоровом на вид процессе.

cat /proc/$(pgrep -f xray | head -1)/limits | grep 'open files'

Тысяча двадцать четыре в первой колонке — приговор для ноды, которая держит хоть сколько-нибудь клиентов.

Заодно про BBRv3, которого нет

Раз уж речь о тюнинге. В сети много статей 2026 года про «BBRv3 в ядре 6.x». Все они путают v3 с v1.

BBRv3 в mainline не влит. Проверяется за минуту по net/ipv4/tcp_bbr.c в master torvalds/linux: там код 2016 года и только BBR v1. Разработчик Google Нил Кардвелл подтверждал это в рассылке bbr-dev в марте 2025.

В стоковых Debian 12/13 и Ubuntu 22.04/24.04/26.04 доступен только bbr первой версии. Он включается одним sysctl, ничего не стоит и на маршрутах с потерями даёт заметный выигрыш. Гнаться за v3 через кастомное ядро имеет смысл только на трансграничных маршрутах с реальной перегрузкой, и цена там высокая: ядра XanMod не подписаны для Secure Boot, а apt autoremove умеет снести старое ядро и оставить сервер без запасного пункта в GRUB.

Проверять надо не имя в sysctl, а версию модуля:

modinfo tcp_bbr | grep -i version

Имени bbr3 может не быть вообще: у части сборок v3 подменяет собой имя bbr.

И про файрвол, пока не поздно

Отдельная категория мёртвых нод — те, где файрвол настроили правильно, а проверили неправильно.

Правила применили, из той же SSH-сессии проверили доступ, всё работает, сессию закрыли. Больше на сервер зайти не удалось.

Открытая сессия живёт по ct state established и продолжает работать, даже когда новые подключения уже отбиваются. Она ничего не доказывает. Проверять надо вторым подключением, не закрывая первое.

Второе правило: ставьте откат по таймеру до того, как применяете правила.

systemd-run --on-active=300 --unit=fw-revert --collect nft delete table inet myfw

Не подтвердили за пять минут — правила снялись сами. Без консоли у провайдера это не перестраховка, а единственная страховка.

И третье: не смешивайте ufw с нативными правилами nftables. Документация Ubuntu Security запрещает это прямым текстом — ufw работает через утилиты iptables, и поверх него nft-правила дают «правило есть, а трафик идёт мимо». Отдельная версия той же ловушки: Docker публикует порты через цепочку FORWARD и обходит ufw полностью.

Что делать со всем этим сразу

Перечисленное закрывается руками за вечер. Если серверов больше одного, вечер превращается в неделю.

Мы собрали это в один скрипт: HUBTune. Он находит ноду сам по меткам Docker, показывает план до применения и откатывается одной командой. Опасные вещи требуют доказательств: правила файрвола подтверждаются из нового подключения, пароли SSH не отключатся, пока скрипт не найдёт другой способ войти, ядро не поставится на машину, где оно не загрузится.

curl -fsSL https://raw.githubusercontent.com/qwe8nxtroud/HUBTune/main/hubtune.sh -o hubtune.sh
less hubtune.sh
sudo bash hubtune.sh status

Команда status только читает и показывает, что не так прямо сейчас. С неё и стоит начать, даже если ставить ничего не собираетесь.