Pingveraблог ← Блог
Главная › Блог › Мониторинг крон-задач

Крон молчит: мониторинг фоновых задач, бэкапов и выгрузок

27 июля 2026 · 9 мин чтения

Мониторинг крон-задач: задача сама отмечается, тишина считается событием

У упавшего сайта есть голос: он отвечает пятисоткой, таймаутом, сорванным сертификатом — чем-нибудь, что видно снаружи. У фоновой задачи голоса нет. Крон-строка, которую не перенесли при переезде на новый хостинг, не подаёт никаких признаков: она просто отсутствует. Скрипт бэкапа, падающий на второй секунде из-за кончившегося места, ведёт себя ровно так же, как скрипт, который отработал идеально, — снаружи ни то ни другое не отличить. Единственный сигнал, который у вас вообще есть, — это тишина. Весь мониторинг фоновых задач построен на том, чтобы научиться её слышать.

Направление наблюдения приходится развернуть

Обычная проверка работает так: мы раз в минуту сами обращаемся к сайту и смотрим, что он ответил. Инициатива наша, адрес известен, результат однозначен.

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

Остаётся ровно одна модель: инициативу отдать задаче. Она получает короткий персональный адрес и обращается к нему сама — когда отработала. Мониторинг знает, по какому графику ждать сигнал, и поднимает тревогу, если тот не пришёл. Отрасль называет это 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. Мониторинг, присылающий десять оповещений в неделю про мелочи, перестают читать — и вместе с мелочами пропускают важное. Начните с того, где тишина стоит дорого:

  • Бэкапы — базы и файлов. Самое дорогое и самое незаметное: сломанный бэкап обнаруживается ровно в тот день, когда он понадобился.
  • Выгрузки и импорты — каталог в маркетплейс, остатки из 1С, цены поставщика. Тут тишина превращается в деньги напрямую: магазин работает, а заказов нет — иногда именно поэтому.
  • Продление сертификатов — certbot renew молчит ровно до дня, когда сертификат истёк. Доступность HTTPS мы проверяем и снаружи, но знать о сломавшемся обновлении заранее спокойнее.
  • Очереди и wp-cron — отложенные публикации, письма, синхронизация. Особенно если WP_CRON отключён и вынесен в системный крон.

Каждая такая проверка занимает один слот монитора — на бесплатном тарифе их пять, и этого хватает, чтобы закрыть бэкапы и главную выгрузку.

Что в сухом остатке

Фоновые задачи — единственная часть инфраструктуры, которая ломается совершенно бесшумно и обнаруживается в худший из возможных моментов. Наблюдение за ними стоит пяти минут: завести проверку, дописать && curl … в конец строки crontab. Дальше обычная логика — не отметилась вовремя, упала с ошибкой или зависла, значит придёт оповещение туда же, куда приходят все остальные.

И ровно два места, где стоит быть внимательным: код возврата, который умеет врать, и допуск, который отличает задержку от поломки. Обе ловушки мы прошли на себе — потому и рассказываем.

Частые вопросы

Почему мониторинг крона устроен «наоборот» — задача сама отмечается?

Потому что снаружи фоновая задача невидима. У сайта есть адрес, к нему можно постучаться и посмотреть ответ. У крон-строки на чужом сервере адреса нет, а заходить на сервер клиента мы не станем: наш агент не принимает команд, это архитектурный инвариант. Остаётся единственная модель, при которой мы вообще ничего не запускаем на вашей машине: задача сама обращается к короткому адресу, когда отработала, а мониторинг считает событием отсутствие этого обращения.

Почему код возврата иногда врёт и приходит «успех» при упавшей задаче?

Потому что $? в bash возвращает статус последней выполненной команды, а не той, которую вы имели в виду. Если между задачей и чтением $? стоит ещё что-нибудь — echo, tail, закрывающая скобка группы — код возьмётся от неё, и упавшая задача отчитается успехом. Для конвейера нужен set -o pipefail, иначе статус берётся от последнего звена, а не от упавшего. Правило: между самой задачей и rc=$? не должно быть ничего.

Что мониторинг крона принципиально не видит?

Всё, о чём задача не рассказала сама. Мы знаем время сигнала, его тип (началась, успех, провал), код возврата и до 10 КБ текста, который задача прислала в теле запроса. Если бэкап отработал успешно, но записал пустой архив, — для мониторинга это успех. Проверять содержимое результата должна сама задача, а нам сообщать ненулевой код возврата. Зависание ловится только у тех задач, которые размечены сигналом start.

Задача запускается не по расписанию, а по событию — это подойдёт?

Да, если у неё есть ожидаемая частота. Проверка умеет работать не только по cron-выражению, но и по периоду: «сигнал должен приходить не реже, чем раз в N минут или часов». Отсчёт идёт от предыдущего сигнала. Для задач, у которых частоты нет в принципе (запускается когда придёт файл, а придёт он неизвестно когда), мониторинг тишины не подходит — тишина у них законное состояние.

Поставьте бэкапы под наблюдение

Pingvera следит и за сайтами снаружи, и за фоновыми задачами внутри: не отметилась вовремя, упала с ошибкой или зависла — придёт оповещение в Telegram, Max или на почту. Без единого подключения к вашему серверу. Бесплатный тариф — до 5 проверок, без карты.

Начать бесплатно

Читайте также: CLI Pingvera: мониторинг из терминала и скриптов · Terraform-провайдер Pingvera: мониторинг как код · Не приходят заявки с сайта: почему форма молчит и как это ловить · Мониторинг, который не заходит на сервер клиента · Бесплатно проверить сайт.

← Все статьи · Политика конфиденциальности · pingvera.ru · Telegram-канал

На сайте осуществляется обработка пользовательских данных с использованием Cookie в соответствии с Политикой конфиденциальности.