Фоновые задачи на проекте — это бэкапы, рассылки, очистка кэша, регенерация картинок, сбор метрик, обработчики очередей (queue workers) и многое другое. На виртуальном хостинге этим чаще управляют через cron и crontab, а на VDS — через связку systemd‑unit + systemd‑timer. Важно не просто «что-то запустить раз в час», а обеспечить надёжный график, логи и email alerts, чтобы поломки не оставались незамеченными. Про удалённые резервные копии см. отдельный разбор: S3‑бэкапы с restic/borg.
Cron на виртуальном хостинге: crontab, окружение и логи
На виртуальном хостинге cron обычно доступен пользователю через crontab -e
. В нём задаём расписание, переменные окружения и команды. Минимальный набор — явно определить SHELL
и PATH
, а также MAILTO
для уведомлений.
# /var/spool/cron/username (редактируется через crontab -e)
SHELL=/bin/bash
PATH=/usr/local/bin:/usr/bin:/bin
MAILTO=admin@site.tld
# Каждые 5 минут: запуск планировщика фреймворка
*/5 * * * * /usr/bin/php /home/username/site/artisan schedule:run >> /home/username/logs/schedule.log 2>&1
# Ночной бэкап с пониженным приоритетом
22 3 * * * ionice -c2 -n7 nice -n 10 /home/username/bin/backup.sh >> /home/username/logs/backup.log 2>&1
Ключевые моменты:
- Используйте абсолютные пути к бинарям и скриптам (никаких относительных
./script.sh
). - Задачи, которые пишут в stdout/stderr, отправятся на почту, если задан
MAILTO
. Если почта недоступна, перенаправляйте вывод в файл:>> file.log 2>&1
. - Если окружение в cron отличается от интерактивной сессии, добавьте
PATH
и нужныеexport
в начале crontab или внутри скрипта.
Анти-дубли: защита от параллельных запусков
Обычная проблема cron — наложение запусков, когда предыдущая задача не успела завершиться. Простой способ — flock
с неблокирующим режимом:
* * * * * flock -n /tmp/wp-cron.lock -c '/usr/bin/php /home/username/public_html/wp-cron.php' >> /home/username/logs/wp-cron.log 2>&1
Это гарантирует, что одновременно будет работать только один экземпляр.
Логирование на хостинге: файлы и ротация
Если у вас нет доступа к logrotate
, создайте каталог ~/logs
и пишите туда. Ротацию можно сделать отдельным cron‑заданием, оставляя, скажем, 14 дней логов:
15 4 * * * find /home/username/logs -type f -name '*.log' -mtime +14 -delete
Для разбора проблем используйте согласованный формат сообщений: добавляйте метки времени внутри скриптов, логируйте начало/конец, количество обработанных элементов и коды возврата.
Email alerts на cron
Варианты уведомлений:
MAILTO
— базовый способ: любой вывод скрипта уйдёт письмом.- Избирательная отправка при ошибке:
cmd || echo "cmd failed on $(hostname)" | mail -s 'Cron error' admin@site.tld
. - Явные коды возврата: убедитесь, что ваши скрипты завершаются с
exit 1
при ошибке, а не молчат.
Если локальная почта недоступна, логируйте в файл и читайте хвост логов при отладке. Продвинутый вариант — отправка в мониторинг из скрипта (CLI‑клиенты или API).

systemd timers на VDS: точность, контроль и наблюдаемость
На VDS systemd‑таймеры чаще всего комфортнее cron: выше точность расписания, встроенная устойчивость к рестартам (Persistent=true
), логирование через journald, OnFailure
‑хуки, перезапуски сервисов, ограничения ресурсов и sandboxing. Если под фоновые задачи нужен отдельный сервер — посмотрите гайд по выбору: как подобрать VDS по CPU/RAM.
Базовый шаблон: .service + .timer
Создаём сервис единичного запуска:
# /etc/systemd/system/site-backup.service
[Unit]
Description=Nightly site backup
[Service]
Type=oneshot
User=www-data
Group=www-data
WorkingDirectory=/var/www/example
Environment="PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin"
ExecStart=/usr/local/bin/backup.sh
SyslogIdentifier=site-backup
StandardOutput=append:/var/log/site-backup.log
StandardError=append:/var/log/site-backup.log
Таймер с ежедневным расписанием и защитой от пропусков:
# /etc/systemd/system/site-backup.timer
[Unit]
Description=Run site backup at 03:30 daily
[Timer] 03:30:00
Persistent=true
RandomizedDelaySec=5m
AccuracySec=1m
Unit=site-backup.service
[Install]
WantedBy=timers.target
Активация:
systemctl daemon-reload
systemctl enable --now site-backup.timer
systemctl list-timers --all
Логи с journald и в файлы
По умолчанию вывод сервиса идёт в journald. Для разбора:
journalctl -u site-backup.service --since today
journalctl -u site-backup.service -f
Если нужно — параллельно пишем в файл: StandardOutput=append:/var/log/site-backup.log
, StandardError=append:/var/log/site-backup.log
. Ротацию файлов оформите в /etc/logrotate.d/site-backup
.

Алерты отказов: OnFailure и перезапуски
Хук OnFailure
позволяет дернуть другой unit при неуспехе:
# В site-backup.service
[Unit]
Шаблон уведомителя:
# /etc/systemd/system/notify-admin@.service
[Unit]
Description=Notify admin about failure of %i
[Service]
Type=oneshot
User=root
ExecStart=/usr/bin/mail -s "Unit %i failed on %H" admin@site.tld
Для длительно работающих задач добавьте устойчивость и лимиты перезапусков:
[Unit]
StartLimitBurst=5
StartLimitIntervalSec=300
[Service]
Restart=on-failure
RestartSec=5s
Расписания OnCalendar и тонкая настройка
OnCalendar=Mon..Fri 09:00
— по будням в 9:00.OnCalendar=*:0/15
— каждые 15 минут.OnCalendar=monthly
— начало месяца.RandomizedDelaySec=
— размажьте старт, чтобы не ловить «пиковый час».Persistent=true
— если сервер был выключен в момент запуска, задача стартует при ближайшей возможности.
User timers: без root и изолированно
Можно запускать таймеры от конкретного пользователя, не трогая систему:
systemctl --user enable --now my-task.timer
systemctl --user list-timers
journalctl --user -u my-task.service -f
Unit‑файлы кладём в ~/.config/systemd/user/
. Удобно для проектных окружений и CI‑агентов.
Длинные процессы и queue workers: nohup, cron и systemd
Обработчики очередей (queue workers) должны работать постоянно и перезапускаться при сбоях. На VDS это проще всего делать через systemd‑сервис с Restart=always
и ограничениями ресурсов. На виртуальном хостинге иногда помогает nohup
как временная мера.
VDS: systemd‑сервис для очередей
# /etc/systemd/system/app-queue.service
[Unit]
Description=Application queue worker
After=network.target
[Service]
Type=simple
User=www-data
WorkingDirectory=/var/www/app
Environment="APP_ENV=prod"
ExecStart=/usr/bin/php artisan queue:work --sleep=3 --tries=3 --memory=256
Restart=always
RestartSec=5
# Лимиты и приоритеты
Nice=5
IOSchedulingClass=best-effort
IOSchedulingPriority=7
CPUQuota=50%
MemoryMax=512M
StandardOutput=append:/var/log/app-queue.log
StandardError=append:/var/log/app-queue.log
[Install]
WantedBy=multi-user.target
Далее: systemctl enable --now app-queue
, затем journalctl -u app-queue -f
. Такой подход надёжнее чем nohup
, поскольку systemd контролирует процесс, перезапускает и пишет логи.
Виртуальный хостинг: временный запуск через nohup
Если нет systemd, можно стартовать воркер фоном и «отвязать» от текущего TTY:
nohup /usr/bin/php /home/username/site/artisan queue:work --sleep=3 --tries=3 >> /home/username/logs/queue.log 2>&1 & disown
Чтобы воркер не копился в нескольких экземплярах, используйте flock
или pid‑lock внутри скрипта. Также добавьте «сторожок» в crontab, который проверяет жив ли процесс и поднимает его при падении.
wp-cron: перевод на реальный cron или systemd timers
Стандартный wp-cron
запускается посетителями сайта и может «спать» на низкой посещаемости. Лучше отключить псевдо‑cron и запускать планировщик по расписанию.
Отключение wp-cron в WordPress
// wp-config.php
define('DISABLE_WP_CRON', true);
Запуск из crontab
*/5 * * * * /usr/bin/php /home/username/public_html/wp-cron.php --doing-cron >> /home/username/logs/wp-cron.log 2>&1
Запуск через systemd timer
# /etc/systemd/system/wp-cron.service
[Unit]
Description=Run WordPress cron
[Service]
Type=oneshot
User=www-data
WorkingDirectory=/var/www/site
ExecStart=/usr/bin/php wp-cron.php --doing-cron
StandardOutput=append:/var/log/wp-cron.log
StandardError=append:/var/log/wp-cron.log
# /etc/systemd/system/wp-cron.timer
[Unit]
Description=Every 5 minutes WordPress cron
[Timer]
Persistent=true
Unit=wp-cron.service
[Install]
WantedBy=timers.target
Такой запуск стабилен при низкой посещаемости и даёт прозрачные логи.
Отладка, безопасность и «краевые случаи»
Надёжное расписание — это не только «когда запускать», но и «как ограничить вред от ошибок».
- Окружение. Задавайте
PATH
,LANG
, переменные приложения. Для сложных проектов храните их в .env и загружайте в начале скрипта. - Таймзона. Cron и systemd timers используют системную зону. Решите заранее — локальное время или UTC. При переходах на летнее/зимнее время используйте
OnCalendar
и проверяйте смещение. - Ресурсы. На VDS ограничивайте потребление:
CPUQuota
,MemoryMax
,IO*
. На виртуальном хостинге снижайте приоритет черезnice
/ionice
. - Файловые права. Скрипты выполняются от пользователя cron или systemd‑пользователя. Не пишите логи туда, где нет прав; проверьте владельца каталога логов.
- Коды возврата. Делайте явный
exit 1
при ошибке и логируйте причину, иначе мониторинг не поймёт, что случилось. - Тишина — враг. Даже при успехе полезно печатать короткую сводку: «processed=123 duration=4.2s» — это упростит и автоматический разбор, и ручную отладку.
- Готовность зависимостей. Для сервисов, которым нужен БД/сеть, укажите
After=network-online.target
или ретраи в скрипте. В cron — добавьте повтор с задержкой.
Чек‑лист перед запуском в прод
- Команда стабильно отрабатывает вручную и в «чистом» окружении.
- Есть защита от параллельных запусков:
flock
или внутренние замки. - Логи пишутся и ротируются; при ошибке есть email alerts.
- Для долгоживущих процессов — systemd с
Restart=
и ресурсными лимитами. - Расписание покрывает перерывы:
Persistent=true
для timers. - Проверены права доступа и таймзона.
Частые ошибки и как их избежать
- Относительные пути и «оно у меня в шелле работает». Лечится явными путями и
PATH
в unit/crontab. - Случайные дубли задач. Используйте замки или «единственный исполнитель».
- Молчаливые падения. Лечится кодами возврата, логами и email alerts.
- Неуправляемые воркеры. На VDS — только systemd, а не вечный
nohup
. - Гигантские лог‑файлы. Включите ротацию и срок хранения.
Итог
Для хостинга — crontab
с грамотным окружением, flock
, логи и почтовые уведомления. Для VDS — связка systemd timers + сервисные юниты: чёткие расписания, перезапуски, OnFailure
, journald и лимиты ресурсов. Отдельно держите под контролем очереди: воркеры должны быть перезапускаемы и наблюдаемы. Такой фундамент снимает массу проблем и делает фоновые задачи предсказуемыми.