Перейти к содержанию

Как перестать зависеть от телеграм-бота для алертов домашнего сервера: ntfy по уровням

Опубликовано
  • Админы
ntfy: уведомления со своего сервера
Пять уровней самостоятельности: от публичного топика до сервера с ACL

Схема «алерты в телеграм-бота» стала настолько стандартной, что её перестали обсуждать. А обсудить есть что: токен бота размазан по переменным окружения половины ваших контейнеров, канал доставки целиком чужой, а в момент, когда бота забанят или мессенджер перестанет открываться, вы окажетесь единственным админом, который не в курсе, что у него горит сервер.

Альтернатива, требующая примерно нулевых усилий на входе, — ntfy: сервер на Go, который принимает обычный HTTP POST и превращает его в push на телефоне. Никакого SDK, никакого формата вебхука, который надо изучать: curl -d "текст" сервер/топик — и всё. При этом сервер можно поднять свой, и тогда цепочка доставки принадлежит вам целиком. Почти целиком — с iOS есть нюанс, к которому мы дойдём.

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

ℹ️ Справка

Актуальная версия на момент написания — v2.24.0 от 4 июня 2026. Проект живой, но релизы редкие: предыдущие — 2.23.0 (18 мая) и 2.22.0 (21 апреля 2026). Это нормальный ритм для инструмента, который делает одну вещь и уже её сделал.


Уровень 0. Чужой сервер и один curl

Публичный ntfy.sh не требует ни регистрации, ни ключей. Топик создаётся в момент первой отправки:

curl -d "Бэкап закончился" ntfy.sh/moi-domashnii-server-x7k2

Подписываетесь на тот же топик в приложении или на https://ntfy.sh/moi-domashnii-server-x7k2 в браузере — и сообщение приходит.

⛔ Так делать нельзя

Ровно здесь совершают главную ошибку. В документации это сказано прямым текстом: топик — это по сути пароль. Кто угадал имя, тот читает все ваши уведомления и может слать свои. ntfy.sh/backup, ntfy.sh/home, ntfy.sh/alerts — это публичная лента, на которую подписан кто угодно.

Имя топика ограничено 64 символами, допустимы буквы, цифры, - и _. Если остаётесь на публичном сервере — генерируйте его как пароль:

head -c 24 /dev/urandom | base64 | tr -d '/+='

Для чего этого уровня хватает: разовые скрипты, «дай знать, когда доедет rsync», уведомления, содержимое которых не жалко показать посторонним. Для всего остального — дальше.


Уровень 1. Свой ntfy в Docker

Образ binwiederhier/ntfy собирается под amd64, armv6, armv7 и arm64 — то есть спокойно едет на Raspberry Pi и на любом мини-ПК.

services:
  ntfy:
    image: binwiederhier/ntfy
    container_name: ntfy
    command: serve
    environment:
      - TZ=Europe/Moscow
    volumes:
      - ./cache:/var/cache/ntfy
      - ./etc:/etc/ntfy
    ports:
      - 8080:80
    healthcheck:
      test: ["CMD-SHELL", "wget -q --tries=1 http://localhost:80/v1/health -O - | grep -Eo '\"healthy\"\\s*:\\s*true' || exit 1"]
      interval: 60s
      timeout: 10s
      retries: 3
      start_period: 40s
    restart: unless-stopped

Минимальный etc/server.yml:

base-url: "https://ntfy.example.org"
cache-file: /var/cache/ntfy/cache.db
auth-file: /var/cache/ntfy/auth.db
attachment-cache-dir: /var/cache/ntfy/attachments

📌 Заметка

Внутри контейнера сервер слушает 80-й порт (listen-http: ":80" по умолчанию), рядом есть listen-https (:443) и listen-unix — если вы предпочитаете отдавать сокет прокси-серверу напрямую, минуя TCP.

cache-file — это SQLite, в котором лежат сообщения. Без него сервер работает целиком в памяти: перезапустили контейнер — история пропала, а подписчик, который был офлайн, ничего не получит. Практически всегда его надо задать.


Уровень 2. За обратным прокси

Здесь живёт грабля, на которую наступают почти все.

⚠️ Осторожно

Если ntfy стоит за nginx, Caddy или Traefik и вы не выставили behind-proxy: true, сервер видит всех клиентов как один IP — адрес прокси. А лимиты в ntfy считаются по «посетителю», то есть по IP. Результат: один активный скрипт съедает лимит на всех, остальные получают 429.

behind-proxy: true
proxy-forwarded-header: "X-Forwarded-For"   # значение по умолчанию

Второе, что обязательно на этом уровне, — корректный base-url. Он используется в ссылках на вложения и в письмах; если он не совпадает с реальным внешним адресом, вложения будут приходить со ссылками в никуда.

Третье — сам прокси. ntfy держит длинные соединения для подписчиков, поэтому буферизация ответа и короткий таймаут прокси ломают доставку. В nginx это proxy_buffering off; и увеличенный proxy_read_timeout, в Caddy обычно достаточно обычного reverse_proxy без дополнительных настроек.


Уровень 3. Кто кому что может

По умолчанию сервер открыт: любой, кто знает адрес, публикует и читает. Закрывается это одной строкой:

auth-file: /var/cache/ntfy/auth.db
auth-default-access: "deny-all"

Значения auth-default-access:

ЗначениеЧто означает для анонимного гостя
read-writeчитает и пишет всё (поведение по умолчанию)
read-onlyтолько подписка
write-onlyтолько отправка
deny-allничего; всё только по логину или токену

Дальше — CLI. Команды выполняются внутри контейнера (docker compose exec ntfy ntfy ...):

# администратор — имеет доступ ко всему
ntfy user add --role=admin admin

# обычный пользователь: по умолчанию не имеет прав ни на что
ntfy user add homeassistant

# выдаём права точечно
ntfy access homeassistant "alerts-*"  write-only
ntfy access phone         "alerts-*"  read-only

# токен вместо пароля для скриптов
ntfy token add --expires=90d homeassistant

Шаблон alerts-* работает — права выдаются на группу топиков, а не только на один.

🔒 Безопасность

Токен лучше пароля не потому, что он длиннее, а потому что его можно отозвать по одному, не меняя доступ везде сразу, и можно выдать со сроком жизни. Для сервиса, который только шлёт, ставьте write-only: скомпрометированный контейнер тогда не сможет прочитать чужие уведомления.

В версии 2.24.0, кстати, чинили как раз проблему в этой области — некорректное сравнение имён топиков в ACL без учёта регистра на SQLite. Если вы держите ntfy с правами доступа, обновиться стоит.

Отправка с токеном:

curl -H "Authorization: Bearer tk_xxxxxxxxxxxx" \
     -d "Диск /dev/sda заполнен на 91%" \
     https://ntfy.example.org/alerts-infra

Уровень 4. Телефон, и почему iOS требует чужого сервера

Это самая недооценённая часть. Одно и то же приложение доставляет уведомления тремя разными способами, и от способа зависит и задержка, и приватность, и расход батареи.

Три маршрута доставки уведомления
Схема на основе документации docs.ntfy.sh, разделы subscribe/phone и config

Android, сборка из Google Play. Firebase используется только для топиков на самом ntfy.sh. Для ваших собственных серверов приложение Firebase не задействует вообще — оно держит прямое соединение. Это прямая цитата из документации, и она снимает главное опасение: свой сервер не «ходит через Google».

Android, сборка из F-Droid. Firebase нет в принципе, все подписки по умолчанию идут в режиме мгновенной доставки.

Мгновенная доставка — это постоянное соединение, а значит foreground-сервис и вечное уведомление в шторке. Без него, предупреждает документация, сообщения могут приходить с задержкой в минуты и даже часы. Выбор здесь честный и явный: либо иконка в статус-баре, либо непредсказуемая задержка.

❗ Главное

iOS — отдельный случай. Apple не разрешает приложениям держать постоянное соединение, всё идёт через APNS. Достучаться до APNS может только владелец сертификата приложения, то есть проект ntfy. Поэтому для своего сервера в конфиг добавляют:

upstream-base-url: "https://ntfy.sh"

Ваш сервер при поступлении сообщения отправляет на ntfy.sh короткий poll-запрос, тот будит телефон через APNS, а телефон приходит к вашему серверу за телом сообщения. Наружу уходит факт события, а не его содержимое.

Если вас и это не устраивает — остаётся вариант «зайти в веб-интерфейс и подписаться там» через Web Push, для которого нужны ключи VAPID:

web-push-public-key: "..."
web-push-private-key: "..."
web-push-file: /var/cache/ntfy/webpush.db
web-push-email-address: "admin@example.org"

Ключи генерируются командой ntfy webpush keys. Заметьте, что Web Push в браузере тоже ходит через сервисы Google/Mozilla/Apple — полностью изолированного пути на смартфон сегодня просто не существует.


Что можно положить в уведомление

Это то, ради чего ntfy стоит предпочесть самописному вебхуку: сообщение — не просто строка.

ЗаголовокКороткие псевдонимыЧто делает
X-Titlet, tiзаголовок уведомления
X-Priorityp, prioприоритет 1…5
X-Tagstag, taэмодзи и метки
X-ClickClickURL, открываемый по тапу
X-ActionsActionдо 3 кнопок действия
X-Attachaвложение по внешней ссылке
X-Markdownmdвключить разметку
X-DelayDelayотложенная отправка
X-EmailEmailпродублировать на почту

Приоритеты влияют на поведение телефона:

УровеньИмяПоведение
5urgent / maxдлинная вибрация, всплывающее уведомление
4highдлинная вибрация, всплывающее
3defaultкороткая вибрация, обычное
2lowбез звука и вибрации, скрыто до открытия шторки
1minбез звука, «под сгибом»

Самое интересное — кнопки. Их три типа: view (открыть ссылку), http (сделать HTTP-запрос) и broadcast (отправить Android-интент в Tasker и подобное). Классический сценарий домашнего сервера:

curl -H "Title: Дверь гаража открыта 40 минут" \
     -H "Priority: high" \
     -H "Tags: warning,house" \
     -H 'Actions: http, Закрыть, https://ha.local/api/webhook/garage_close, method=POST' \
     -H "Authorization: Bearer tk_xxxxxxxxxxxx" \
     -d "Закрыть прямо отсюда?" \
     https://ntfy.example.org/alerts-home

Одно уведомление — и действие выполняется с экрана блокировки, без открытия Home Assistant.

✅ Как правильно

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

  • X-Delay принимает 30s, 5m, 2h или Unix-таймстамп — готовый планировщик напоминаний без cron.
  • Markdown: yes превращает тело в размеченный текст: список отвалившихся сервисов читается несравнимо лучше.
  • Тело до 4096 байт. Больше — сервер попробует превратить это во вложение.
  • JSON-публикация на корень сервера: curl ntfy.example.org -d '{"topic":"alerts-infra","title":"…","priority":5}' — удобно, когда отправляет не bash, а код.

Куда это подключается на практике

  • systemd. В юнит добавляется OnFailure=, который дёргает одноразовый сервис с curl. Падение любого сервиса — сразу в телефон.
  • cron и бэкап-скрипты. curl -d "..." -H "Priority: 5" в ветке ошибки — и вы наконец узнаёте о неудавшемся бэкапе не через месяц.
  • Uptime Kuma и Healthchecks. ntfy у обоих в списке нотификаторов из коробки.
  • Home Assistant. Через rest_command или интеграцию ntfy; сюда же — кнопки действий с примера выше.
  • Grafana, Alertmanager. Обычный webhook-приёмник, форматирование настраивается шаблоном.

Лимиты, которые стоит знать заранее

Даже на своём сервере лимиты включены — они защищают от того, чтобы один взбесившийся скрипт не забил диск.

ПараметрЗначение по умолчаниюСмысл
visitor-request-limit-burst60сколько запросов можно сделать залпом
visitor-request-limit-replenish5sкак быстро «бак» пополняется
attachment-file-size-limit15Mмаксимум на один файл
attachment-total-size-limit5Gобщий кэш вложений
attachment-expiry-duration3hсколько живёт вложение
visitor-attachment-total-size-limit100Mквота на «посетителя»
visitor-attachment-daily-bandwidth-limit500Mсуточный трафик вложений

Три часа хранения вложений — это про то, что ntfy не файлопомойка. Скриншот с камеры дойдёт, архив логов — нет, и не должен.

⚠️ Осторожно

Ещё одна деталь для тех, кто выставляет сервер наружу: в 2.23.0 добавили лимит на создание новых топиков с одного адреса — именно чтобы нельзя было перебором нащупать существующие. Если у вас открытый сервер с auth-default-access: read-write, перебор имён топиков остаётся полностью рабочей атакой. Это ещё один аргумент за deny-all.

Чего ntfy не делает

Честности ради: это не мессенджер и не система оповещений с дежурными сменами.

Здесь нет шифрования сообщений между отправителем и получателем — трафик защищён TLS, но на сервере сообщение лежит открытым текстом в SQLite. Нет эскалаций «не подтвердил за 5 минут — звони следующему», нет расписаний дежурств, нет группировки и подавления шквала одинаковых алертов. Если вам нужно это — смотрите в сторону Alertmanager перед ntfy, а ntfy оставьте как транспорт последней мили.

И ещё: своим сервером вы берёте на себя его доступность. Уведомление о том, что сервер лёг, не придёт с сервера, который лёг. Классическое решение — держать ntfy не на той же машине, за которой он следит, а внешний watchdog вроде Healthchecks.io — на стороне.


Источники

💬 Вопрос к сообществу

Чем вы будите себя, когда падает домашний сервер, — телеграм-ботом, ntfy, Gotify или чем-то совсем экзотическим? И был ли случай, когда уведомление не дошло именно тогда, когда было нужнее всего?

Featured Replies

No posts to show

Присоединяйтесь к обсуждению

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

Гость
К сожалению, ваш контент содержит запрещённые слова. Пожалуйста, отредактируйте контент, чтобы удалить выделенные ниже слова.
Ответить в этой теме...

Последние посетители 0

  • Ни одного зарегистрированного пользователя не просматривает данную страницу
Яндекс.Метрика

Account

Navigation

Поиск

Поиск

Configure browser push notifications

Chrome (Android)
  1. Tap the lock icon next to the address bar.
  2. Tap Permissions → Notifications.
  3. Adjust your preference.
Chrome (Desktop)
  1. Click the padlock icon in the address bar.
  2. Select Site settings.
  3. Find Notifications and adjust your preference.