
Как запускать задачи по расписанию в Linux: cron и systemd timers
Настраиваем периодический запуск команд через cron и systemd timers, проверяем окружение, журналы, пропущенные запуски и защиту от одновременной работы двух копий.
Содержание
Допустим, раз в ночь нужно создавать отчёт, выгружать резервную копию или очищать временные файлы. Запускать команду вручную ненадёжно: достаточно забыть о ней один раз. В Linux эту работу обычно поручают cron либо таймеру systemd.
Оба варианта запускают команды по расписанию, но наблюдать за ними приходится по-разному. Cron компактен и привычен. systemd разделяет расписание и выполняемую службу, показывает следующий запуск и сохраняет вывод в журнале. Сначала разберём выбор, затем настроим проверяемый пример.
Когда достаточно cron, а когда удобнее timer
Cron подходит для короткого задания, которое уже работает без интерактивного ввода и пишет результат в известное место. Пользовательская таблица cron хранится отдельно для каждой учётной записи, поэтому важно понимать, от чьего имени выполняется строка.
Systemd timer удобнее для серверной задачи, если нужны отдельный журнал, ограничения прав, зависимость от сети, компенсация пропущенного запуска или понятное состояние через systemctl. Таймер отвечает только за время, а одноимённая служба описывает саму команду.
Независимо от планировщика сначала запустите команду вручную от будущего пользователя. Она не должна спрашивать пароль, открывать редактор или ждать подтверждения.
Проверьте команду без расписания
Для учебного примера будем сохранять сведения о корневой файловой системе. Сначала найдите точный путь к df и убедитесь, что команда выполняется от обычного пользователя:
command -v df
/usr/bin/df -h /
Первая строка должна вернуть /usr/bin/df или другой путь на вашей системе. Вторая покажет общий, занятый и свободный объём раздела, на котором расположен /. Если путь отличается, используйте найденное значение в дальнейших примерах.
Простой запуск через cron
Откройте таблицу текущего пользователя штатным редактором cron. Первый запуск может предложить выбрать текстовый редактор — это нормальное одноразовое действие:
crontab -e
Добавьте в конец строку ниже. Пять полей означают минуту, час, день месяца, месяц и день недели. Значение 15 2 * * * запускает команду ежедневно в 02:15 по системному времени сервера:
15 2 * * * /usr/bin/df -h / >> "$HOME/disk-usage.log" 2>&1
Оператор >> дописывает обычный вывод в файл, а 2>&1 направляет туда же сообщения об ошибках. $HOME будет домашним каталогом владельца таблицы. Для системной задачи лучше указать абсолютный путь к журналу и заранее проверить права на каталог.
Сохраните файл и попросите cron показать установленную таблицу. В выводе должна появиться ровно одна добавленная строка:
crontab -l
Не меняйте системное время ради проверки. Временно поставьте ближайшую минуту, дождитесь записи в журнале и верните рабочее расписание через crontab -e. Сообщения самой службы cron на Ubuntu можно искать в системном журнале:
systemctl status cron --no-pager
sudo journalctl -u cron --since "30 minutes ago" --no-pager
Первая команда подтверждает, что планировщик запущен. Вторая показывает его сообщения за последние полчаса. Сам вывод задания будет в disk-usage.log, потому что мы явно перенаправили его туда.
Почему у cron отличается окружение
Интерактивный shell читает пользовательские настройки и формирует PATH, а cron запускает задание с небольшим набором переменных. Из-за этого команда может работать в терминале и завершаться ошибкой по расписанию.
Используйте абсолютные пути к программам и файлам. Не рассчитывайте на текущий каталог: передавайте путь программе либо начинайте сложный скрипт с явного cd. Секреты не следует записывать в строку cron, потому что таблица и список процессов могут раскрыть их; загрузите защищённый файл конфигурации внутри программы.
Та же задача через systemd timer
Создадим системную службу disk-report.service. Она выполнит команду один раз и завершится. Откройте новый файл с правами администратора:
sudoedit /etc/systemd/system/disk-report.service
Поместите в него следующее содержимое. Type=oneshot обозначает конечную задачу, User=nobody не даёт ей прав администратора, а ExecStart содержит абсолютные пути:
[Unit]
Description=Report root filesystem usage
[Service]
Type=oneshot
User=nobody
ExecStart=/usr/bin/df -h /
Для реальной задачи создайте отдельного сервисного пользователя с доступом только к нужным каталогам. Учётная запись nobody здесь подходит, потому что df только читает общие сведения и не создаёт файлов.
Теперь создайте таймер с тем же базовым именем:
sudoedit /etc/systemd/system/disk-report.timer
В секции [Timer] календарное выражение задаёт ежедневный запуск в 02:15. Persistent=true просит systemd выполнить пропущенное срабатывание после включения сервера, а RandomizedDelaySec=10m распределяет нагрузку в пределах десяти минут:
[Unit]
Description=Run disk report every night
[Timer]
OnCalendar=*-*-* 02:15:00
Persistent=true
RandomizedDelaySec=10m
Unit=disk-report.service
[Install]
WantedBy=timers.target
Случайная задержка полезна, когда много серверов одновременно запускают тяжёлую работу. Для задания, которое обязано начаться в точную минуту, параметр можно убрать осознанно.
Проверьте файлы до включения
Systemd умеет проверять синтаксис unit-файлов. Команда ниже не запускает задачу и не включает расписание:
sudo systemd-analyze verify /etc/systemd/system/disk-report.service /etc/systemd/system/disk-report.timer
Исправьте все сообщения об ошибках, относящиеся к этим двум файлам. После сохранения сообщите systemd о новых units и один раз запустите службу вручную:
sudo systemctl daemon-reload
sudo systemctl start disk-report.service
sudo systemctl status disk-report.service --no-pager
sudo journalctl -u disk-report.service -n 20 --no-pager
Для oneshot состояние inactive (dead) после успешного завершения нормально. Важны строка status=0/SUCCESS и таблица df в журнале. Ненулевой код означает, что сначала нужно исправить службу, а уже потом включать таймер.
Включите расписание и найдите следующий запуск
Команда enable --now добавит таймер в автозагрузку и сразу запустит сам таймер, но не обязательно службу: она дождётся рассчитанного времени:
sudo systemctl enable --now disk-report.timer
systemctl list-timers disk-report.timer --all
systemctl status disk-report.timer --no-pager
В list-timers проверьте столбцы NEXT и LAST. Если следующий запуск не соответствует ожиданиям, проверьте часовой пояс через timedatectl и календарное выражение через systemd-analyze calendar.
Не запускайте вторую копию поверх первой
Если резервное копирование длится дольше интервала, две копии могут одновременно менять одни файлы. Для программы без собственной блокировки оберните команду в flock. Следующий вариант немедленно завершит новый запуск, когда файл блокировки уже занят:
ExecStart=/usr/bin/flock -n /run/my-task.lock /usr/local/sbin/my-task
Замените исходную строку ExecStart в службе, выполните daemon-reload и снова запустите службу вручную. Убедитесь, что каталог /run доступен сервисному пользователю для создания файла; для строгой конфигурации удобнее задать RuntimeDirectory= и использовать каталог службы.
Как остановить и удалить расписание
Сначала отключите таймер. Эта команда отменит будущие запуски, но не прервёт уже работающую службу:
sudo systemctl disable --now disk-report.timer
systemctl list-timers disk-report.timer --all
Если задача больше не нужна, удалите оба созданных файла, перечитайте конфигурацию и сбросьте состояние ошибок:
sudo rm /etc/systemd/system/disk-report.service /etc/systemd/system/disk-report.timer
sudo systemctl daemon-reload
sudo systemctl reset-failed
Перед rm ещё раз проверьте точные имена. Для cron откройте crontab -e и удалите только соответствующую строку; команда crontab -r удаляет всю пользовательскую таблицу и для этой задачи не подходит.
Что проверить у настоящей периодической задачи
У расписания должен быть владелец, понятный журнал и признак успеха. Для резервной копии недостаточно кода возврата: проверьте, что появился новый файл, он читается и может быть восстановлен. Для очистки ограничьте рабочий каталог и не формируйте путь из непроверенных данных.
Раз в несколько недель смотрите время последнего запуска и ошибки. Связать таймер с наблюдением за сервером поможет руководство по мониторингу и резервным копиям, а устойчивую команду для планировщика — статья о shell-скриптах.
Первоисточники
- systemd.timer — параметры календарного запуска,
Persistentи случайная задержка. - systemd.service — описание запускаемой службы и
ExecStart. - crontab(5) — формат расписания и окружение cron.
- flock(1) — блокировка запуска второй копии команды.
Самопроверка
Проверьте, что материал усвоен
Ответьте на все вопросы. Результат сохранится только в этом браузере и будет учтён в статистике прочитанных материалов.
Разбираем коротко
Частые вопросы
Что выбрать для новой задачи: cron или systemd timer?
На сервере с systemd таймер обычно удобнее: у службы есть отдельные права, состояние и журнал, а пропущенный запуск можно выполнить после включения машины. Cron подходит для коротких простых команд и уже существующих заданий.
Почему команда работает в терминале, но не запускается по расписанию?
Планировщик получает более короткий PATH, другой рабочий каталог и меньше переменных окружения. Используйте абсолютные пути, задайте WorkingDirectory и EnvironmentFile явно, затем читайте журнал запуска.
Как не допустить одновременный запуск двух копий задачи?
Используйте блокировку через flock либо встроенный механизм самой программы. Если предыдущий запуск ещё работает, новая копия должна завершиться без изменения данных и оставить понятную запись в журнале.


