ntfy — простой сервер push-уведомлений: curl на URL — и пуш на телефон. Подходит для бэкапов, Fail2ban, Uptime Kuma, Zabbix и cron-задач.

Запуск ntfy с Docker Compose через Traefik

1. Каталог

mkdir -p ~/ntfy/{cache,etc}
cd ~/ntfy

2. Конфиг сервера

cat > etc/server.yml << 'EOF'
base-url: "https://ntfy.yourdomain"
cache-file: "/var/cache/ntfy/cache.db"
attachment-cache-dir: "/var/cache/ntfy/attachments"
auth-file: "/var/lib/ntfy/user.db"
auth-default-access: "deny-all"
behind-proxy: true
upstream-base-url: "https://ntfy.sh"
EOF

deny-all — без анонимной публикации; пользователей создадите после старта.

3. docker-compose.yml

services:
  ntfy:
    image: binwiederhier/ntfy:latest
    container_name: ntfy
    restart: unless-stopped
    command: serve
    environment:
      TZ: Europe/Moscow
    volumes:
      - ./cache:/var/cache/ntfy
      - ./etc:/etc/ntfy
      - ntfy_lib:/var/lib/ntfy
    labels:
      - traefik.enable=true
      - traefik.http.middlewares.ntfy_https.redirectscheme.scheme=https
      - traefik.http.routers.ntfy.entrypoints=web
      - traefik.http.routers.ntfy.middlewares=ntfy_https@docker
      - traefik.http.routers.ntfy.rule=Host(`ntfy.yourdomain`)
      - traefik.http.routers.ntfy_https.entrypoints=websecure
      - traefik.http.routers.ntfy_https.rule=Host(`ntfy.yourdomain`)
      - traefik.http.routers.ntfy_https.tls=true
      - traefik.http.routers.ntfy_https.tls.certresolver=le
      - traefik.http.services.ntfy.loadbalancer.server.port=80
    networks:
      - traefik_network

volumes:
  ntfy_lib:

networks:
  traefik_network:
    external: true

4. Запуск и пользователь

docker compose up -d
docker exec -it ntfy ntfy user add --role=admin youruser

Установите приложение ntfy, подпишитесь на топик, например homelab-alerts (с логином).

В веб-интерфейсе управления пользователями нет. Обычных людей и доступ к топику задаёте в CLI:

docker exec ntfy ntfy user list
docker exec -it ntfy ntfy user add username
docker exec ntfy ntfy access username homelab-alerts read-write
docker exec ntfy ntfy user del username

read-only вместо read-write — только получать пуши. Админ видит все топики; обычный пользователь — только те, куда выдали access.

Топик как сущность в базе не создаётся: достаточно имени и ACL. Новый канал тому же человеку:

docker exec ntfy ntfy access username backups read-write
docker exec ntfy ntfy access

После этого он подписывается в приложении на backups. Закрыть топик: ntfy access username backups deny.

5. Отправка из скрипта

curl -u 'youruser:password' \
  -H "Title: Backup OK" \
  -d "Ночной rclone завершился успешно" \
  https://ntfy.yourdomain/homelab-alerts

6. Оповещения из Zabbix

Цепочка: триггер → Trigger action → media пользователя → ntfy.sh → топик. Кнопка Test у media type эту цепочку не проверяет.

6.1. На стороне ntfy

Не кладите пароль админа в Zabbix. Служебный пользователь только на запись и токен:

docker exec -it ntfy ntfy user add zabbix
docker exec ntfy ntfy access zabbix homelab-alerts write-only
docker exec ntfy ntfy token add --label=zabbix-media zabbix

token add печатает tk_…. Людям, которые должны видеть алерты:

docker exec ntfy ntfy access username homelab-alerts read-write

В приложении: сервер https://ntfy.yourdomain, свой логин, подписка на тот же топик. Android — Instant delivery. iPhone — в server.yml строка upstream-base-url: "https://ntfy.sh", иначе фоновые пуши почти не приходят.

Сервер Zabbix должен открывать https://ntfy.yourdomain (проверка: curl -I https://ntfy.yourdomain с хоста Zabbix).

6.2. Скрипт на сервере Zabbix (5.0)

YAML-webhook в 5.0 не импортируется, нужен media type Script. Путь из AlertScriptsPath (часто /usr/lib/zabbix/alertscripts), файл ntfy.sh:

#!/bin/bash
# $1 топик  $2 тема  $3 текст
set -eu
topic="${1:?}"
title="${2:-Zabbix}"
body="${3:-}"

curl -fsS \
  -H "Authorization: Bearer TOKEN" \
  -H "Title: ${title}" \
  -H "Tags: zabbix" \
  -H "Priority: high" \
  -d "${body}" \
  "https://ntfy.yourdomain/${topic}"
chmod 755 /usr/lib/zabbix/alertscripts/ntfy.sh
chown zabbix:zabbix /usr/lib/zabbix/alertscripts/ntfy.sh
sudo -u zabbix /usr/lib/zabbix/alertscripts/ntfy.sh homelab-alerts "Test" "Проверка из Zabbix"

Если JSON с id вернулся — сеть, токен и топик живые.

6.3. Media type и пользователи

Administration → Media types → Create

  • Name: ntfy, Type: Script, Script name: ntfy.sh (без пути)
  • Script parameters — три отдельные строки, как есть: {ALERT.SENDTO}, {ALERT.SUBJECT}, {ALERT.MESSAGE}

Administration → Users — у каждого, кто должен получать пуш, вкладка Media → Add:

  • Type: ntfy
  • Send to: имя топика (homelab-alerts), не email
  • When active: 1-7,00:00-24:00
  • Use if severity — те же уровни, что у триггеров

Пользователь Enabled. Нет строки Media — в журнале будет No media defined for user, скрипт не вызовется.

6.4. Текст алерта (иначе Test зелёный, триггер молчит)

Реальный триггер без шаблона падает с No message defined for media type. Нужен один из двух вариантов.

A. У media type ntfy вкладка Message templates: Problem, Problem recovery, Problem update (можно скопировать с Email/Telegram).

B. В операции action галка Custom message. Отдельно для проблемы и для восстановления — не один текст на обе.

Проблема (тема, затем тело):

Проблема на {HOST.NAME}: {EVENT.NAME}
Проблема началась в {EVENT.TIME} {EVENT.DATE}
Название проблемы: {EVENT.NAME}
Хост: {HOST.NAME}
Серьёзность: {TRIGGER.SEVERITY}

Исходный ID проблемы: {EVENT.ID}
{TRIGGER.URL}

Восстановление:

Решена проблема на {HOST.NAME}: {EVENT.NAME}
Проблема решена в {EVENT.RECOVERY.TIME} {EVENT.RECOVERY.DATE}
Название проблемы: {EVENT.NAME}
Длительность: {EVENT.DURATION}
Хост: {HOST.NAME}
Серьёзность: {TRIGGER.SEVERITY}

Исходный ID проблемы: {EVENT.ID}
{TRIGGER.URL}

В операции проблемы — только {EVENT.TIME} / {EVENT.DATE}. Макросы {EVENT.RECOVERY.*} там не раскрываются и уйдут в пуш как текст.

6.5. Trigger action

Configuration → Actions → Trigger actions (не Internal). Действие Enabled, после правок — Update.

Вкладка Operations:

  • Type: Send message, шаги 1–1, длительность шага 0 (первый шаг и так сразу; час в Default step duration — пауза до шага 2)
  • Send to users — те, у кого есть media ntfy (или группа, в которой они состоят)
  • Send only via: ntfy (если только Email — Test ntfy зелёный, алерты уйдут в почту)
  • Custom message — как в 6.4, либо шаблоны media type

То же в Recovery operations, иначе придёт только «упало».

Галка «приостанавливать операции для подавленных проблем»: хост в maintenance — пуша не будет.

Уже висящие PROBLEM повторно не шлются. Нужна новая авария.

6.6. Zabbix 6+

Импорт ntfy.yaml в Alerts → Media types, URL своего сервера и Token, у пользователя Send to — топик. Дальше те же action и подписка в приложении.

6.7. Если не приходит

Reports → Action log после нового PROBLEM:

В журнале Что не так
нет строк action не сработал: выключен, не те условия, maintenance, не Trigger actions
No message defined for media type нет шаблонов / Custom message
No media defined for user у пользователя нет media ntfy
ошибка ntfy.sh / curl токен, URL, права на скрипт, DNS с хоста Zabbix
Max/Email sent, ntfy нет в операции Send only via не ntfy или у пользователя нет этой media

Test media type с темой «Тестовая тема» — не алерт. Настоящий пуш: тема вида Проблема на hostname: ….

Замечания

  • Не оставляйте auth-default-access: write-only на публичном сервере без необходимости.
  • Бэкапьте volume ntfy_lib и каталог ./etc.
  • Топики с секретами в имени — слабая защита; лучше auth.

Итог: лёгкие push-алерты для всего домашнего сервера через HTTPS.