Scheduled Tasks

jekcms runs background work through a lightweight task scheduler stored in the scheduled_tasks DB table. Each row is a named job with an interval, a PHP callable, and a last_run_at timestamp. A single entry point — cron.php — walks the table every minute and fires anything that's due.

This page covers how it works, what runs by default, how to add custom tasks, and how to set up cron on your host.

How jekcms runs scheduled tasks

The preferred path is an OS-level cron job that hits cron.php once a minute. On every tick the scheduler:

  1. Acquires a lock file at storage/locks/cron.lock (prevents overlapping runs)
  2. Selects all tasks where NOW() >= last_run_at + interval_seconds
  3. Runs each callable, captures stdout and exceptions, writes a row to scheduled_task_runs
  4. Updates last_run_at and last_status on the parent task

On shared hosts without real cron, jekcms falls back to a sparse web-request tick: roughly 2% of front-end page requests invoke a tiny shim that runs one due task and returns. Low-traffic sites limp along this way, but the recommendation is always: install real cron if you can.

Built-in tasks

Every install ships with these rows already populated:

  • post.scheduled_publish — every 1 min. Promotes posts whose publish_at is in the past from scheduled to published, fires the post.published webhook
  • ai.bulk_worker — every 1 min. Pulls the next pending item off the AI bulk generation queue and runs it (respecting per-batch rate limit)
  • license.heartbeat — every 6 h. Pings the jekcms license server, rotates the local license cache token
  • updates.check — every 6 h. Calls the update manifest endpoint, populates the Update available banner
  • backup.rotate — daily at 03:00. Keeps the five newest pre-update backups, deletes older ones
  • sitemap.refresh — every 30 min. Rebuilds sitemap.xml + per-type shards if posts.updated_at changed since last build

You cannot delete built-in tasks, but you can disable any of them from the admin UI at admin/scheduled-tasks.php.

Creating a custom task

Go to Automation → Scheduled Tasks → New task. Fill in:

  • Name: unique slug, e.g., myplugin.daily_digest
  • Interval: seconds between runs (e.g., 3600 for hourly)
  • Callable: any PHP callable reachable from the bootstrap scope, e.g., MyPlugin\Jobs\DailyDigest::run
  • Enabled: on/off

The callable receives one argument — the task row — and should return a short string used as last_status (e.g., "ok: 42 items").

namespace MyPlugin\Jobs;

class DailyDigest {
    public static function run(array $task): string {
        $rows = DB::query('SELECT id FROM posts WHERE created_at > NOW() - INTERVAL 1 DAY')->all();
        Mailer::sendDigest($rows);
        return 'ok: ' . count($rows) . ' posts';
    }
}

Viewing execution history

Each task row has a History button that opens the last 100 runs with start time, duration, status, and captured stdout/exception. Failed runs are highlighted red. Use this to debug "my task isn't firing" — first thing to check is whether it ran at all.

Failure retry

If a callable throws, the scheduler records the exception and schedules a retry with exponential backoff: 1 min, 5 min, 15 min, 1 h. After four consecutive failures the task is marked degraded and paused — you'll see an alert in the admin dashboard. Re-enable manually once you've fixed the underlying cause.

Setting up cron

cPanel

Advanced → Cron Jobs → Add New Cron Job:

  • Schedule: /5 (every 5 min) or (every 1 min)
  • Command:
*/5 * * * * /usr/bin/php /path/to/cron.php >/dev/null 2>&1

Replace /path/to/ with your install root. Some cPanel hosts require the full PHP binary path — which php on the shell, or try /usr/local/bin/php if /usr/bin/php isn't found.

Plain Linux (crontab)

crontab -e

Add:

* * * * * cd /var/www/jekcms && /usr/bin/php cron.php >> storage/logs/cron.log 2>&1

Windows Task Scheduler

Create a basic task → trigger daily, repeat every 1 minute, duration 24 hours → action: start C:\xampp\php\php.exe with argument C:\xampp\htdocs\jekcms\cron.php.

Verifying cron is actually running

Open any scheduled task in the admin UI and look at last_run_at. If it's within the expected interval, cron is alive. If it's stuck or empty, check:

  • Is the cron job installed? crontab -l
  • Is the PHP binary path correct?
  • Any error output? Redirect to a log file (drop the >/dev/null) and read it
  • File permissions on storage/locks/ — the web user needs write access

Be the first to know

New features, release notes & CMS guides — a couple of emails a month, no spam.