Срочное обновление Subscription Page Remnawave: проверка версии, выбор тега, откат
Как узнать версию сабпейджа, выбрать тег под свою панель, обновиться с точкой отката и вернуться назад, если что-то сломалось. Отдельно разобрано, что про уязвимость подтверждено официально, а что нет.
- Что потребуется
- 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.4 | 14-15.06.2026 | a59fa9d | b99c2715551a | До исправления. Обновление зависимостей, правка передачи shortUuid |
| 7.2.5 | 16.06.2026 | de06851 | 3b8160459fe0 | Версия из объявления. Рефакторинг конфигурации, исправление чтения .env |
| 7.2.6 | 24.06.2026 | 5fefecd | da5ee26ec70e | Последняя в ветке 7. Добавлен TRUST_PROXY, защита от подмены IP |
| 8.0.0 | 01.08.2026 | 2a125d1 | 04e8d479afb3 | Ветка 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.
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, по дайджесту. Он считается из содержимого образа и не зависит от тега.
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.x | 7.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-backupchmod 700 /root/subpage-backupЗапишите дайджест текущего образа (он же выводится на экран):
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: и длинный хэш после неё.
Теперь определите, чем управляется контейнер, и скопируйте файлы, из которых он поднят:
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/envchmod 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.6cd /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:
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-pagedocker rename remnawave-subscription-page-old remnawave-subscription-pagedocker 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-page9. Прокси после перехода на 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 сжимает и так.
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 их нужно прописать вручную; пример для одного прокси перед сервисом:
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 -tnginx -s reloadДля Caddy:
caddy validate --config /etc/caddy/Caddyfilecaddy reload --config /etc/caddy/Caddyfile10. Откат
Своих данных страница подписки не хранит (источником служит панель), поэтому откат состоит в возврате образа. Ограничения: возврат ниже 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):
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, вставьте команду целиком:
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 принесёт только ту версию, которую вы выбрали сами.