Cron в Linux: как правильно настроить задачи по расписанию
Практика3 минРедакция Innovialabs
Коротко
Добавьте задачу через crontab -e, указывайте абсолютные пути, перенаправляйте вывод в лог и оборачивайте долгие задачи во flock, чтобы они не запускались параллельно.
Коротко: как добавить задачу
Откройте редактор своего расписания:
crontab -e
Каждая строка — это время запуска и команда:
*/15 * * * * /usr/bin/php /var/www/app/artisan schedule:run >> /var/log/app-cron.log 2>&1
Посмотреть текущие задачи: crontab -l. Большинство проблем с cron связаны не с синтаксисом, а с окружением: задача работает из терминала, но молча падает по расписанию.
Синтаксис расписания
Пять полей слева направо:
| Поле | Значения |
|---|---|
| Минута | 0-59 |
| Час | 0-23 |
| День месяца | 1-31 |
| Месяц | 1-12 |
| День недели | 0-7 (0 и 7 — воскресенье) |
Примеры:
0 3 * * *— каждый день в 03:00;*/10 * * * *— каждые 10 минут;0 9 * * 1-5— в 09:00 по будням;30 2 1 * *— 1-го числа каждого месяца в 02:30;0 */6 * * *— каждые 6 часов в начале часа.
Время считается по часовому поясу сервера. Проверьте его командой timedatectl, прежде чем ставить задачи «на ночь».
Ловушка: если заданы и день месяца, и день недели, cron запускает задачу, когда совпало любое из условий, а не оба сразу.
Окружение и PATH
Cron запускает команды с минимальным окружением: короткий PATH, другая оболочка (/bin/sh), нет переменных из .bashrc и .env. Отсюда типичная ошибка «command not found».
Что делать:
- указывайте абсолютные пути к программам и файлам (
/usr/bin/node, а неnode); путь можно узнать черезwhich node; - задайте переменные в начале crontab:
SHELL=/bin/bash
PATH=/usr/local/bin:/usr/bin:/bin
- для сложной логики пишите отдельный скрипт, а в cron вызывайте только его;
- символ
%в crontab означает перевод строки — экранируйте его как\%(часто встречается вdate +%F).
Логирование вывода
По умолчанию вывод задачи отправляется по почте локальному пользователю — на большинстве серверов это означает, что он теряется. Перенаправляйте вывод явно:
0 3 * * * /opt/scripts/backup.sh >> /var/log/backup.log 2>&1
2>&1 важно: без него ошибки (stderr) не попадут в лог. Не забудьте про ротацию этого файла, иначе он со временем займет диск. Чтобы проверить, запускался ли cron вообще, смотрите системный журнал: journalctl -u cron или grep CRON /var/log/syslog (название службы зависит от дистрибутива).
Защита от параллельных запусков
Если задача раз в 5 минут иногда работает 7 минут, запуски наложатся: двойные письма, гонки в базе, перегрузка. Простое решение — flock:
*/5 * * * * /usr/bin/flock -n /tmp/sync.lock /opt/scripts/sync.sh
Флаг -n означает: если предыдущий запуск еще держит блокировку, новый просто не стартует.
systemd timers как альтернатива
На современных дистрибутивах можно использовать systemd timers. Нужны два файла: .service (что запускать) и .timer (когда).
# /etc/systemd/system/backup.timer
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
[Install]
WantedBy=timers.target
| cron | systemd timer | |
|---|---|---|
| Настройка | одна строка | два файла |
| Логи | нужно перенаправлять | автоматически в journalctl |
| Пропущенные запуски | теряются | Persistent=true догоняет |
| Параллельные запуски | нужен flock | сервис не запустится дважды |
| Лимиты ресурсов | нет | есть (CPU, память) |
Для пары простых задач cron удобнее. Для важных фоновых процессов timers дают больше контроля.
Частые ошибки
- Относительные пути и надежда на
PATHиз терминала. - Вывод никуда не пишется, и о сбоях никто не знает.
- Задачи в UTC, а ожидание — по местному времени.
- Отредактировали
/etc/crontab, забыв, что там есть дополнительное поле с именем пользователя.