Срочное обновление Subscription Page Remnawave: проверка версии, выбор тега, откат

23 мин чтенияОбновлено 21.10.2026

Как узнать версию сабпейджа, выбрать тег под свою панель, обновиться с точкой отката и вернуться назад, если что-то сломалось. Отдельно разобрано, что про уязвимость подтверждено официально, а что нет.

Что потребуется
SSH-доступ к серверу, где работает Subscription Page, и право запускать docker. Известна версия панели (из лога сабпейджа или из дашборда). Окно на несколько минут: пока контейнер пересоздаётся, подписки не отдаются. Команды в гайде по большей части не прогонялись на живом сервере, поэтому после каждого шага есть проверка.
Проверено автором
Subscription Page 7.2.4, 7.2.5, 7.2.6, 8.0.0 (теги, образы и исходники сверены с GitHub и Docker Hub 15-16.10.2026); панель backend 2.8.1 и 3.4.3-3.4.5 (теги и advisory)

Бесплатный материал · можно читать без регистрации

Подставить мои значения в команды

Значения хранятся только в вашем браузере. В командах и тексте гайда они подставятся вместо шаблонов, кнопка «Копировать» скопирует уже готовый вариант.

19 июня 2026 года в Telegram-канале Remnawave закрепили сообщение о критической уязвимости в Subscription Page (сабпейдже) с просьбой обновиться немедленно. Подробностей в нём нет, надёжного способа проверить конкретную сборку тоже нет: решает номер версии. Порядок действий: определить версию, выбрать тег под свою панель, обновиться с точкой отката, проверить результат, при необходимости вернуться назад. В конце типичные ошибки после обновления и меры, которые сужают поверхность вокруг сервиса.

1. Что известно об уязвимости, а что нет

Сообщение Security Notice вышло 19 июня 2026 года (около 20:22 UTC) и закреплено. По нему: пользователь сообщил о критической уязвимости в Subscription Page, обновиться до 7.2.5 нужно немедленно, подробности обещаны в течение трёх месяцев с даты объявления. К сообщению приложена команда обновления для установки по официальной инструкции.

Больше официально не сказано ничего: ни класса уязвимости, ни условий эксплуатации, ни номера CVE. На 15-16 октября 2026 года в репозитории subscription-page нет опубликованных security advisory и нет файла SECURITY.md. Трёхмесячный срок закончился около 19 сентября, опубликованного разбора найти не удалось. Если он появится, то в репозитории или в канале.

Поэтому в гайде нет механики уязвимости и нет способа проверить, затронута ли именно ваша сборка. Рабочее правило: 7.2.5 и выше содержат исправление, всё ниже считается уязвимым.

Что видно в публичной истории репозитория. Между тегами 7.2.4 и 7.2.5 три коммита: рефакторинг чтения конфигурации, обновление зависимостей и сам релиз. В заметке к 7.2.5 на форуме docs.rw написано, что исправлена ошибка, из-за которой сервис мог неверно считывать данные из .env или environment и применять неправильную конфигурацию. Связана ли эта правка с объявленной уязвимостью, источники не говорят. Слова «уязвимость» в заметках и сообщениях коммитов нет, поэтому по их тону срочность оценивать нельзя.

В 7.2.6 есть ещё одна правка, в сообщении коммита она помечена security: IP клиента определяется через доверенный прокси, а не берётся из заголовков, которые клиент может подделать. Для настройки добавлена переменная TRUST_PROXY. В объявлении от 19 июня эта правка не упомянута. Минимальной целью разумно брать 7.2.6, а не 7.2.5.

ВерсияДата тега (UTC)КоммитНачало digest образаЧто известно
7.2.414-15.06.2026a59fa9db99c2715551aДо исправления. Обновление зависимостей, правка передачи shortUuid
7.2.516.06.2026de068513b8160459fe0Версия из объявления. Рефакторинг конфигурации, исправление чтения .env
7.2.624.06.20265fefecdda5ee26ec70eПоследняя в ветке 7. Добавлен TRUST_PROXY, защита от подмены IP
8.0.001.08.20262a125d104e8d479afb3Ветка 8 под панель 3.x. Тот же образ, что у тега latest

У репозитория нет GitHub Releases, только теги. Образы публикуются на Docker Hub (remnawave/subscription-page), дайджесты в таблице взяты оттуда. Это дайджесты тегов верхнего уровня; у скачанного образа RepoDigests обычно показывает тот же дайджест, но если он не совпал, ориентируйтесь на лог (способ 1).

2. Чем рискует необновлённый сабпейдж

Сабпейдж доступен из интернета и ходит в API панели с токеном (переменная REMNAWAVE_API_TOKEN, токен создаётся в дашборде: Remnawave Settings, API Tokens). Входа по паролю у него нет: подписку получает любой, кто знает ключ в адресе. Что именно даёт уязвимость, неизвестно, поэтому оценка рисков опирается на роль сервиса, а не на факты об эксплуатации.

Признаков взлома нет. Перевыпуск API-токена панели после обновления это страховка, а не следствие найденных фактов; дёшево сделать (шаг 6 в разделе 8).

3. Как узнать, какая версия стоит

Сначала имя контейнера. Штатное remnawave-subscription-page, но установщики и ручной docker run могут назвать иначе. Дальше в командах стоит штатное имя, при другом подставьте своё.

Код
docker ps -a --format '{{.Names}} | {{.Image}} | {{.Status}}' | grep -i sub

Тег в этой строке говорит только о том, что попросили при установке. С 1 августа 2026 года тег latest указывает на 8.0.0 (тот же образ, что у тега 8.0.0), а у контейнера, поднятого весной, latest мог быть 7.2.1. Реальную версию показывают лог, содержимое образа или дайджест.

Способ 1, самый простой. При старте сервис печатает рамку с заголовком Remnawave Subscription Page и номером версии.

Код
docker logs remnawave-subscription-page 2>&1 | grep -m1 'Subscription Page v'

Пустой вывод не значит, что версия старая. Рамка печатается один раз при запуске, а у контейнера, который работает месяцами, она давно вытеснена из лога. Тогда перезапуск покажет версию за пару секунд (или используйте способ 2 или 3 ниже). Рядом печатается строка Connected to Remnawave с номером панели, она понадобится в разделе 4.

Код2 строки
docker restart remnawave-subscription-page && sleep 5 && \
docker logs --tail 60 remnawave-subscription-page 2>&1 | grep -E 'Subscription Page v|Connected to Remnawave'
Внимание

На время перезапуска подписки не отдаются. Кроме того, внутренний секрет сервиса генерируется заново при каждом старте (его создаёт docker-entrypoint.sh), а доступ к /assets проверяется по cookie, подписанной этим секретом. Поэтому открытые у клиентов браузерные страницы подписки могут потерять оформление, пока их не обновят. Делайте это не в час пик.

Способ 2, без перезапуска, для образов ветки 7. В них лежит package.json с номером версии:

Код
docker exec remnawave-subscription-page grep -m1 '"version"' /opt/app/package.json

В образе 8.0.0 этого файла нет, и ответ «No such file or directory» тоже полезен: значит, это ветка 8.

Способ 3, по дайджесту. Он считается из содержимого образа и не зависит от тега.

Код2 строки
docker image inspect "$(docker inspect remnawave-subscription-page --format '{{.Image}}')" \
  --format '{{index .RepoDigests 0}}'

Сравните первые двенадцать символов после sha256: с таблицей из раздела 1. Совпадение называет версию. Несовпадение ничего не доказывает: образ мог быть скачан иначе или собран локально. Ответ «index out of range» означает, что у образа нет RepoDigests (его собрали на этом сервере или переименовали), и тогда остаётся только рамка в логе.

Совет

label-ы образа смотреть бесполезно. В Dockerfile заданы только title, description, url, source, vendor, licenses и documentation, поля с версией среди них нет.

Если версию определить не удалось, считайте сборку уязвимой. Это касается и самосборных образов и форков: обновление из реестра их не лечит, нужна пересборка на актуальной основе.

4. Какой тег ставить

Тег выбирается по версии панели. Её показывает строка Connected to Remnawave vX.Y.Z в логе сабпейджа или дашборд.

ПанельТег сабпейджаОснование
2.x7.2.6Ветка 7 собрана на @remnawave/backend-contract 2.7.2. Исправление из 7.2.5 и защита от подмены IP внутри
3.0.0 - 3.4.2Сначала обновить панель, потом 8.0.0У backend этих версий опубликована критическая уязвимость (раздел 5)
3.4.3 и выше8.0.0В заметке к 8.0.0 рекомендована панель 3.0.0 и выше, в зависимостях контракт 3.1.1

О достоверности. Официально сказано одно: для 8.0.0 рекомендована панель 3.0.0 и выше. Строка «для 2.x нужна 7.2.6» восстановлена по версиям контракта в зависимостях (2.7.2 в 7.2.x, 3.1.1 в 8.0.0) и по датам релизов. Разработчики не заявляют, что 8.0.0 работает на панели 2.x, а 7.2.6 на панели 3.x. Проверьте на своей установке: после запуска в логе должна быть строка [OK] Connected to Remnawave и подписка должна отдаваться.

Что меняется в 8.0.0 по сравнению с 7.2.6:

  • сервис больше не сжимает ответы (сказано в заметке к релизу), сжатие нужно включить на реверс-прокси (раздел 9);
  • статика с пути /assets отдаётся с max-age на 7 дней и признаком immutable, поэтому изменённое оформление может доходить до клиентов не сразу;
  • в образ добавлен HEALTHCHECK и адрес /internal/health, отвечающий только с loopback самого контейнера;
  • в образе больше нет /opt/app/package.json;
  • обновлены зависимости, контракт и типы.

Тег latest не используйте: он ничего не фиксирует, и 1 августа он без предупреждения переехал с ветки 7 на ветку 8. Тег dev (образ от 23 августа) не релиз. Для максимальной предсказуемости пропишите в compose дайджест.

5. Если панель 3.0.0 - 3.4.2, начать с неё

У backend опубликованы два advisory, оба про панель, а не про сабпейдж.

GHSA-8mcp-v46j-fp26 (31 августа 2026), CVSS 9.8, критическая. Затронуты backend 3.0.0-3.4.2, исправлено в 3.4.3. Суть: проверка доступа к служебному пути /api/backend-tools различала регистр символов, а маршрутизация нет, поэтому неавторизованный запрос мог дойти до интерфейса очередей задач, а через них, по описанию advisory, до данных подписок и, в цепочке с другими слабостями, вплоть до запуска чужого кода на нодах и в контейнере базы. Пример запроса здесь не приводится: на порядок действий он не влияет, лечит только обновление. Последний тег backend на 16 октября 2026 года: 3.4.5.

GHSA-985p-44h5-v3pq / CVE-2026-39880 (7 апреля 2026), оценка 5.0, средняя. Затронут backend 2.7.4 и ниже: гонка при регистрации устройств позволяла обойти лимит HWID. В advisory исправленной названа 2.7.5, но такого тега в репозитории нет, после 2.7.4 идёт 2.8.0. Для ветки 2.x ориентируйтесь на 2.8.1, это последний тег ветки.

Панель обновляется по документации docs.rw (docs.rw/install/upgrading), в таком порядке: сначала панель, потом ноды. Перед этим сделайте копию базы и .env панели по инструкции из документации: в этом гайде команды бэкапа панели не приводятся, потому что имена контейнеров и пользователей базы зависят от установки. Если в compose панели стоит :latest, замените его на явный тег (например 3.4.5): иначе pull возьмёт то, что окажется latest в день обновления.

Код
cd /opt/remnawave && docker compose pull && docker compose down && docker compose up -d && docker compose logs -f
Внимание

Переход с 2.x на 3.x это мажорный релиз со сменой API (пользователи идентифицируются числовым id вместо UUID, JWT_AUTH_SECRET переименован в APP_SECRET), его описывают заметки к релизу панели v3.x.x на форуме docs.rw. Если у вас панель 2.x и в её compose стоит :latest, не запускайте на ней pull, пока не решили переходить: он поднимет мажорную версию. Для 2.x достаточно оставаться на 2.8.1 и 7.2.6. Ноду не поднимайте выше версии панели: нода 3.3.0 совместима только с панелью 3.3.0 и выше, обратное допустимо.

После обновления панели вернитесь к разделу 4 и берите 8.0.0.

6. Подготовка: точка отката и опись навешанного

Откат возможен, только если заранее записано, куда возвращаться. Команды ниже ничего не меняют, кроме создания каталога и копий. Каталог закрыт правами только для root: в .env лежит API-токен панели открытым текстом.

Код
mkdir -p /root/subpage-backup
Код
chmod 700 /root/subpage-backup

Запишите дайджест текущего образа (он же выводится на экран):

Код2 строки
docker image inspect "$(docker inspect remnawave-subscription-page --format '{{.Image}}')" \
  --format '{{index .RepoDigests 0}}' | tee /root/subpage-backup/digest.txt

Если команда вернула ошибку «index out of range», образ собран локально, и точкой отката служит ваш исходник и его коммит. Иначе в файле будет запись remnawave/subscription-page@sha256: и длинный хэш после неё.

Теперь определите, чем управляется контейнер, и скопируйте файлы, из которых он поднят:

Код4 строки
docker inspect remnawave-subscription-page --format 'проект: {{index .Config.Labels "com.docker.compose.project"}}
сервис: {{index .Config.Labels "com.docker.compose.service"}}
каталог: {{index .Config.Labels "com.docker.compose.project.working_dir"}}
файл: {{index .Config.Labels "com.docker.compose.project.config_files"}}'

Пустые строки значат, что контейнер поднят командой docker run, а не через compose (тогда переходите к варианту В в разделе 7 и пропустите копирование compose). Если compose есть, выполняйте команды ниже. Команды ниже выполняются подряд в одной сессии терминала: переменные сохраняются до закрытия окна.

Определите путь к compose-файлу и сохраните его в переменную CFG (если файлов несколько, берётся первый):

Код
CFG=$(docker inspect remnawave-subscription-page --format '{{index .Config.Labels "com.docker.compose.project.config_files"}}')
Код
CFG=${CFG%%,*}

Скопируйте compose-файл в каталог отката:

Код
cp -a "$CFG" /root/subpage-backup/compose.yml

Найдите подключённый .env:

Код
grep -n 'env_file' -A2 "$CFG"

Последняя команда показала, какой .env подключён (путь в env_file считается от каталога compose-файла). Скопируйте его рядом и закройте права; вместо /путь/к/.env подставьте найденный путь:

Код
cp -a /путь/к/.env /root/subpage-backup/env
Код
chmod 600 /root/subpage-backup/env

И последнее, что стоит увидеть до обновления: не навешано ли на контейнер что-то, что пересоздание сотрёт.

Подключённые файлы и каталоги:

Код
docker inspect remnawave-subscription-page --format '{{json .Mounts}}'

Файлы, изменённые внутри работающего контейнера:

Код
docker diff remnawave-subscription-page | head -20

Что показывает вывод:

  • В Mounts подключённые файлы и каталоги (свой шаблон, тема). Они переживут пересоздание.
  • В docker diff строки означают, что файлы правили прямо внутри работающего контейнера. Обновление образа сотрёт эти правки без ошибок в логе. Заберите файлы себе (docker cp) и после обновления верните через монтирование.
Совет

Если подключён единственный файл (не каталог), открывайте его в редакторе и сохраняйте поверх того же файла, потому что sed -i подменяет файл новым. sed -i создаёт новый файл, и контейнер продолжает видеть старый inode: правка не применится, а ошибки не будет.

7. Обновление

Порядок: прописать явный тег, скачать образ, потом пересоздать контейнер. Тогда сервис простоит только те секунды, что проходят между остановкой старого контейнера и запуском нового, без ожидания загрузки образа. Тег берите из раздела 4. В примерах 7.2.6, для панели 3.4.3+ подставьте 8.0.0.

Вариант А. Отдельный compose, как в официальной инструкции (каталог /opt/remnawave/subscription). Откройте compose-файл в этом каталоге и замените в нём строку image: remnawave/subscription-page:latest на такую:

Код
image: remnawave/subscription-page:7.2.6
Код
cd /opt/remnawave/subscription && docker compose pull && docker compose up -d && docker compose logs -f --tail 50

Официальная команда в объявлении и в docs.rw содержит ещё down между pull и up. Без down compose сам пересоздаёт контейнер, если изменился образ или конфигурация, и простой короче. Выход из логов по Ctrl+C контейнер не останавливает.

Если после up -d в логе осталась старая версия, пересоздайте контейнер принудительно в том же каталоге (либо используйте официальную последовательность с down):

Код
docker compose up -d --force-recreate

Вариант Б. Сабпейдж описан в общем compose вместе с панелью. Не запускайте pull и up без имени сервиса: вместе со страницей подписки подтянутся и другие образы файла. Название сервиса видно в выводе команды из раздела 6. Вместо /каталог/compose подставьте каталог, где лежит ваш compose-файл.

Код
cd /каталог/compose && docker compose pull remnawave-subscription-page && docker compose up -d remnawave-subscription-page

Вариант В. Контейнер поднят через docker run или чужой скрипт. Сначала запишите всё, что к нему подключено (в Binds и PortBindings токенов нет). Файл .env с REMNAWAVE_PANEL_URL и REMNAWAVE_API_TOKEN должен существовать: если контейнер запускали с ключами -e, соберите такой файл вручную и не выводите переменные окружения контейнера в чат или в тикет, там токен. Старый контейнер не удаляется, а переименовывается: если новый не поднимется, его можно вернуть.

Код
docker inspect remnawave-subscription-page --format '{{json .HostConfig.Binds}} {{json .HostConfig.PortBindings}}'

Скачайте новый образ:

Код
docker pull remnawave/subscription-page:7.2.6

Остановите старый контейнер:

Код
docker stop remnawave-subscription-page

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

Код
docker rename remnawave-subscription-page remnawave-subscription-page-old

Запустите новый контейнер. Сеть и порт подставьте свои, вместо /путь/к/.env укажите путь к вашему env-файлу, а для всего, что нашли в Binds, добавьте ключи -v:

Код4 строки
docker run -d --name remnawave-subscription-page --hostname remnawave-subscription-page \
  --restart always --env-file /путь/к/.env \
  --network remnawave-network -p 127.0.0.1:3010:3010 \
  remnawave/subscription-page:7.2.6

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

Код
docker rm -f remnawave-subscription-page
Код
docker rename remnawave-subscription-page-old remnawave-subscription-page
Код
docker start remnawave-subscription-page

Когда новый контейнер работает, старый удаляется:

Код
docker rm remnawave-subscription-page-old

У старого контейнера политика перезапуска сохранится: после перезагрузки сервера он может подняться вместе с новым и занять порт, поэтому не оставляйте -old надолго и сразу отключите ему автозапуск:

Код
docker update --restart=no remnawave-subscription-page-old

Заодно перенесите запуск в compose-файл: так проще фиксировать тег и откатываться.

Если сервис поднимает за вас установщик стороннего автора (меню, скрипт), правьте его compose, а не меню. При следующем заходе в меню строка image может снова стать latest, поэтому после каждого захода перечитывайте её.

8. Проверка после обновления

Проверка идёт изнутри наружу: снаружи здоровый сервис и сломанный нередко выглядят одинаково. Кода ошибки сервис не возвращает: он обрывает соединение, и прокси превращает это в 502 или пустой ответ.

Шаг 1. Лог старта. Нужны рамка с новой версией и строка [OK] Connected to Remnawave. Строка Connection to Remnawave Panel failed! означает, что сервис не достучался до панели и завершился с ошибкой.

Код
docker logs remnawave-subscription-page 2>&1 | grep -E 'Subscription Page v|Connected to Remnawave|failed|FATAL|rror'

Шаг 2. Состояние контейнера.

Код
docker ps --format '{{.Names}} | {{.Status}}' | grep -i sub

Состояние встроенного healthcheck (только для 8.0.0):

Код
docker inspect remnawave-subscription-page --format '{{.State.Health.Status}}'

Ожидается healthy (сразу после старта до 20 секунд может быть starting). На 7.2.x проверки в образе нет, и команда вернёт ошибку шаблона: это нормально, живость показывают рамка в логе и состояние Up. Адрес проверки отвечает только на loopback самого контейнера, с хоста запрос приходит с адреса docker-сети и ответа не получит, поэтому следующая команда идёт через docker exec:

Код
docker exec remnawave-subscription-page sh -c 'curl -fsS http://127.0.0.1:${APP_PORT:-3010}/internal/health'

Ожидаемый ответ: OK. Это только для 8.0.0, на 7.2.x такого адреса нет. curl в образах есть (его ставит Dockerfile).

Шаг 3. Подписка снаружи, двумя клиентами. Ответ для браузера и для клиентского приложения формируется разными ветками кода, и одна может работать при сломанной другой. Вместо sub.вашдомен.com подставьте домен подписки, вместо КЛЮЧ_ПОДПИСКИ реальный ключ из ссылки подписки (после префикса, если он у вас задан). Какой User-Agent считается браузером, по коду не проверялось, поэтому если первая команда вернула не text/html, ориентируйтесь на ответ в браузере.

Код
curl -sS -o /dev/null -w '%{http_code} %{content_type}\n' -A 'Mozilla/5.0' https://sub.вашдомен.com/КЛЮЧ_ПОДПИСКИ

Ожидается 200 и text/html. Теперь запрос от клиентского приложения:

Код
curl -sS -A 'Happ/4.12.0' https://sub.вашдомен.com/КЛЮЧ_ПОДПИСКИ | wc -c

Тело ответа должно быть заметно больше нескольких сотен байт. Маленький ответ при коде 200 значит, что конфигов в нём нет: разбираться надо в шаблонах и настройках подписки в панели, а не в контейнере.

Шаг 4. Сжатие (только для 8.0.0). Проверка, что прокси сжимает ответы:

Код
curl -sSI -H 'Accept-Encoding: gzip' -A 'Mozilla/5.0' https://sub.вашдомен.com/КЛЮЧ_ПОДПИСКИ | grep -i content-encoding

Если строки content-encoding: gzip нет, сжатие на прокси не включено (раздел 9). Для Caddy с encode zstd строка может быть zstd, если клиент её принимает; в запросе выше запрошен только gzip.

Шаг 5. IP клиентов. С 7.2.6 адрес клиента определяется по настройке TRUST_PROXY, по умолчанию 1: один доверенный прокси перед сервисом, например nginx или Caddy на том же хосте. Адрес пересылается в панель. Если в вашей версии панели есть история запросов подписки, откройте её у любого пользователя: там должен быть адрес клиента. Адрес вашего прокси значит, что число доверенных звеньев указано неверно. Допустимые значения: true, false, неотрицательное число или список предустановок (loopback, linklocal, uniquelocal) и адресов с подсетями через запятую. Правка вступает в силу после пересоздания контейнера. Если перед nginx стоит ещё и CDN, по логике Express нужно TRUST_PROXY=2, а nginx должен передавать цепочку целиком: proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for, а не только $remote_addr. Эту схему на живом сервере проверьте отдельно.

Шаг 6. Перевыпуск API-токена как страховка (по желанию). Создайте новый токен в дашборде (Remnawave Settings, API Tokens), впишите его в REMNAWAVE_API_TOKEN, пересоздайте контейнер, убедитесь, что подписки отдаются, и только потом удалите старый токен. Если контейнер поднят через docker run, пересоздайте его командой из варианта В.

Код
cd /каталог/compose && docker compose up -d --force-recreate remnawave-subscription-page

9. Прокси после перехода на 8.0.0

Раньше сабпейдж сжимал ответы сам, с 8.0.0 это должен делать прокси. Без сжатия сервис продолжит работать, просто подписка и страница будут отдаваться тяжелее.

Конфигурация прокси правится в файле, где описан домен подписки. Найдите его (вместо ВАШ_ДОМЕН_ПОДПИСКИ подставьте домен, по которому открывается подписка; поиск идёт и по каталогам Docker-установки в /opt):

Код
grep -rl "server_name ВАШ_ДОМЕН_ПОДПИСКИ" /etc/nginx /opt

Откройте найденный файл (вместо ПУТЬ_К_ФАЙЛУ подставьте путь из вывода команды; сохранение в nano: Ctrl+O, затем Enter, выход: Ctrl+X). Тот же файл правится ниже для заголовков и лимита запросов:

Код
nano ПУТЬ_К_ФАЙЛУ

Для nginx добавьте строки ниже в блок server или location этого файла. Типы подберите по заголовку Content-Type реальных ответов (curl -I) и добавьте недостающие; text/html nginx сжимает и так.

Код4 строки
gzip on;
gzip_comp_level 6;
gzip_min_length 256;
gzip_types text/plain text/css application/json application/javascript text/javascript;

Caddy: добавьте строку в блок сайта в Caddyfile (обычно это /etc/caddy/Caddyfile, при установке в Docker он лежит рядом с docker-compose.yml). Открыть файл:

Код
nano /etc/caddy/Caddyfile

Строка для блока сайта:

Код
encode zstd gzip

Заодно проверьте, что прокси передаёт сервису два заголовка, без которых он обрывает соединение: X-Forwarded-For и X-Forwarded-Proto: https. Caddy ставит оба сам (проверьте, что клиент подключается по https). В nginx их нужно прописать вручную; пример для одного прокси перед сервисом:

nginx6 строк
location / {
    proxy_pass http://127.0.0.1:3010;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $remote_addr;
    proxy_set_header X-Forwarded-Proto $scheme;
}

Перед перезагрузкой проверьте синтаксис. Для nginx проверка и перезагрузка:

Код
nginx -t
Код
nginx -s reload

Для Caddy:

Код
caddy validate --config /etc/caddy/Caddyfile
Код
caddy reload --config /etc/caddy/Caddyfile

10. Откат

Своих данных страница подписки не хранит (источником служит панель), поэтому откат состоит в возврате образа. Ограничения: возврат ниже 7.2.5 возвращает уязвимость; если сломала 8.0.0, цель отката 7.2.6, а не то, что стояло раньше; 7.2.6 на панели 3.x разработчики не заявляют, проверьте строку Connected to Remnawave в логе. Сохранённый в разделе 6 дайджест подходит, только если тогда стояла версия 7.2.5 или новее.

В compose замените строку image на нужный тег или дайджест и пересоздайте контейнер.

Если возвращаетесь по тегу, строка image такая:

Код
image: remnawave/subscription-page:7.2.6

Если по сохранённому дайджесту, строка image такая; вместо ХВОСТ_ИЗ_ФАЙЛА подставьте хэш после sha256: из файла /root/subpage-backup/digest.txt:

Код
image: remnawave/subscription-page@sha256:ХВОСТ_ИЗ_ФАЙЛА
Код
cd /каталог/compose && docker compose up -d remnawave-subscription-page && docker compose logs --tail 50 remnawave-subscription-page

Дайджест годится везде, где годится тег, в том числе в команде docker run. С docker image prune подождите, пока новая версия не проработает сутки без сбоев: команда удаляет образы без имени, а после переезда тега latest старый образ как раз такой, и откат по дайджесту пришлось бы качать заново (образ может быть и недоступен в реестре).

11. Типичные ошибки после обновления

Что видноПричинаЧто делать
Пустой ответ или 502, а контейнер UpСервис рвёт сокет, если нет X-Forwarded-For или X-Forwarded-Proto: https. В логе Reverse proxy and HTTPS are required.Прописать оба заголовка на прокси (раздел 9)
То же на отдельный ключ или адресНеизвестный ключ подписки, неизвестный маршрут или формат клиента не из белого списка. В логе Invalid client type или Error in GetSubscriptionInfo RequestШтатный отказ. Проверить ключ в панели
Контейнер перезапускается по кругу, в логе Connection to Remnawave Panel failed!С этого сервера не открывается адрес из REMNAWAVE_PANEL_URL: панель лежит, закрыта фильтром или адрес изменилсяПроверить адрес и доступ с хоста. Если панель закрыта от посторонних, она закрыта и для сабпейджа
Сервис не стартует сразу после правки TRUST_PROXYЗначение не из допустимыхВернуть 1 или другое допустимое значение
Исчезла своя кнопка или темаФайлы лежали внутри старого контейнера и ушли вместе с нимВернуть через монтирование (раздел 6)
Страница в браузере без стилей, подписка в приложении отдаётсяСтарые cookie после перезапуска или кэш статики на CDN или прокси (статика 8.0.0 кэшируется на 7 дней)Обновить страницу, сбросить кэш статики на CDN
В панели у всех клиентов один и тот же IPНеверное число доверенных звеньев в TRUST_PROXYРаздел 8, шаг 5
После 8.0.0 вырос исходящий трафикСервис больше не сжимает ответыВключить сжатие на прокси (раздел 9)

Чтобы понять, ломается сервис или путь до него, пошлите запрос на контейнер напрямую, минуя прокси и CDN, и с нужными заголовками (порт опубликован на 127.0.0.1; если нет, выполните curl внутри контейнера через docker exec):

Код3 строки
curl -sS -o /dev/null -w '%{http_code}\n' -A 'Mozilla/5.0' \
  -H 'X-Forwarded-For: 127.0.0.1' -H 'X-Forwarded-Proto: https' \
  http://127.0.0.1:3010/КЛЮЧ_ПОДПИСКИ

200 значит, что сервис здоров, а проблема в прокси, CDN или заголовках. Пустой ответ при верном ключе значит, что искать надо в логах контейнера.

Внимание

Не вешайте автоперезапуск на 502 или пустой ответ. Для этого сервиса оборванное соединение это штатный ответ на неизвестный ключ, и сторожевой скрипт будет перезапускать здоровый контейнер. Для мониторинга годится проверка порта по TCP (отказ в соединении значит, что сервис упал) или статус healthy на 8.0.0.

12. Закрыть лишнее вокруг сервиса

Обновление закрывает известную проблему, но сервис по-прежнему доступен из интернета. Четыре меры, которые имеют смысл независимо от версии.

Порт 3010 только на себя. В официальном compose порт опубликован как 127.0.0.1:3010:3010. При ручной правке легко получить 3010:3010, и сервис окажется доступен всем в обход прокси. Проверка:

Код
docker port remnawave-subscription-page

И по слушающим портам хоста:

Код
ss -lntp | grep 3010

Должно быть 127.0.0.1:3010. UFW здесь не защита: Docker публикует порты в обход его правил, поэтому вывод ufw status об этом порте ничего не скажет. Если порт открыт наружу, поправьте строку ports в compose и пересоздайте контейнер. Пока не поправлено, порт закроет временное правило в цепочке DOCKER-USER (после перезагрузки оно пропадёт; оно же отрежет доступ к порту 3010 любого контейнера на этом интерфейсе, поэтому если прокси стоит на другом хосте и ходит на этот порт снаружи, не применяйте). Добавить правило:

Код
iptables -I DOCKER-USER -i "$(ip route show default | awk '{print $5; exit}')" -p tcp --dport 3010 -j DROP

Посмотреть цепочку:

Код
iptables -L DOCKER-USER -n --line-numbers

Снять правило (ИНТЕРФЕЙС имя внешнего сетевого интерфейса сервера, которое подставила первая команда):

Код
iptables -D DOCKER-USER -i ИНТЕРФЕЙС -p tcp --dport 3010 -j DROP

Если прокси и сабпейдж в одной docker-сети, порт можно не публиковать вовсе и обращаться по имени контейнера. Проверка снаружи, с другой машины (sub.вашдомен.com домен подписки):

Код
nc -zv -w 5 sub.вашдомен.com 3010

Ожидается отказ или тишина.

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

Зона лимита описывается в контексте http. Её создаёт отдельный файл в conf.d, вставьте команду целиком:

Код4 строки
cat > /etc/nginx/conf.d/subscription-limit.conf <<'EOF'
limit_req_zone $binary_remote_addr zone=sub:10m rate=10r/s;
limit_req_status 429;
EOF

В location подписки того же файла (открывается командой nano выше) добавьте (не в location /assets: страница тянет много файлов за раз):

Код
limit_req zone=sub burst=20 nodelay;

Пороги взяты как отправная точка, а не как рекомендация разработчиков; после включения посмотрите в логе, нет ли 429 у обычных пользователей. Если перед nginx стоит CDN, ключом лимита станет адрес CDN, и квоту разделят все клиенты сразу. Нужна подстановка реального адреса клиента (модуль ngx_http_realip_module).

Не кэшируйте подписки на CDN и прокси. Ответ персональный (ключи, срок, остаток трафика), и общий кэш способен выдать один конфиг разным людям. Проверка: два разных ключа должны давать разные ответы.

Подставьте два разных действующих ключа подписки (КЛЮЧ_ОДИН, КЛЮЧ_ДВА) и домен подписки sub.вашдомен.com. Первый ключ:

Код
curl -sS -A 'Happ/4.12.0' https://sub.вашдомен.com/КЛЮЧ_ОДИН | md5sum

Второй ключ:

Код
curl -sS -A 'Happ/4.12.0' https://sub.вашдомен.com/КЛЮЧ_ДВА | md5sum

Одинаковые суммы у разных ключей значит, что кэш включён (или оба ключа недействительны и вернули одинаковую пустоту). Отключите кэш для этого домена и не переводите клиентов, пока не исправите.

Сузить маршруты. Сервис обслуживает две группы адресов: подписку (/КЛЮЧ и /КЛЮЧ/формат, перед ними CUSTOM_SUB_PREFIX, если он задан) и статику /assets. Если префикс задан, прокси может пропускать только эти два пути и рвать остальное. Без префикса подписка висит в корне, и сузить путь, не сломав её, нельзя. Перед включением правила проверьте браузерную страницу: она тянет статику с /assets, а конфиг страницы лежит по пути /assets/.app-config-v2.json, вне префикса.

13. Как не пропустить следующее обновление

Для этого репозитория сканеры и рассылки молчат: advisory нет, GitHub Releases нет, объявление пришло одним сообщением в канале. Что работает:

  • Лента тегов https://github.com/remnawave/subscription-page/tags.atom. Лента releases.atom пуста, потому что релизов нет.
  • Канал Remnawave в Telegram (там и вышло объявление) и раздел announces на форуме docs.rw.
  • Опрос списка advisory раз в месяц. Пустой массив [] значит «ничего не опубликовано», а не «уязвимостей нет».

Список advisory сабпейджа:

Код
curl -sL https://api.github.com/repos/remnawave/subscription-page/security-advisories

Идентификаторы advisory панели (backend):

Код
curl -sL https://api.github.com/repos/remnawave/backend/security-advisories | grep -o '"ghsa_id": *"[^"]*"'

Ответ rate limit exceeded означает лимит GitHub API на ваш адрес, а не пропавшие advisory. Повторите позже.

И последнее: найдите на серверах все места, где образы Remnawave остались подвижными.

Код
grep -rnE 'image:.*remnawave' /opt --include='docker-compose*.y*ml' --include='compose*.y*ml'

Строки с :latest, а также image без тега (он тоже означает latest) замените на явный тег или дайджест. Тогда очередной pull принесёт только ту версию, которую вы выбрали сами.

← Все материалы