Uptime Kuma: установка в Docker и мониторинг сайтов и API

Uptime Kuma: установка в Docker на отдельный VPS, свой домен с TLS, мониторинг доступности сайтов и API, уведомления в Telegram, статус-страница и переход на 2.x.

Актуально на сентябрь 2026 года: Uptime Kuma 2.5.5, ветка 1.x больше не обновляется. Упор — на то, что ломается в эксплуатации: ложные тревоги, прокси, миграция и рост нагрузки.

Что понадобится

  • Отдельный VPS под мониторинг. Для десятков мониторов хватит 1 vCPU и 1 ГБ RAM, расчёт на 200 — ниже. ForestsNet Cloud разворачивает VPS за несколько минут после оплаты, площадки — в Нидерландах и Германии.
  • Ubuntu 24.04 или Debian 12/13 с Docker и плагином Compose, см. Docker и Docker Compose.
  • Поддомен вроде kuma.example.com с A-записью на сервер, см. Как привязать домен к серверу.
  • Базовая защита: усиление SSH и файрвол ufw.

Пункты меню Kuma даны по английскому интерфейсу, русский включается в Settings → Appearance.

Шаг 1. Решите, где стоит мониторинг и что он проверяет

Не на том же сервере

Мониторинг на одной машине с сервисами молчит ровно тогда, когда нужен: сервер завис или потерял сеть — замолкает и Kuma, а тишина выглядит как «всё в порядке». Проверка изнутри к тому же идёт мимо внешней сети, DNS и файрвола. Ставьте Kuma на отдельный VPS, лучше в другой площадке или у другого провайдера.

За самой Kuma тоже нужен присмотр: второй экземпляр в другом месте или внешний «сторожевой таймер». Например, HTTP-монитор в Kuma на адрес проверки Healthchecks.io с интервалом в минуту: пока Kuma жива, она его дёргает, а замолчит — уведомит Healthchecks.io, бесплатно до 20 проверок.

Что мониторить

  • Входы для пользователей: главная страница с ключевым словом, страница входа, эндпоинт здоровья API.
  • Сроки TLS-сертификатов и доменов.
  • Базы и внутренние сервисы, открытые только для IP мониторинга.
  • Фоновые задачи через push: бэкапы и cron-задания.
  • Не сюда: загрузку CPU, память и диск Kuma не собирает, для этого есть Beszel и Zabbix. Монитору Docker Container нужен доступ к Docker API, то есть фактически root на проверяемом хосте, так что с отдельного сервера он неудобен.

Шаг 2. Установите Uptime Kuma в Docker

mkdir -p /opt/uptime-kuma && cd /opt/uptime-kuma

Файл compose.yaml:

services:
  uptime-kuma:
    image: louislam/uptime-kuma:2.5.5-slim
    container_name: uptime-kuma
    restart: unless-stopped
    ports:
      - "127.0.0.1:3001:3001"
    volumes:
      - ./data:/app/data
    environment:
      TZ: Etc/UTC   # замените на свой, например Europe/Berlin
docker compose up -d
docker compose logs -f

Дождитесь в логе строки Listening on. Что здесь важно:

  • Тег. latest до сих пор указывает на 1.23.17 без исправлений безопасности. Тег 2 даёт последнюю 2.x, но версию лучше закрепить: 2.2.0–2.3.1, например, рассылали ложные DOWN из-за блокировок SQLite.
  • Образ slim. Полный образ занимает около 1,7 ГБ с MariaDB и Chromium для мониторов Browser Engine, slim — около 0,5 ГБ.
  • 127.0.0.1. Порт, опубликованный Docker, обходит ufw: трафик уходит в цепочки iptables Docker раньше правил ufw, и порт на 0.0.0.0 открыт всему интернету.
  • Каталог data держите на локальном диске: на NFS SQLite портится из-за блокировок файлов.
  • TZ — часовой пояс для окон обслуживания, см. шаг 7.
Важно: первый вход делайте через SSH-туннель. Страницу создания администратора займёт первый, кто на неё попадёт.
ssh -L 3001:127.0.0.1:3001 deploy@IP_сервера

На http://localhost:3001 Kuma спросит, какую базу использовать. SQLite официально рекомендована для небольших установок, а на практике держит сотни мониторов. Встроенная MariaDB есть только в полном образе и ест больше памяти. Выбирайте сразу: перенос из SQLite в MariaDB официально не поддерживается.

Шаг 3. Подключите домен с TLS и WebSocket

Интерфейс Kuma работает через WebSocket, поэтому прокси должен пропускать заголовки Upgrade и Connection и не подменять Host. Подкаталог вроде example.com/kuma не поддерживается, нужен домен или поддомен.

Caddy

Caddy сам получит сертификат Let's Encrypt, пропустит WebSocket и выставит заголовки X-Forwarded-*, установка — в статье Caddy: автоматический HTTPS. Файл /etc/caddy/Caddyfile:

kuma.example.com {
    reverse_proxy 127.0.0.1:3001
}
sudo systemctl reload caddy

Nginx

Основа — Nginx: установка и первый сайт, сертификат — Let's Encrypt через certbot.

server {
    listen 80;
    server_name kuma.example.com;

    location / {
        proxy_pass http://127.0.0.1:3001;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d kuma.example.com

Host обязателен: с версии 1.23.9 Kuma сверяет Origin у WebSocket с Host и отклоняет несовпадения, в логе появляется Origin (...) does not match host. В proxy_pass пишите 127.0.0.1: на Debian имя localhost разрешается и в ::1, и nginx засорит лог ошибками.

В самой Kuma включите Settings → Reverse Proxy → Trust Proxy, чтобы видеть IP клиентов, и задайте Settings → General → Primary Base URL https://kuma.example.com: из него строятся адреса push-мониторов. За Cloudflare включите переключатель WebSockets в разделе Network.

Шаг 4. Закройте прямой доступ и включите 2FA

sudo ufw default deny incoming
sudo ufw allow 22/tcp          # или ваш порт SSH
sudo ufw allow 80,443/tcp
sudo ufw enable
sudo ss -tlnp | grep 3001

В выводе ss должно быть 127.0.0.1:3001, а curl -m 5 http://IP_сервера:3001 с другой машины должен закончиться таймаутом или отказом. Затем включите 2FA в Settings → Security: форма входа доступна из интернета всегда, в том числе на домене статус-страницы.

Шаг 5. Настройте первые мониторы

Настройки HTTP-мониторов, которые снимают большую часть ложных тревог:

  • Heartbeat Interval — 60 секунд.
  • Retries — 2–3. В 2.x у новых мониторов по умолчанию 0, и одна потерянная проверка сразу шлёт DOWN. Пока идут повторы, монитор в статусе PENDING и уведомлений нет.
  • Heartbeat Retry Interval — 20–30 секунд, чтобы подтверждать сбой быстрее.
  • Request Timeout — 10–20 секунд вместо 48 по умолчанию, это 80% интервала.

HTTP с ключевым словом

Тип HTTP(s) - Keyword требует ответ 2xx и заданную строку в теле: так ловятся страница с ошибкой приложения и заглушка, которые тоже отдают 200. Поиск идёт по исходному HTML или JSON с учётом регистра, а у SPA текст появляется только после JavaScript, поэтому берите строку из <title>. Invert Keyword проверяет, что строки нет, а для API есть HTTP(s) - Json Query с выражением JSONata. Не включайте Ignore TLS/SSL errors: вместе с ним отключаются и уведомления о сроке сертификата.

Сроки сертификатов и доменов

Флажок Certificate Expiry Notification в HTTP-мониторе по умолчанию снят, включите его. За сколько дней предупреждать, задаётся в Settings → Notifications → TLS Certificate Expiry, по умолчанию за 7, 14 и 21 день. С версии 2.1 сертификат проверяет и монитор TCP Port: IMAPS и STARTTLS в SMTP, IMAP и XMPP.

Срок домена Kuma с версии 2.1 проверяет по RDAP, флажок Domain Name Expiry Notification включён по умолчанию. Но для .ru, .рф, .su, .de, .eu и .io в реестре RDAP IANA на сентябрь 2026 года записей нет, и проверка не работает: держите такие домены на автопродлении.

TCP для баз

TCP Port видит только, что порт принимает соединения. Мониторы PostgreSQL и MySQL/MariaDB выполняют запрос, по умолчанию SELECT 1. Дайте им пользователя с минимальными правами, а порт базы откройте только для IP мониторинга:

sudo ufw allow from 203.0.113.10 to any port 5432 proto tcp

Если база в Docker с опубликованным портом, ufw её не закроет по той же причине, что в шаге 2. Публикуйте такой порт на внутренний интерфейс, например WireGuard.

Push для cron-заданий

Push-монитор работает наоборот: задание само стучится в Kuma по URL, и если отчёта не было дольше Heartbeat Interval, будет тревога. Для ночного бэкапа поставьте интервал 25 часов (90000 секунд) и Retries 0:

#!/bin/sh
PUSH="https://kuma.example.com/api/push/ТОКЕН"
if /usr/local/bin/backup.sh >>/var/log/backup.log 2>&1; then
  curl -fsS -m 10 --retry 3 "$PUSH?status=up&msg=OK" >/dev/null
else
  curl -fsS -m 10 --retry 3 "$PUSH?status=down&msg=backup_failed" >/dev/null
fi

Так вы узнаете и о падении скрипта, и о том, что cron не запустил его вовсе. Сам скрипт и расписание — в статьях Автобэкапы по расписанию и Бэкапы баз данных.

Шаг 6. Настройте уведомления в Telegram

  1. В @BotFather отправьте /newbot и получите токен.
  2. Для личного чата напишите боту /start. В группе отправьте /start@имя_бота: обычное сообщение бот из-за режима приватности может не увидеть. В канале сделайте бота администратором.
  3. В Settings → Notifications → Set Up Notification выберите Telegram, вставьте токен и нажмите Auto Get: Kuma возьмёт ID последнего чата из getUpdates. У групп ID отрицательный, у супергрупп начинается с -100, для темы форума есть Message Thread ID.
  4. Нажмите Test. Флажок Default enabled подключит канал к новым мониторам, Apply on all existing monitors — к уже созданным.

Auto Get обращается к Telegram из браузера. Если не срабатывает, возьмите chat.id из ответа curl -s https://api.telegram.org/botТОКЕН/getUpdates на сервере.

Как не заспамить себя

  • Сообщения идут только при смене состояния: UP → DOWN, DOWN → UP и DOWN после окна обслуживания. Пока монитор в PENDING на повторах, тишина.
  • Resend Notification if Down X times consecutively по умолчанию 0 — одно сообщение на аварию. Значение 30 при интервале 60 секунд даст напоминание раз в полчаса.
  • Объедините проверки сервера в монитор Group и вешайте уведомления на него: при падении сервера придёт одно сообщение со списком упавших вместо десяти.
  • Плановые работы проводите через окна обслуживания, второстепенные проверки шлите в отдельный чат или с флажком Send Silently.

Шаг 7. Сделайте статус-страницу и окна обслуживания

Статус-страница

В Status Pages → New Status Page задайте имя и slug, страница откроется по адресу /status/slug. Добавляйте только публичные мониторы. Инциденты публикуются кнопкой Create Incident, с версии 2.1 хранится их история.

Отдельный домен вроде status.example.com впишите в Domain Names и добавьте в прокси с тем же reverse_proxy. Прокси должен передавать Host, иначе Kuma не поймёт, какую страницу показать. Статусы на публичной странице кэшируются до минуты.

Окна обслуживания

В Maintenance → Schedule Maintenance выберите мониторы, статус-страницы и стратегию: вручную, разовое окно, cron-выражение, интервал, дни недели или месяца. В окне мониторы синие, уведомлений нет, а на статус-странице висит плашка. Если после окна сервис всё ещё лежит, придёт DOWN.

С часовыми поясами есть подвохи:

  • Пояс сервера Kuma при первом входе берёт из браузера администратора, а TZ перебивает его при каждом старте контейнера. Держите одинаковыми TZ в compose и Settings → General → Server Timezone, иначе смена пояса в интерфейсе откатится после перезапуска.
  • Пояс окна задавайте явно, а не «как у сервера».
  • На сентябрь 2026 года открыты баги: окно по времени суток не учитывает летнее время (#6233), список окон показывает время в поясе браузера (#7895). После перевода часов сверяйте окна в форме редактирования.

Шаг 8. Перейдите с 1.x на 2.x

Что нового в 2.x

  • Агрегаты вместо сырой истории. Детальные проверки хранятся около суток, дальше остаются поминутные итоги за 24 часа, почасовые за 30 дней и суточные на весь срок хранения. Дашборды быстрее, база не разрастается до гигабайт. Срок задаётся в Settings → Monitor History: 365 дней у новых установок, прежний — после миграции.
  • MariaDB, встроенная или внешняя, и образы slim и rootless.
  • Новые мониторы: SNMP, SMTP, SFTP, NTP, RabbitMQ, WebSocket Upgrade и Globalping — проверка с чужих узлов в выбранной стране, анонимно до 250 тестов в час.
  • Уведомления: шаблоны на LiquidJS, свой сервер Bot API для Telegram, прокси NOTIFICATION_PROXY.
  • API для управления мониторами так и нет (#118 открыт с 2021 года): только push, бейджи, JSON статус-страниц и /metrics для Prometheus с ключом из Settings → API Keys.

Миграция

При первом старте 2.x переносит историю в агрегаты. У автора проекта 20 мониторов с историей за 90 дней мигрировали около 7 минут, большая история на медленном диске займёт часы.

Важно: миграцию нельзя прерывать. После обрыва Kuma не стартует с ошибкой «Aggregate table migration is already in progress, or it was interrupted», и выход один — восстановить бэкап и начать заново.
cd /opt/uptime-kuma                          # каталог с compose.yaml
docker compose down
tar -czf kuma-v1-$(date +%F).tar.gz data     # ваш каталог с /app/data
# в compose.yaml поменяйте тег на louislam/uptime-kuma:2.5.5-slim
docker compose up -d && docker compose logs -f

В логе идут строки [DON'T STOP] Migrating monitor ... с процентом, в конце — Aggregate Table Migration Completed. Ещё:

  • Если старая история не нужна, до перехода сократите срок хранения в 1.23 и нажмите Shrink Database: мигрировать придётся меньше.
  • Rootless-образы для перехода с 1.x не рекомендуются.
  • Удалены экспорт бэкапа в JSON и DNS-кэш HTTP-мониторов, в Docker его заменяет nscd. Переменные в шаблонах SMTP стали чувствительны к регистру, период у бейджей — только 24, 24h, 30d или 1y. Без Docker нужен Node.js 20.4+.

Шаг 9. Настройте бэкап и обновления

Официальный бэкап в 2.x — копия каталога data. Копировать файл SQLite на ходу нельзя: база работает в режиме WAL, и копия может оказаться битой. Снимок делает сам SQLite, утилита sqlite3 есть в образе:

#!/bin/sh
set -e
D=/opt/uptime-kuma
mkdir -p $D/backup
rm -f $D/data/snapshot.db
docker exec uptime-kuma sqlite3 /app/data/kuma.db "VACUUM INTO '/app/data/snapshot.db'"
tar -czf $D/backup/kuma-$(date +%F).tar.gz -C $D/data --exclude='kuma.db*' .
rm -f $D/data/snapshot.db
find $D/backup -name 'kuma-*.tar.gz' -mtime +14 -delete

Для восстановления остановите контейнер, распакуйте архив в пустой data, переименуйте snapshot.db в kuma.db и запустите ту же версию Kuma. Архивы уносите с сервера. Внешнюю MariaDB бэкапьте через mariadb-dump.

Обновление — смена тега в compose.yaml и:

docker compose pull && docker compose up -d

Перед обновлением снимите бэкап и прочитайте релиз. Минимум — 2.5.5, где закрыта утечка памяти в TCP-мониторах.

Сколько ресурсов нужно

Kuma — один процесс Node.js, больше одного ядра он почти не использует. Замеры из issues: 200 HTTP-мониторов с интервалом 60 секунд на 1.23 — около 130 МБ памяти и 2–3% от двух vCPU, около 250 мониторов на 2.0 — в среднем меньше 400 МБ.

  • 20 мониторов: 1 vCPU и 1 ГБ RAM с запасом: Kuma займёт 100–150 МБ, остальное — система, Docker и прокси.
  • 200 мониторов: 2 vCPU и 2 ГБ RAM, второе ядро — системе, прокси и процессам ping. Встроенная MariaDB или мониторы Browser Engine с Chromium прибавят сотни мегабайт, тогда закладывайте 4 ГБ.
  • Диск: образ slim около 0,5 ГБ, полный — около 1,7 ГБ. База 2.x по схеме хранения — порядка 1 МБ на монитор с интервалом 60 секунд, а 1.x за полгода накопила бы для 200 мониторов около 9 ГБ. Нужен SSD или NVMe: на медленных носителях SQLite ловит блокировки.
  • Потолок. Список мониторов не разбит на страницы: с 600–900 мониторами дашборд грузится до 8–15 секунд, а сопровождающие прямо пишут, что на тысячи мониторов Kuma не рассчитана. Дальше — несколько экземпляров или Gatus.
  • Трафик почти весь входящий: Kuma скачивает проверяемые страницы, а отправляет лишь короткие запросы и уведомления. В пакет ForestsNet Cloud входит только исходящий трафик, входящий не считается.

Альтернативы: Gatus и Beszel

Gatus

Gatus 5.37 написан на Go, и все проверки в нём описываются в YAML: конфиг живёт в git и проходит ревью вместе с кодом. Условия пишутся выражениями вроде [STATUS] == 200, [RESPONSE_TIME] < 300 и [CERTIFICATE_EXPIRATION] > 240h. Есть Telegram и десятки других каналов, push через external-endpoints, окна обслуживания, SQLite или PostgreSQL. Отличия от Kuma:

  • мониторы создаются только в конфиге, не в интерфейсе;
  • без блока storage история живёт в памяти и пропадает при перезапуске;
  • оповещение срабатывает после 3 провалов подряд, а о восстановлении по умолчанию молчит (send-on-resolved: false);
  • конфиг перечитывается на лету, но если смонтировать отдельный файл, а не каталог, правки могут не подхватиться.

Берите Gatus, если мониторинг разворачивается из репозитория вместе с инфраструктурой.

Beszel

Beszel 0.20 отвечает на другой вопрос: что происходит внутри сервера. Хаб на PocketBase (порт 8090) собирает с агентов CPU, память, диски, сеть, температуру, SMART и статистику контейнеров Docker и Podman, хранит историю и шлёт оповещения по порогам. Агенты принимают хаб по SSH на порту 45876 или сами подключаются к нему по WebSocket. С версии 0.20 они выполняют сетевые пробы ICMP, TCP, HTTP и DNS, но статус-страниц и проверки содержимого нет. Разумная пара: Kuma — доступность снаружи, Beszel — ресурсы изнутри.

Для полноценного мониторинга инфраструктуры с шаблонами и триггерами есть Zabbix.

Диагностика

  • «Cannot connect to the socket server. Reconnecting…» — прокси не пропускает WebSocket: проверьте Upgrade и Connection в nginx и WebSockets в Cloudflare. Если в логе Kuma Origin (...) does not match host, прокси подменяет Host. Отключать проверку через UPTIME_KUMA_WS_ORIGIN_CHECK=bypass — крайняя мера.
  • 502 от прокси — контейнер не запущен или ещё мигрирует базу: docker compose ps и docker compose logs --tail=50.
  • DOWN, а сайт открывается. timeout of ... exceeded — сайт отвечает дольше таймаута или теряются пакеты. 403 и 503 — запросы режет WAF, например Browser Integrity Check в Cloudflare: разрешите IP мониторинга в IP Access Rules. Повторите запрос из контейнера: docker exec -it uptime-kuma curl -sv https://example.com.
  • Ложные DOWN с SQLITE_BUSY: database is locked — регрессия 2.2.0–2.3.1, обновитесь. На свежей версии ищите медленный диск или сетевую ФС.
  • Не стартует после перехода с 1.x с ошибкой миграции агрегатов — миграцию прервали, восстановите бэкап.
  • Память контейнера растёт до гигабайт — в версиях 2.1–2.5.4 TCP-мониторы с проверкой сертификата оставляли незакрытые сокеты. Обновитесь до 2.5.5.
  • Telegram отвечает chat not found — боту не писали /start или ID неверный. Ошибка group chat was upgraded to a supergroup chat значит, что группа стала супергруппой и нужен новый ID с -100.
  • Push-монитор краснеет, хотя задание работает — интервал монитора короче периода задания или curl не доходит до Kuma: выполните его вручную.
  • Забыли пароль: docker exec -it uptime-kuma npm run reset-password.

Источники