Что происходит с задачами cron в час, которого не было или который был дважды
Два раза в год в странах, которые всё ещё переводят стрелки, с локальным временем на сервере происходит нечто, что plain-текстовый файл /etc/crontab совершенно не готов обрабатывать: один конкретный час либо исчезает из суток, либо повторяется дважды подряд. Если у вас есть задача, расписание которой попадает ровно в этот интервал, она может молча не выполниться ни разу — или отработать два раза за одну ночь. Разбираемся, откуда берётся этот эффект и почему самый надёжный способ его убрать — вообще не иметь на сервере часового пояса с переходами.
Содержание
- Что физически происходит с часами дважды в год
- Почему это вообще касается cron
- Весенний переход: почему задача может не выполниться совсем
- Осенний переход: почему задача может выполниться дважды
- Таблица: что происходит в обе стороны перехода
- Почему UTC убирает саму возможность такой путаницы
- Что делать, если полностью перейти на UTC пока нельзя
Что физически происходит с часами дважды в год
Переход на летнее и зимнее время (DST, daylight saving time) — это не плавное смещение, а мгновенный скачок стрелок на конкретной секунде. В большинстве регионов, где переходы ещё практикуются, это происходит ночью, когда трафика и активности меньше всего, — что, впрочем, не спасает от проблем именно с фоновыми задачами, потому что весь ночной cron живёт как раз в этом окне.
Весной происходит так называемый «прыжок вперёд»: в момент, скажем, 02:00 часы сразу переставляются на 03:00. Интервал с 02:00:00 до 02:59:59 в этот день просто не существует в локальном времени — его не было. Календарь дня короче на час, хотя в реальности, по физическому времени, прошёл ровно час, никуда не делся.
Осенью происходит обратное — «откат назад»: в момент 03:00 часы переводятся обратно на 02:00. В результате интервал с 02:00:00 до 02:59:59 в эту ночь проживается дважды: сначала как первый проход, затем — после отката — второй раз с теми же самыми показаниями часов. Календарь дня на час длиннее, и любая метка времени вида «02:37» в логах в эту ночь неоднозначна: было это до отката или после?
Важно: конкретный час, в который происходит переход, и его точное календарное число зависят от региона и локального законодательства — это не мировая константа. Если сервер стоит с локальным часовым поясом, значение имеет часовой пояс именно этого сервера (или пояс, который вы ему назначили), а не то, что происходит у вас в браузере или в офисе.
Почему это вообще касается cron
Классический демон cron (в Linux это обычно cronie или vixie-cron, в зависимости от дистрибутива) читает crontab и каждую минуту сравнивает текущее время по системным часам с расписанием: если минута, час, число, месяц и день недели совпали — задача запускается. Ключевое слово здесь — «текущее время по системным часам», то есть локальное время системы, wall-clock time, а не непрерывный поток секунд.
Пока часовой пояс на сервере не меняется и переходов на летнее время не происходит, разница между «локальным временем» и «монотонно идущим временем» неощутима: минута системных часов всегда равна одной прожитой минуте. Но в ночь перехода это равенство ломается ровно на один час — и cron, который ничего не знает о физике происходящего, а просто смотрит на показания часов, либо не увидит момент, на который назначена задача, либо увидит его дважды.
Это касается не только классического cron. systemd-таймеры с директивой OnCalendar (в отличие от OnUnitActiveSec, которая считает интервалы от предыдущего запуска и переходов не боится) устроены точно так же — они тоже сверяются с календарным locale-time. Планировщики в приложениях (Celery beat, Laravel Scheduler, cron-модули в Node.js вроде node-cron), если они настроены работать с локальной таймзоной сервера, наследуют ту же проблему на уровне логики, даже если формально не используют системный cron.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверВесенний переход: почему задача может не выполниться совсем
Представим задачу, которая должна запускаться каждую ночь в 02:30:
30 2 * * * /usr/local/bin/nightly-report.sh
В обычную ночь всё просто: часы дошли до 02:30, cron сверил расписание, запустил скрипт. Но в ночь весеннего перехода, если 02:00 сразу становится 03:00, время 02:30 физически не наступает — система «перепрыгивает» через него. Cron проверяет расписание раз в минуту и просто не застаёт момент, когда часы показывают 02:30, потому что такого момента в этот день не было. В итоге задача в эту ночь не запускается вообще — ни разу, без ошибки, без записи в лог, без exit-кода, потому что сам факт запуска зависел от совпадения с несуществующей отметкой времени.
Это особенно неприятно, если скрипт критичен — например, ротация логов, снятие бэкапа перед началом рабочего дня или отправка ночного отчёта. Проблема в том, что «пропуск» ничем не отличается от тихого сбоя: cron не пишет «час не наступил, задача отменена», он просто не имеет повода её запустить. Если у вас нет отдельного мониторинга выполнения (а не просто мониторинга «жив ли демон cron»), вы узнаёте о пропуске только тогда, когда кто-то заметит отсутствие отчёта или бэкапа — иногда через несколько дней.
Отдельно стоит сказать: то, как именно разные версии и сборки cron обрабатывают этот конкретный случай, может отличаться в деталях — где-то есть попытка «довыполнить» пропущенные задачи при следующем тике, если это разрешено конфигурацией, а где-то такой логики нет вовсе. Полагаться на то, что ваш конкретный cron «сам разрулит» пропуск, не стоит — надёжнее исходить из того, что задача внутри пропавшего часа в этот день не выполнится.
Осенний переход: почему задача может выполниться дважды
Возьмём ту же задачу на 02:30. В ночь осеннего перехода, когда 03:00 откатывается обратно на 02:00, интервал 02:00–02:59 проживается дважды. Формально условие «сейчас 02:30» становится истинным два раза за одну ночь: первый раз — при первом проходе через этот интервал, второй раз — после отката, когда часы во второй раз доходят до той же отметки.
Что произойдёт дальше, зависит от конкретной реализации демона cron и от того, как именно ядро и системные библиотеки на этом сервере обрабатывают переход. В одних случаях задача действительно выполняется дважды — cron не хранит состояние «этот час уже отработал» между проверками, он просто сравнивает текущие показания часов с расписанием на каждом тике. В других случаях (например, если демон опирается не только на wall-clock, но и на внутренний монотонный таймер для дедупликации ближайшего тика) второй запуск может быть подавлен. Точное поведение зависит от версии и сборки cron на конкретном дистрибутиве, поэтому лучше не рассчитывать заранее, как поведёт себя именно ваш сервер, — считайте дублирование реальным риском и проверяйте постфактум по логам.
Двойной запуск обычно куда заметнее пропуска, потому что оставляет след: два письма с одним и тем же отчётом, два запуска резервного копирования подряд, задвоенные записи в базе, если скрипт не идемпотентен. Именно поэтому такие инциденты чаще попадают в поле зрения команды, чем тихий пропуск весной, — но это не значит, что весенний пропуск менее вреден, просто он менее заметен.
Таблица: что происходит в обе стороны перехода
| Переход | Что с часами | Что видит cron | Типичное следствие для задачи внутри окна |
|---|---|---|---|
| Весна (перевод вперёд) | Час физически «вырезается» из суток | Отметка времени внутри вырезанного часа не наступает | Задача не запускается ни разу в эту ночь |
| Осень (перевод назад) | Час физически «повторяется» дважды | Отметка времени внутри повторного часа наступает дважды | Задача может запуститься дважды подряд (поведение зависит от реализации cron) |
| Любой переход | Момент скачка смещается на конкретную секунду | Логи в окне перехода содержат неоднозначные или пропущенные метки | Сложно восстановить точную последовательность событий по времени в логах |
Обратите внимание на нижнюю строку отдельно: даже если ваша задача расписана вне «опасного» часа, сам факт перехода всё равно делает логи в эту ночь менее надёжными для диагностики — метки времени в интервале перехода не идут строго по возрастанию в привычном смысле.
Почему UTC убирает саму возможность такой путаницы
У часового пояса UTC нет перехода на летнее и зимнее время — это фиксированное смещение (+00:00), которое никогда не сдвигается ни на секунду ни по каким календарным правилам. Если системные часы сервера настроены на UTC, для cron просто не существует ночи, в которую час пропадает или повторяется: сутки в UTC всегда состоят ровно из 24 часов, 1440 минут, без исключений. Расписание вида 30 2 * * * в этом случае гарантированно наступает один раз в сутки, каждый день, без вариантов.
Это не значит, что переход на летнее время перестаёт существовать в реальности — он по-прежнему происходит для людей, которые живут по местному времени. Но сервер, настроенный на UTC, просто не участвует в этом ритуале: для операционной системы и всех процессов, читающих системные часы, DST не существует в принципе, потому что смещение UTC не меняется от того, что где-то на планете перевели стрелки.
Проверить текущий часовой пояс сервера и переключить его на UTC можно через timedatectl на системах с systemd:
timedatectl status
timedatectl list-timezones | grep -i UTC
sudo timedatectl set-timezone UTC
После смены пояса стоит перепроверить, что она действительно применилась и что расписания cron продолжают ссылаться на системное время, а не на что-то захардкоженное отдельно:
date
timedatectl status
cat /etc/timezone
Если сервис или планировщик внутри приложения (не системный cron, а, например, Celery beat или встроенный шедулер в фреймворке) явно указывает часовой пояс в конфиге, недостаточно поменять таймзону только на уровне ОС — нужно проверить и привести к UTC настройку самого приложения тоже, иначе получится наполовину решённая проблема: система в UTC, а логика планировщика внутри всё ещё привязана к локальному поясу с переходами.
Практический побочный эффект перехода на UTC — расписание задач больше не совпадает с «человеческим» временем на глаз. Если раньше 30 2 * * * означало половину третьего ночи по местному времени, после перехода на UTC вам придётся пересчитать это значение с учётом текущего смещения вашего региона от UTC (и помнить, что зимой и летом это смещение разное, если вы всё ещё думаете в терминах местного времени). Это не баг, а прямое следствие того, что вы сознательно убрали переменное смещение из уравнения — компромисс между предсказуемостью расписания и удобством чтения crontab «на глаз». Подробно про сам механизм смены пояса и что при этом происходит с логами разобрано в статье про настройку часового пояса на сервере.
Что делать, если полностью перейти на UTC пока нельзя
Иногда часовой пояс с DST оставляют намеренно — например, если сервер обслуживает только один регион и все, кто читает логи и получает уведомления, привыкли именно к местному времени, а пересчитывать смещения в голове никто не хочет. В этом случае риск пропуска или дублирования в ночь перехода никуда не девается, но им можно управлять:
- Не ставить критичные задачи ровно в «опасный» диапазон. Если переходы в вашем регионе исторически происходят в интервале с 02:00 до 04:00, старайтесь не назначать туда ничего важного — сдвиньте на, например, 05:00 или на время до полуночи.
- Сделать задачи идемпотентными там, где это возможно. Если скрипт можно безопасно запустить дважды подряд без побочных эффектов (проверка на «уже выполнено сегодня» перед основной работой), риск двойного запуска осенью перестаёт быть критичным.
- Мониторить факт выполнения, а не только факт существования cron-демона. Внешний контроль вроде healthchecks-подхода, когда задача сама отправляет сигнал по завершении, а система следит за тем, что сигнал пришёл вовремя, ловит и пропуск, и повтор — в отличие от простого «cron работает». Подробнее об этом подходе — в статье про мониторинг cron-задач через healthchecks.io.
- Логировать факт запуска с полной меткой времени, включая смещение, а не только часы и минуты — это помогает потом разобрать по логам, что именно произошло в ночь перехода, если что-то пошло не так.
- Проверять расписание за неделю до ближайшего перехода, если у вас есть задачи внутри диапазона 00:00–06:00 по локальному времени — это дешевле, чем разбирать инцидент постфактум.
Если хотите увидеть, как выглядит реальный инцидент с дублированием отчёта из-за перехода на летнее время, — есть отдельный разбор конкретного случая в статье «Переход на летнее время запустил ночной отчёт два раза». А базовые принципы настройки самого cron, включая синтаксис расписания и типичные грабли с путями и переменными окружения, разобраны в статье «Как установить и настроить cron-задачи на VPS».
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Затрагивает ли эта проблема серверы, которые уже настроены на UTC?
Нет. У UTC нет переходов на летнее и зимнее время, поэтому для системных часов, настроенных на UTC, «опасного» часа просто не существует — cron всегда видит ровно 24 часа в сутках.
Можно ли узнать заранее, в какую именно ночь и час произойдёт переход у конкретного часового пояса?
Да, эта информация есть в системной базе часовых поясов (tzdata), которую использует timedatectl и утилиты вроде date. Правила периодически обновляются пакетными обновлениями ОС, если законодательство региона меняется, поэтому стоит следить за обновлениями пакета tzdata, а не хардкодить дату перехода в документации проекта.
Что будет, если сервер физически стоит в одной стране, а часовой пояс на нём выставлен по другой?
Часы будут вести себя ровно так, как предписывают правила выставленного пояса, независимо от физического расположения сервера. Если, например, на UK-сервере выставлен часовой пояс с переходами другого региона, «опасный» час будет наступать по расписанию именно этого региона, а не по местному для дата-центра времени.
Затронет ли переход задачи, расписание которых не попадает в интервал перехода?
Сами задачи — нет, они выполнятся штатно. Но диагностика по логам в эту ночь может быть менее надёжной, потому что метки времени в самом интервале перехода неоднозначны или отсутствуют, и это иногда мешает восстановить точную последовательность событий, если параллельно случилось что-то ещё.
Есть ли смысл переводить в UTC уже давно работающий сервер, если проблем с cron пока не было?
Смысл есть, если на сервере действительно есть задачи, критичные к точному времени выполнения, или если удобнее иметь однозначные метки времени в логах для будущей диагностики. Если таких задач нет и вся команда стабильно читает логи по локальному времени, экстренной необходимости менять пояс задним числом нет — но стоит хотя бы держать это в голове перед ближайшим переходом.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →