Pricing Blog Compare Glossary
Login Start Free

Cron Expressions Explained: Complete Syntax Guide

Master cron expressions with detailed explanation of all 5 fields, special characters, step values, common patterns, timezone handling, and debugging tips.

2026-03-26 · 12 min · Technical Guide

Cron expression — это пять чисел, которые определяют, когда запустится задача. 0 9 * * 1-5 означает: в 09:00 каждый рабочий день. Это основной способ планировать периодические задачи в Unix и во всех облачных сервисах.

Синтаксис прост на первый взгляд, но дьявол в деталях. Разница между 0 9 15 * ? (9:00 на 15-е число) и 0 9 ? * 5 (9:00 каждую пятницу) может стоить часов отладки. Неправильная интерпретация timezone'а может привести к тому, что задача запустится на час раньше или позже (daylight saving time). Это руководство разберёт все corner case'ы.

Пять полей cron expression

MIN HOUR DOM MON DOW

MIN: минута (0-59)

HOUR: час (0-23, 24-hour format)

DOM: день месяца (1-31)

MON: месяц (1-12, или JAN-DEC)

DOW: день недели (0-7, где 0=воскресенье, 7=воскресенье, 1=понедельник)

Пример: разбор выражения

30 14 15 6 *
│  │  │  │  └─ день недели: любой (*) 
│  │  │  └──── месяц: июнь (6)
│  │  └─────── день месяца: 15
│  └────────── час: 14 (2:00 PM)
└───────────── минута: 30

Результат: 14:30 (2:30 PM) на 15 июня любого года, любого дня недели

Ключевой момент: если день месяца И день недели заполнены (не *), они работают как OR, а не AND. Задача запустится если совпадает день месяца ИЛИ день недели. Это частый источник ошибок.

Специальные символы

* (звёздочка) — любое значение

0 * * * * → каждый час в начале (00:00, 01:00, 02:00, ...)

* * * * * → каждую минуту

0 0 * * * → каждый день в полночь

/ (слеш) — step/интервал

*/5 * * * * → каждые 5 минут (0, 5, 10, 15, ...)

0 */4 * * * → каждые 4 часа (00:00, 04:00, 08:00, ...)

0 0 1 */3 * → каждый третий месяц на 1-е число (январь, апрель, июль, ...)

0 0 * */6 * → каждый 6-й месяц

- (тире) — диапазон

0 9-17 * * * → каждый час с 09:00 по 17:00 (рабочий день)

0 9 * 1-3 * → 09:00 каждый день с января по март

0 9 * * 1-5 → 09:00 с понедельника по пятницу (рабочие дни)

, (запятая) — список значений

0 9,12,15 * * * → в 09:00, 12:00, 15:00

0 0 1,15 * * → в полночь на 1-е и 15-е число

0 9 * * 1,3,5 → 09:00 в понедельник, среду, пятницу

? (вопрос) — нет значения

? используется ТОЛЬКО в поле day-of-month ИЛИ day-of-week (никогда в обоих сразу). Это говорит: "в этом поле нет ограничения". Нужно это для избежания конфликтов.

0 9 15 * ? → 09:00 на 15-е число (день недели не важен)

0 9 ? * 1 → 09:00 каждый понедельник (день месяца не важен)

0 9 ? * 1-5 → 09:00 с пн по пт

Распространённые примеры

Ежедневные задачи

0 0 * * * → полночь каждый день

0 6 * * * → 06:00 каждый день (рассвет, популярно для backup'ов)

0 12 * * * → полдень каждый день

0 23 * * * → 23:00 каждый день

Еженедельные задачи

0 9 ? * 1 → 09:00 каждый понедельник

0 9 ? * 1-5 → 09:00 с пн по пт (рабочие дни)

0 9 ? * 0 → 09:00 каждое воскресенье

0 9 ? * 0,6 → 09:00 в выходные (суббота и воскресенье)

Ежемесячные задачи

0 0 1 * ? → полночь на 1-е число каждого месяца

0 0 15 * ? → полночь на 15-е число каждого месяца

0 0 L * ? → последний день месяца (некоторые системы поддерживают L)

0 0 ? * 1L → последний понедельник месяца

Интервальные задачи

*/5 * * * * → каждые 5 минут

0 */6 * * * → каждые 6 часов (00:00, 06:00, 12:00, 18:00)

0 */4 * * * → каждые 4 часа

*/30 * * * * → каждые 30 минут

15 * * * * → в 15-й минуте каждого часа

Рабочие часы (business hours)

0 9-17 * * 1-5 → каждый час с 09:00 по 17:59 в рабочие дни

0 */2 * * 1-5 → каждые 2 часа в рабочие дни

0 18 * * 1-5 → 18:00 (6 PM) в рабочие дни (end of business)

Shortcut'ы (@-синтаксис)

Некоторые системы (Celery, APScheduler, Kubernetes) поддерживают сокращённые записи:

@yearly или @annually = 0 0 1 1 * (1 января в полночь)

@monthly = 0 0 1 * * (1-е число в полночь)

@weekly = 0 0 ? * 0 (воскресенье в полночь)

@daily = 0 0 * * * (полночь)

@hourly = 0 * * * * (начало каждого часа)

@reboot = при запуске системы (только в crontab, не в Celery)

Расширенный формат: 6 полей (с секундами)

Стандартный cron имеет 5 полей (минута, час, день, месяц, день недели). Некоторые системы поддерживают 6-field формат с секундами:

SEC MIN HOUR DOM MON DOW

Celery Beat (Python задачи)

Google Cloud Scheduler (не поддерживает, только 5 полей)

Quartz Scheduler (Java, поддерживает 6 полей)

Unix crontab (не поддерживает, только 5 полей)

# 6-field format (Celery)
*/15 * * * * *  # каждые 15 секунд
0 */5 * * * *   # каждые 5 минут

Timezone в cron

Cron работает в конкретном timezone'е. Проблема: при переводе на летнее время (daylight saving time), часы смещаются на 1 час. Если ваша задача должна запустить в 2:00 AM, а система перелетает с 1:59 AM на 3:00 AM — ваша задача никогда не запустится.

UTC vs Local timezone

Unix crontab: использует local timezone сервера (из /etc/timezone)

Celery Beat: укажите timezone явно в конфиге

Google Cloud Scheduler: укажите timezone явно (default: UTC)

AWS EventBridge: использует UTC по умолчанию

Kubernetes CronJob: использует UTC всегда

Правильная конфигурация в Celery

# celery.py
from celery import Celery
from celery.schedules import crontab

app = Celery('myapp')

# Укажите timezone явно
app.conf.timezone = 'Europe/Moscow'  # или 'America/New_York', 'UTC'

app.conf.beat_schedule = {
    'daily-backup': {
        'task': 'backup.run',
        'schedule': crontab(hour=2, minute=0),
        # 2:00 AM по московскому времени
    },
    'hourly-sync': {
        'task': 'sync.run',
        'schedule': crontab(minute=0),
        # Каждый час в начале
    },
}

Всегда укажите timezone явно. Не полагайтесь на default сервера.

Частые ошибки в cron выражениях

Ошибка 1: неправильный диапазон дней недели

❌ Неправильно: 0 9 * * 1-6 (интерпретируется как пн-сб)

❌ Неправильно: 0 9 * * 0-5 (интерпретируется как вс-пт, это не рабочие дни!)

✓ Правильно: 0 9 * * 1-5 (пн-пт = рабочие дни)

Ошибка 2: оба поля day и weekday заполнены

❌ Неправильно: 0 9 15 * 1

Это запустит задачу на 15-е число ИЛИ в понедельник. Если 15-е падает на четверг, задача запустится дважды (раз на 15-е, раз на понедельник 16-го/17-го?).

✓ Правильно: 0 9 15 * ? (ровно на 15-е число)

✓ Правильно: 0 9 ? * 1 (ровно в понедельник)

Ошибка 3: забыли ноль в начале

❌ Неправильно: 9 9 * * *

Это означает: в 09:09 (9-я минута 9-го часа), не в 09:00

✓ Правильно: 0 9 * * * (в 09:00, нулевая минута)

Ошибка 4: interval охватывает границу

❌ Неправильно: 0 9-18/5 * * *

Системы расходятся в интерпретации. Обычно это означает: 9:00, 14:00 (пропускает 18-е часы конца дня).

✓ Правильно: 0 9,14 * * * (явно перечислить часы)

Ошибка 5: неправильный месяц

❌ Неправильно: 0 0 1 12 * (ожидаемо декабрь)

Это работает, но ясности нет. Лучше использовать названия месяцев.

✓ Правильно: 0 0 1 DEC * или 0 0 1 12 *

Отладка cron выражений

Online validators

Перед запуском cron в production — проверьте на online validator'е. Введите выражение, посмотрите следующие 10 запусков.

crontab.guru — простой, instant feedback, поддерживает 5-field

Логирование в crontab

# Unix crontab
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=admin@example.com

# Outputs to /var/log/syslog
0 9 * * * /path/to/script.sh

# Or redirect to file
0 9 * * * /path/to/script.sh >> /var/log/my-cron.log 2>&1

Всегда логируйте cron задачи. Молчаливые failure'ы — самые дорогие. Если задача не запустилась — лог покажет, что произошло (permission denied, file not found, exit code ненулевой).

Мониторинг cron задач с AtomPing

Cron задачи не отправляют heartbeat'ы автоматически. Используйте Heartbeat check в AtomPing. Ваша cron задача должна:

# Python cron task
def my_cron_job():
    try:
        # Do work
        result = expensive_operation()
        
        # Send heartbeat to AtomPing
        requests.post(
            'https://your-monitor.atomping.com/heartbeat/my-job',
            json={'status': 'success', 'result': result}
        )
    except Exception as e:
        # Send failure
        requests.post(
            'https://your-monitor.atomping.com/heartbeat/my-job',
            json={'status': 'failed', 'error': str(e)},
            headers={'X-Webhook-Status': 'error'}
        )

# In crontab:
0 9 * * * /usr/bin/python3 /path/to/cron_job.py

Если heartbeat'т не приходит в течение expected window (например, 65 секунд для задачи на 9:00) — AtomPing alert'ит. Это ловит:

✓ Задача не запустилась (cron сломался, скрипт удалён)

✓ Задача зависла (обработка занимает 10+ минут)

✓ Задача упала с ошибкой (exception не caught)

Checklist: cron выражения

Синтаксис: правильно ли заполнены 5 полей (min hour dom mon dow)?

Специальные символы: используйте *, -, /, , ? правильно

День месяца vs день недели: если оба заполнены — используйте ? в одном

Timezone: явно укажите timezone (не полагайтесь на default)

Validation: проверьте на crontab.guru перед production

Логирование: настройте вывод в лог файл

Мониторинг: настройте heartbeat check для отслеживания успешности

Связанные материалы

Cron Job Monitoring — как мониторить cron задачи с Heartbeat checks

Complete Guide to Uptime Monitoring — overview мониторинга

Heartbeat Check — в AtomPing

FAQ

What is a cron expression?

A cron expression is a string that specifies when a scheduled job should run. It uses 5 fields (minute hour day month weekday) to define the schedule in a compact format. For example: '0 9 * * 1-5' means every weekday at 9:00 AM. Cron expressions are used in Unix/Linux systems, cloud schedulers (AWS EventBridge, Google Cloud Scheduler), and background job frameworks (Celery, APScheduler, node-cron).

What do the 5 fields in cron mean?

Minute (0-59): which minute of the hour. Hour (0-23): which hour of the day (24-hour format). Day of Month (1-31): which day of the month. Month (1-12): which month of the year. Day of Week (0-7): which day of the week (0 and 7 are Sunday). The order matters: '30 14 15 6 *' = 2:30 PM on June 15th of any day of the week.

What do the special characters mean in cron?

* (asterisk) = any value in that field. , (comma) = multiple values (e.g., 1,3,5 = 1st, 3rd, 5th). - (dash) = range (e.g., 1-5 = 1 through 5). / (slash) = step/interval (e.g., */5 = every 5 units). ? (question mark) = no specific value (used only in day or weekday to avoid conflicts). Examples: */30 = every 30 minutes, 0-6 = 0 through 6, 9-17 = 9 AM to 5 PM.

What is the difference between day-of-month and day-of-week?

Both specify 'which days'. Day-of-month (3rd field) = calendar days (15, 16, 17). Day-of-week (5th field) = weekdays (Monday, Tuesday, etc.). If you specify both, cron uses OR logic: the job runs on EITHER the specified day-of-month OR the specified day-of-week. To avoid conflicts, use '?' in one field when the other is specified. '0 9 15 * ?' = 9 AM on the 15th day of the month only.

What's the difference between UTC and local timezone in cron?

Cron expressions are evaluated in a specific timezone. By default, cron uses the system's local timezone. In cloud schedulers (AWS, Google Cloud) and modern frameworks (Celery Beat, APScheduler), you specify the timezone explicitly. If you don't specify, assume UTC. This matters for daylight saving time: when clocks spring forward/back, a '9:00 AM' job might run at 8 AM or 10 AM local time. Always set timezone explicitly in production.

How do you monitor cron jobs with AtomPing?

Cron jobs don't send heartbeats automatically. Use the 'Heartbeat' check type: your cron job should POST to an AtomPing endpoint when it completes (including success/error status). If the heartbeat doesn't arrive within expected window (e.g., 65 seconds for a job that runs every 60 seconds), AtomPing alerts you. This detects failures (job didn't run), hangs (job ran but took too long), and crashes (job exited abnormally).

Start monitoring your infrastructure

Start Free View Pricing