Cron — это встроенный в Linux планировщик: он раз в минуту просыпается, сверяет текущее время со списком заданий и запускает те, чьё расписание совпало. Настройка сводится к одной строке из пяти полей времени и команды, которую нужно выполнить, а работает такая строка одинаково и на домашнем компьютере, и на арендованном VPS с Linux. Никакого отдельного демона (так называют фоновую программу, которая работает без вашего участия) ставить не нужно: пакет cron уже стоит в Ubuntu, Debian, CentOS и Alma Linux.
Материал для тех, кто первый раз открывает crontab и хочет, чтобы бэкап реально делался каждую ночь, а не «делался» только в голове. Разберём формат расписания, шаблоны на каждые 15 минут и на воскресенье, отличие пользовательского задания от системного, три классические грабли (окружение, потерянный вывод, наложение запусков) и два готовых примера — ночной архив сайта с базой и автопродление сертификата. В конце — способы проверить, что задание действительно отработало.
Как устроен cron и чем он отличается от systemd-таймеров
Демон cron стартует вместе с системой и держит в памяти таблицу заданий. Каждую минуту он сравнивает системное время с расписанием каждой строки и, если совпадение есть, запускает команду в отдельной оболочке от имени владельца задания. Минимальный интервал в классическом cron — 1 минута: запустить что-то раз в 10 секунд напрямую не получится.
Альтернатива — systemd-таймеры: расписание описывается двумя файлами (unit и timer), зато есть точность до секунды и опция запуска пропущенных заданий после перезагрузки. Cron проигрывает в гибкости, но выигрывает в скорости настройки: одна строка вместо двух файлов. Для 90% бытовых задач — бэкап, чистка временных файлов, обновление сертификата — этого достаточно.
Ещё одна особенность: cron не «догоняет» пропущенное. Если сервер был выключен в 3:00, ночное задание не выполнится, и следующая попытка будет через 24 часа. Для машин, которые регулярно уходят в перезагрузку, существует anacron — отдельный планировщик, который ориентируется на факт «прошли сутки с последнего запуска», а не на время на часах.
Пять полей расписания: как читать строку crontab
Строка задания состоит из пяти полей времени и команды. Порядок полей всегда один: минута, час, день месяца, месяц, день недели. Диапазоны такие: минуты 0–59, часы 0–23, дни месяца 1–31, месяцы 1–12, дни недели 0–7, где и 0, и 7 означают воскресенье.
Звёздочка в поле означает «любое значение». Строка * * * * * — это «каждую минуту каждого часа каждого дня». Косая черта задаёт шаг: */15 в поле минут — это 0, 15, 30 и 45, то есть четыре запуска в час. Запятая перечисляет значения: 0,30 — ровно в начале часа и на 30-й минуте. Дефис задаёт диапазон: 9-18 в поле часов — это десять запусков с 9 до 18 включительно.
Важная тонкость: если заполнены одновременно поле «день месяца» и поле «день недели», cron считает их через ИЛИ, а не через И. Строка 0 5 13 * 5 сработает и 13-го числа любого месяца, и каждую пятницу. Чтобы избежать путаницы, в одном из этих двух полей почти всегда ставят звёздочку.
Готовые шаблоны расписаний на каждый день
Проще всего начать с готовой строки и подправить цифры под себя. В таблице ниже — расписания, которые закрывают большинство задач на сервере, с указанием, сколько запусков в сутки они дают. Последняя колонка помогает прикинуть нагрузку: задание раз в минуту за сутки стартует 1440 раз, и если каждый запуск читает базу, это уже заметно.
Кроме числовой записи cron понимает сокращения-псевдонимы: @daily (равносильно 0 0 * * *), @hourly (0 * * * *), @weekly, @monthly и @reboot — последний запускает команду один раз после старта системы. Псевдонимы читаются легче, но не позволяют выбрать конкретный час, поэтому в реальных заданиях чаще пишут цифрами.
| Строка | Что означает | Запусков в сутки |
|---|---|---|
| 0 3 * * * | Каждый день в 3:00 | 1 |
| */15 * * * * | Каждые 15 минут | 96 |
| 0 4 * * 0 | По воскресеньям в 4:00 | 1 раз в неделю |
| 30 2 1 * * | 1-го числа каждого месяца в 2:30 | 1 раз в месяц |
| 0 9-18 * * 1-5 | Ежечасно с 9 до 18 в будни | 10 в рабочий день |
| */5 * * * * | Каждые 5 минут | 288 |
| * * * * * | Каждую минуту | 1440 |

Где лежат задания: пользовательский crontab и системные файлы
Свой список заданий каждый пользователь редактирует командой crontab -e — она открывает временный файл в редакторе по умолчанию и после сохранения проверяет синтаксис. Посмотреть текущее содержимое без редактора — crontab -l. Удалить всё разом — crontab -r. Это единственная из трёх команд, которая ничего не переспрашивает: список исчезнет сразу и без подтверждения. Поэтому перед ней полезно сохранить копию: crontab -l > ~/cron-backup.txt запишет текущие задания в файл в домашнем каталоге.
Задания разных пользователей не пересекаются. То, что вы завели под обычным логином, выполнится от его имени и не получит прав на системные каталоги. Чтобы задание работало от суперпользователя, его заводят через sudo crontab -e. Подробнее про менеджер crontab и его роль в панелях управления — в глоссарии.
Второй способ — системные файлы в каталоге /etc/cron.d. Формат там почти тот же, но между расписанием и командой добавляется шестое поле — имя пользователя, от которого запускать. Такой файл удобно класть вместе с приложением: он лежит в системе как обычный конфиг, попадает в резервную копию сервера и виден любому администратору. Ещё есть каталоги /etc/cron.daily и /etc/cron.weekly — туда достаточно положить исполняемый скрипт без всякого расписания: система сама запустит его раз в сутки или раз в неделю.
# Пользовательское задание: пять полей + команда
0 3 * * * /usr/local/bin/backup.sh
# Системный файл /etc/cron.d/backup: шестое поле (root) — имя пользователя
0 3 * * * root /usr/local/bin/backup.sh
Главная грабля: короткий PATH и чужой рабочий каталог
Девять из десяти историй «в терминале работает, из cron — нет» упираются в окружение. Когда вы заходите по SSH, оболочка читает профиль пользователя и подставляет переменную PATH — список каталогов, где искать программы. Cron профиль не читает: у него свой короткий PATH, обычно /usr/bin:/bin. Поэтому команда вроде docker, certbot или python3.11 из /usr/local/bin просто не находится, и задание падает с «command not found».
Второе отличие — рабочий каталог. Cron стартует команду из домашнего каталога владельца задания, а не из папки проекта. Любой относительный путь вида ./script.sh или backup/db.sql будет считаться относительно домашнего каталога и укажет не туда, куда вы думали.
Лечится это тремя приёмами. Первый: писать в задании только абсолютные пути и к программе, и к файлам. Второй: задать PATH прямо в верхней строке crontab — переменные, объявленные до заданий, действуют на все строки ниже. Третий: если скрипту нужен конкретный каталог, явно перейти в него через cd в самой команде.
# Переменные объявляем до заданий — они действуют на все строки ниже
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
SHELL=/bin/bash
# Абсолютные пути и явный переход в каталог проекта
15 2 * * * cd /var/www/site && /usr/bin/php artisan schedule:run
Куда пропадает вывод задания и как завести нормальный лог
По умолчанию всё, что команда напечатала в стандартный вывод или в поток ошибок, cron отправляет письмом локальному пользователю. Если на сервере не настроен почтовый агент (программа, которая эти письма отправляет), а на свежем VPS его обычно нет, письмо никуда не уходит и вывод теряется. Внешне это выглядит так: задание «молча не работает», и понять причину невозможно.
Решение — перенаправить оба потока в файл. Конструкция >> /var/log/job.log дописывает стандартный вывод в конец файла, а 2>&1 добавляет туда же поток ошибок. Порядок важен: если поставить 2>&1 перед >>, ошибки уйдут не в файл, а туда, куда вывод шёл раньше, — то есть снова потеряются. Одна стрелка > вместо двух перезапишет файл при каждом запуске, и вы увидите только последний прогон.
Чтобы лог не рос бесконечно, к нему подключают ротацию через logrotate: конфиг в /etc/logrotate.d/ с хранением 14 архивов и сжатием держит размер в пределах нескольких мегабайт. Полезная привычка — печатать в начале каждого запуска дату, тогда по логу видно не только ошибки, но и сам факт старта.
# Весь вывод и все ошибки — в один файл, дописыванием
0 3 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1
# С отметкой времени в начале строки
0 3 * * * date "+%F %T старт" >> /var/log/backup.log; /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1
Блокировка одновременных запусков через flock
Задание раз в 5 минут обычно отрабатывает за секунды. Но однажды база вырастет, диск затормозит, и прошлый запуск не успеет закончиться до следующего. Cron это не отслеживает: он честно запустит вторую копию, потом третью, и через час на сервере будет десяток одновременных процессов, которые дерутся за диск. Load average — средняя очередь задач к процессору и диску — уходит в потолок, и сайт начинает отдавать ошибку 502.
Штатное лекарство — утилита flock из пакета util-linux, она есть почти в любом дистрибутиве. Команда берёт «замок» на указанный файл и держит его до завершения процесса. Флаг -n означает «не ждать»: если замок уже занят предыдущим запуском, новая копия молча завершится с кодом 1 и не наделает дублей.
Есть и вариант с ожиданием: -w 60 заставит новый процесс подождать освобождения замка до 60 секунд. Такой режим уместен для коротких задач. Для тяжёлых бэкапов лучше жёсткий -n: пропустить один запуск дешевле, чем получить два параллельных архива одной базы.
# -n: если предыдущий запуск ещё идёт, просто выходим
*/5 * * * * /usr/bin/flock -n /tmp/sync.lock /usr/local/bin/sync.sh >> /var/log/sync.log 2>&1
# -w 60: подождать освобождения замка не больше 60 секунд
*/5 * * * * /usr/bin/flock -w 60 /tmp/sync.lock /usr/local/bin/sync.sh

Пример первый: ночной архив сайта и базы
Соберём практичное задание: каждую ночь упаковать каталог сайта и дамп базы MySQL в один архив, а копии старше 14 дней удалить. Сначала пишем скрипт, потом ставим его в расписание — так проще отлаживать и не придётся экранировать знаки процента, которые cron трактует как перевод строки.
- Создайте каталог для копий:
sudo mkdir -p /var/backups/siteи убедитесь, что на диске свободно хотя бы вдвое больше, чем весит проект. - Положите пароль базы в файл
/root/.my.cnfс правами 600 (читать и писать может только владелец файла), чтобы пароль не светился в списке процессов при каждом запуске. - Создайте файл
/usr/local/bin/backup.shс содержимым из блока ниже и сделайте его исполняемым:sudo chmod +x /usr/local/bin/backup.sh. - Запустите скрипт руками —
sudo /usr/local/bin/backup.sh— и проверьте, что архив появился и открывается. - Добавьте строку в системный crontab через
sudo crontab -eи сохраните файл. - На следующее утро посмотрите лог:
tail -n 30 /var/log/backup.log.
#!/bin/bash
set -e
DATE=$(date +%F)
tar -czf /var/backups/site/files-$DATE.tar.gz /var/www/site
mysqldump --single-transaction mydb | gzip > /var/backups/site/db-$DATE.sql.gz
find /var/backups/site -type f -mtime +14 -delete
# Каждый день в 3:10, с замком и логом
10 3 * * * /usr/bin/flock -n /tmp/backup.lock /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1
Строка set -e останавливает скрипт на первой же ошибке, иначе find удалит старые копии даже после неудачного архивирования. Ключ --single-transaction снимает дамп без блокировки таблиц, так что сайт продолжает работать. Локальные копии — только половина дела: их стоит отправлять на второй сервер или в объектное хранилище, для чего удобен rsync. Подробный разбор схем хранения — в отдельном гайде про настройку бэкапов на VPS, а базовая теория — в статье про резервное копирование.
Пример второй: автопродление TLS-сертификата
Сертификаты Let’s Encrypt живут 90 дней, продлевать их принято начиная с 30-го дня до окончания. Делать это руками четыре раза в год — верный способ однажды забыть и получить страницу с предупреждением браузера. Команда Certbot renew сама проверяет срок и обновляет только те сертификаты, которым осталось меньше 30 дней, поэтому её безопасно запускать хоть каждый день.
Хорошая практика — ставить два запуска в сутки в непохожие минуты, например в 3:27 и 15:27. Так продление не совпадёт с пиком обращений к серверам центра сертификации, а если первая попытка упрётся в сетевой сбой, вторая её подхватит. Флаг --quiet убирает болтовню в лог, оставляя только ошибки, а --deploy-hook перезагружает веб-сервер строго после успешного обновления.
# Две попытки в сутки, перезапуск nginx только при реальном обновлении
27 3,15 * * * /usr/bin/certbot renew --quiet --deploy-hook "systemctl reload nginx" >> /var/log/certbot.log 2>&1
Проверить логику до боевого запуска помогает режим certbot renew --dry-run: он проходит весь сценарий на тестовом сервере и ничего не меняет. Если сертификат выпускался вручную и нужен разбор с нуля, пригодится пошаговая инструкция по установке SSL от Let’s Encrypt.
Как проверить, что задание действительно отработало
Диагностику удобно вести слоями: сначала выясняем, дошло ли дело до запуска, потом — что случилось с самой командой. Первый слой — журнал системы. Команда journalctl -u cron --since today покажет строки вида «CMD (…)» с точным временем старта каждого задания. На CentOS и Alma юнит называется crond, а в старых системах записи лежат в /var/log/syslog.
Если запись о старте есть, а результата нет — проблема внутри команды, и ответ ищем в собственном логе задания. Если записи нет вовсе, проверяем три вещи: жив ли сам демон (systemctl status cron), сохранилась ли строка (crontab -l) и верен ли часовой пояс (timedatectl). Классика: сервер стоит в UTC, задание на 3:00 отрабатывает в 6:00 по Москве.
| Что проверяем | Команда | Плохой ответ |
|---|---|---|
| Демон запущен | systemctl status cron | inactive (dead) |
| Задание сохранено | crontab -l | Пустой вывод |
| Факт запуска | journalctl -u cron —since today | Нет строки CMD |
| Часовой пояс | timedatectl | Time zone: Etc/UTC |
| Права на скрипт | ls -l /usr/local/bin/backup.sh | Нет флага x |
| Свободное место | df -h /var | Use% выше 95% |
Ещё один типичный сценарий — задание стартовало, но было убито системой из-за нехватки памяти. Тогда в dmesg найдётся строка от OOM-killer с именем процесса. Разобраться, кто именно съел память и процессор, помогает разбор команд из материала о том, как смотреть процессы в Linux.
На каком сервере держать задачи по расписанию
Требования у cron скромные: сам демон занимает единицы мегабайт памяти. Реальные аппетиты определяет команда, которую вы запускаете. Архивирование каталога на 5 ГБ спокойно проходит на 1 ГБ оперативной памяти, а вот mysqldump большой базы или сборка проекта потребуют запаса — и по памяти, и по свободному месту под временные файлы.
Ориентир по ресурсам простой: диска нужно минимум вдвое больше, чем весит то, что вы архивируете, плюс место под 7–14 старых копий. Если бэкапы уезжают на внешнее хранилище в ту же ночь, хватит и полуторного запаса. Ниже — стартовые конфигурации площадок каталога, на которых такие задачи держат чаще всего.
| Провайдер | Стартовая цена | Что входит |
|---|---|---|
| Cloudcore | от 59 ₽/мес | 1 ядро, 1 ГБ RAM, 10 ГБ NVMe |
| Hosting-Russia | от 99 ₽/мес | Стартовый тариф под мелкие задачи |
| 4VPS | от 110 ₽/мес | 1 ядро, 1 ГБ RAM, 5 ГБ NVMe |
| HostVDS | от 127 ₽/мес | 1 ядро, 1 ГБ RAM, 10 ГБ NVMe |
Данные проверены 04.09.2026.
Всего в каталоге 44 площадки, и разброс стартовых цен — от 59 до 593 ₽/мес. Для машины, которая только гоняет скрипты по расписанию, переплачивать смысла нет: подойдёт любой тариф из подборки недорогих VPS. Единственное, на что стоит смотреть, — объём диска под архивы и наличие снапшотов.
Частые ошибки
Ошибки в cron почти всегда однотипные, и большинство из них видно ещё до первого ночного запуска. Ниже — те, что чаще всего доводят до вопроса «почему задание не сработало». Проверьте свою строку по списку сразу после сохранения.
- Относительные пути вместо абсолютных — cron стартует из домашнего каталога, а не из папки проекта.
- Неэкранированный знак процента: в crontab он означает перевод строки, поэтому в командах с
dateего пишут как%. - Файл crontab без пустой строки в конце — часть систем молча игнорирует последнее задание.
- Скрипт без флага исполнения или без строки
#!/bin/bashв первой строке файла. - Задание раз в минуту без
flock— рано или поздно запуски наложатся друг на друга. - Часовой пояс сервера в UTC, а расписание составлено по московскому времени.
Частые вопросы
Как запустить задание чаще, чем раз в минуту? Штатными средствами cron — никак, его минимальный шаг составляет 1 минуту. Обходной путь — одна строка раз в минуту, внутри которой скрипт делает нужное действие несколько раз с паузой sleep 20. Для честных секундных интервалов лучше взять systemd-таймер с параметром OnUnitActiveSec — он задаёт паузу между запусками в секундах.
Почему команда работает в терминале, но не работает из cron? В 90% случаев виноват PATH: cron даёт короткий список каталогов и не находит программу из /usr/local/bin. Пропишите полный путь к бинарнику или объявите PATH в первой строке crontab. Второй по частоте виновник — относительные пути к файлам.
Куда уходит вывод, если не настроить перенаправление? Cron пытается отправить его письмом на локальный ящик владельца задания. Без настроенного почтового агента письмо теряется, и вы не увидите ни результата, ни ошибки. Всегда добавляйте >> /var/log/имя.log 2>&1.
Что будет, если сервер был выключен во время запуска? Задание просто пропустится, cron не выполняет пропущенное после включения. Если сервер регулярно перезагружается, используйте anacron — он отсчитывает интервал от последнего фактического запуска, а не от времени на часах.
Как временно отключить задание, не удаляя его? Поставьте символ # в начале строки через crontab -e — строка станет комментарием и перестанет выполняться. Это удобнее, чем удалять: сохраняется и расписание, и точная команда со всеми ключами.
Нужно ли перезапускать cron после правки расписания? Нет, если вы редактировали через crontab -e — демон подхватывает изменения сам. Перезагрузка через systemctl reload cron нужна только после ручной правки файлов в /etc/cron.d на некоторых системах.
Можно ли ставить задачи по расписанию на обычном виртуальном хостинге? Обычно да, но с ограничениями: панель часто разрешает минимальный интервал 5 или 15 минут и запрещает произвольные системные команды. Полная свобода в расписании и в наборе утилит появляется только на VPS с root-доступом.
Итог
Настройка cron сводится к трём правилам: абсолютные пути в команде, перенаправление вывода в свой лог-файл и flock -n на всём, что запускается чаще раза в час. Сохраните расписание через crontab -e, проверьте факт запуска в journalctl -u cron, и через сутки у вас будет и рабочий бэкап, и понятный лог. Всё это требует root-доступа, который даёт VPS на Linux — стартовые тарифы в каталоге начинаются от 59 ₽/мес.