
У упавшего сайта есть голос: он отвечает пятисоткой, таймаутом, сорванным сертификатом — чем-нибудь, что видно снаружи. У фоновой задачи голоса нет. Крон-строка, которую не перенесли при переезде на новый хостинг, не подаёт никаких признаков: она просто отсутствует. Скрипт бэкапа, падающий на второй секунде из-за кончившегося места, ведёт себя ровно так же, как скрипт, который отработал идеально, — снаружи ни то ни другое не отличить. Единственный сигнал, который у вас вообще есть, — это тишина. Весь мониторинг фоновых задач построен на том, чтобы научиться её слышать.
Обычная проверка работает так: мы раз в минуту сами обращаемся к сайту и смотрим, что он ответил. Инициатива наша, адрес известен, результат однозначен.
С крон-задачей ни одного из трёх условий нет. Она живёт внутри сервера, у неё нет адреса, и достучаться до неё можно единственным способом — зайти на машину. Этого мы не делаем и не будем: наш агент не принимает команд, у хаба физически нет способа что-либо запустить на клиентском сервере. Это записано в архитектуру как инвариант и не пересматривается.
Остаётся ровно одна модель: инициативу отдать задаче. Она получает короткий персональный адрес и обращается к нему сама — когда отработала. Мониторинг знает, по какому графику ждать сигнал, и поднимает тревогу, если тот не пришёл. Отрасль называет это dead man's switch: срабатывает не действие, а его отсутствие.
Разворот направления меняет и сам вопрос, на который отвечает мониторинг. Внешняя проверка отвечает «сайт открывается?». Сигнал от задачи отвечает «работа сделана?». Второе ближе к делу: сайт может прекрасно открываться, пока база не бэкапится, остатки не обновляются, а отложенные публикации стоят в очереди.
Проверка типа «Задача» выдаёт адрес. Дальше строка crontab выглядит так:
30 3 * * * /usr/local/bin/backup.sh && curl -fsS -m 10 https://app.pingvera.ru/hb/hb_XXXX
Здесь важен каждый символ, и меньше всего — адрес.
&& — договор о том, что считать успехом. Пинг уходит, только если скрипт завершился с нулевым кодом. Поставьте ; вместо &&, и вы будете отслеживать факт запуска крона, а не факт выполнения работы. Разница между этими двумя вещами и есть весь смысл затеи.
-m 10 — предохранитель. Мониторинг не имеет права стать причиной аварии: если наш сервис недоступен или тормозит, curl обязан сдаться через десять секунд и отпустить вашу задачу. -f и -s — чтобы ошибка не потерялась и чтобы в почту крона не сыпался индикатор прогресса.
Голого пинга хватает для ответа «дошла ли задача до конца». Дальше начинается интересное: у адреса есть три хвоста.
/start — задача началась. Сам по себе признаком жизни он не считается, иначе зависший бэкап выглядел бы здоровым. Зато даёт две вещи: длительность каждого запуска и возможность поймать зависание — если после старта завершение не пришло за отведённое время, открывается событие «задача запустилась и не завершилась».
/fail — задача сама сообщает, что упала, и событие открывается сразу, не дожидаясь следующего срока. Для ночного бэкапа это разница между «узнали в 3:35» и «узнали через сутки».
/<код возврата> — то же самое, но числом: /0 означает успех, любой другой код — провал. Тело запроса, до 10 КБ, сохраняется как лог запуска и видно в карточке рядом с временем и длительностью. Когда в три часа ночи придёт оповещение, ответ на вопрос «что именно случилось» будет уже там, а не в почте крона, которую никто не читает.
Мы ставили эту схему на настоящий крон на своём сервере — не в тестах, а живьём, с реальной задачей раз в две минуты. За первый час она поймала две ошибки, обе наши, и обе поучительные.
Обёртка выглядела безобидно:
out=$( { df -h /mnt/backup | tail -1; echo "готово"; } 2>&1 )
rc=$? # ← всегда 0
Задача честно ломалась, монтирования не было, а мониторинг получал /0 и рисовал зелёное. Причина простая: $? — это статус последней выполненной команды, а последним тут оказался echo, который не падает никогда.
Правило, которое из этого следует: между самой задачей и rc=$? не должно быть ничего. Если задача — конвейер, добавьте set -o pipefail, иначе код придёт от последнего звена, а не от упавшего. Правильная обёртка короче неправильной:
#!/bin/bash
set -o pipefail
PING=https://app.pingvera.ru/hb/hb_XXXX
curl -fsS -m 10 "$PING/start"
out=$(/usr/local/bin/backup.sh 2>&1); rc=$?
curl -fsS -m 10 --data-raw "$out" "$PING/$rc"
Ошибка эта коварна именно тем, что не ломает ничего видимого. Мониторинг работает, письма не приходят, все довольны — и ровно поэтому её стоит проверить у себя прямо сейчас, если обёртка писалась на бегу.
Вторую ошибку допустили уже мы, в самом продукте. Свежесозданная проверка присылала событие «задача не отметилась» через четыре минуты — то есть ровно в тот момент, когда человек ещё копирует адрес и идёт вписывать команду в crontab.
Формально всё верно: сигнала нет, срок вышел, событие открыто. По сути — вредно. Отличить «крон сломан» от «команду ещё не подключили» невозможно в принципе, а оповещение, которое предсказуемо срабатывает не по делу, делает ровно одну вещь: приучает игнорировать оповещения. Один раз промотал — и промотаешь следующее, настоящее.
Мы дали пятнадцать минут на первый сигнал. Не потому что это красивое число, а потому что за это время успевают подключить команду, и при этом забытая проверка не остаётся молчать до вечера. Задача, которая раньше отмечалась и вдруг замолчала, реагирует по-прежнему мгновенно — пятнадцать минут касаются только тех, кто не подавал признаков жизни ни разу.
«Раз в сутки» и «каждую ночь в 03:30» звучат похоже, но ведут себя по-разному. Интервал отсчитывается от предыдущего сигнала: задача, отработавшая в 03:31, следующего срока ждёт до 03:31, потом до 03:33 — и постепенно уезжает. Расписание привязано к часам и никуда не едет.
Поэтому у задачи можно задать cron-выражение и часовой пояс отдельно. Пояс — не формальность: сервер может жить в UTC, вы в Москве, клиент во Владивостоке, и «три часа ночи» у всех троих наступает в разное время. Мониторинг обязан ждать сигнал тогда же, когда крон его запускает.
Само выражение собирается мышью: частота, время, дни недели, число месяца. Строка crontab пересчитывается на каждое действие и лежит рядом — её копируют к себе на сервер. Обратный разбор тоже работает: существующая задача открывается своими контролами, а не голой строкой.
И отдельно — то, ради чего стоило возиться. Интерфейс проговаривает выражение вслух: «по будням в 09:00», «каждые 15 мин», «1-го числа в 05:00», — и показывает шесть ближайших ожидаемых запусков в выбранном поясе. Причина конкретная: самая частая ошибка в cron — перепутанные местами день месяца и день недели. Выражение при этом остаётся синтаксически безупречным, крон его принимает, а работает оно не тогда. Заметить это можно единственным способом: если система скажет, как она вас поняла, до того как вы нажали «Сохранить».
Задача редко стартует секунда в секунду. Сервер занят, предыдущий скрипт держит блокировку, дамп большой базы идёт двадцать минут. Поэтому кроме срока есть допуск — сколько ждать сверх него, прежде чем поднимать тревогу.
Пока срок прошёл, а допуск ещё идёт, задача помечается просроченной — жёлтым в списке. Событие в этот момент не открывается и никого не будит: это состояние для глаз, а не для тревоги. Практически: ночному бэкапу разумно дать полчаса, выгрузке остатков каждые 15 минут — пару минут. Если оставить допуск нулевым, он берётся по умолчанию: у расписания — пять минут, у периодической проверки — сам период.
Дальше всё как с обычными проверками, и это, пожалуй, главный практический довод: не нужно заводить ещё один сервис со своими оповещениями и своим логином. Событие приходит в те же каналы — почта, Telegram, Max, VK, Slack, Discord, вебхук, — попадает в общую ленту и в отчёт клиенту за месяц. Когда задача снова отметится успешно, событие закроется само: ручного «починил» у нас нет по устройству — мониторинг фиксирует факты, а не ведёт тикеты.
Такой мониторинг видит ровно то, о чём задача рассказала сама, и ни байтом больше. Список того, чего он не умеет, стоит знать до внедрения, а не после.
Он не запускает задачи. Это не планировщик и не замена крону. Если крон-демон на сервере умер целиком, мы сообщим о тишине — но запустить за него ничего не сможем.
Он не видит содержимое результата. Бэкап, который отработал с нулевым кодом и записал пустой архив, для мониторинга — успех. Проверять осмысленность результата должна сама задача: размер файла, число строк, целостность архива, — и сообщать ненулевой код, если что-то не так. Мы честно передадим то, что она сказала.
Он не поймает зависание у неразмеченной задачи. «Запустилась и не завершилась» работает только там, где есть сигнал /start. Без него зависшая задача выглядит как молчащая — событие придёт, но позже и с другой формулировкой.
Адрес пинга — это секрет. Отдельного пароля у него нет, вся защита в самом адресе: так проще всего дёрнуть его из любого скрипта, крона или systemd-юнита. Знающий адрес может отметить задачу выполненной, но не может ничего прочитать о вашем аккаунте. Обращаться с ним стоит как с токеном и не публиковать в открытых репозиториях.
История запусков не бесконечна. Храним последние 50 сигналов на задачу и не дольше 30 дней. Этого хватает, чтобы разобрать инцидент и увидеть, что выгрузка, которая шла две минуты, последнюю неделю идёт двадцать. Для архива логов есть системы логирования, это не они.
Не стоит вешать проверку на каждую строку crontab. Мониторинг, присылающий десять оповещений в неделю про мелочи, перестают читать — и вместе с мелочами пропускают важное. Начните с того, где тишина стоит дорого:
certbot renew молчит ровно до дня, когда сертификат истёк. Доступность HTTPS мы проверяем и снаружи, но знать о сломавшемся обновлении заранее спокойнее.WP_CRON отключён и вынесен в системный крон.Каждая такая проверка занимает один слот монитора — на бесплатном тарифе их пять, и этого хватает, чтобы закрыть бэкапы и главную выгрузку.
Фоновые задачи — единственная часть инфраструктуры, которая ломается совершенно бесшумно и обнаруживается в худший из возможных моментов. Наблюдение за ними стоит пяти минут: завести проверку, дописать && curl … в конец строки crontab. Дальше обычная логика — не отметилась вовремя, упала с ошибкой или зависла, значит придёт оповещение туда же, куда приходят все остальные.
И ровно два места, где стоит быть внимательным: код возврата, который умеет врать, и допуск, который отличает задержку от поломки. Обе ловушки мы прошли на себе — потому и рассказываем.
Потому что снаружи фоновая задача невидима. У сайта есть адрес, к нему можно постучаться и посмотреть ответ. У крон-строки на чужом сервере адреса нет, а заходить на сервер клиента мы не станем: наш агент не принимает команд, это архитектурный инвариант. Остаётся единственная модель, при которой мы вообще ничего не запускаем на вашей машине: задача сама обращается к короткому адресу, когда отработала, а мониторинг считает событием отсутствие этого обращения.
Потому что $? в bash возвращает статус последней выполненной команды, а не той, которую вы имели в виду. Если между задачей и чтением $? стоит ещё что-нибудь — echo, tail, закрывающая скобка группы — код возьмётся от неё, и упавшая задача отчитается успехом. Для конвейера нужен set -o pipefail, иначе статус берётся от последнего звена, а не от упавшего. Правило: между самой задачей и rc=$? не должно быть ничего.
Всё, о чём задача не рассказала сама. Мы знаем время сигнала, его тип (началась, успех, провал), код возврата и до 10 КБ текста, который задача прислала в теле запроса. Если бэкап отработал успешно, но записал пустой архив, — для мониторинга это успех. Проверять содержимое результата должна сама задача, а нам сообщать ненулевой код возврата. Зависание ловится только у тех задач, которые размечены сигналом start.
Да, если у неё есть ожидаемая частота. Проверка умеет работать не только по cron-выражению, но и по периоду: «сигнал должен приходить не реже, чем раз в N минут или часов». Отсчёт идёт от предыдущего сигнала. Для задач, у которых частоты нет в принципе (запускается когда придёт файл, а придёт он неизвестно когда), мониторинг тишины не подходит — тишина у них законное состояние.
Pingvera следит и за сайтами снаружи, и за фоновыми задачами внутри: не отметилась вовремя, упала с ошибкой или зависла — придёт оповещение в Telegram, Max или на почту. Без единого подключения к вашему серверу. Бесплатный тариф — до 5 проверок, без карты.
Начать бесплатноЧитайте также: CLI Pingvera: мониторинг из терминала и скриптов · Terraform-провайдер Pingvera: мониторинг как код · Не приходят заявки с сайта: почему форма молчит и как это ловить · Мониторинг, который не заходит на сервер клиента · Бесплатно проверить сайт.