Job & Cron Monitor

Job & Cron Monitor 3.1.2

No permission to download
Compatible XF 2.x versions
  1. 2.2
  2. 2.3
License
MIT
Command-line tools for watching, inspecting and diagnosing XenForo's job queue and cron tasks.

Run the queue by hand and see each job and cron task as it runs, with its execution time. Check from a script or a monitoring system that jobs and cron are actually running. See which jobs failed and why, how long each job and cron task takes, what is queued, and when each cron task is next due.

This add-on was called "CLI Job Runner" before version 3.0.0, and it is no longer a job runner. Cron runs XenForo's own xf:run-jobs; this add-on is for the person trying to find out what it is doing. If you are upgrading from 2.x, read Upgrading from 2.x below: your crontab keeps working, but it wants changing.

Features

  • job-monitor:validate checks that the job queue and cron are running, with an exit code a deploy script or monitoring system can read, and optional JSON output
  • job-monitor:run-jobs runs outstanding jobs and due cron tasks by hand, logging each one as it runs
  • job-monitor:show-jobs lists the job queue
  • job-monitor:show-failed-jobs lists jobs that failed, with the exception and the data each ran with (XenForo 2.3)
  • job-monitor:show-stats shows how often each job and cron task ran and how long it took, kept hour by hour for 90 days
  • job-monitor:show-crons lists cron tasks and when each is next due
  • job-monitor:run-cron runs one cron task now, as the scheduler would
  • job-monitor:queue-test-job queues a job that sends one test email
  • every failed job is recorded on XenForo 2.3, not only in debug mode
  • your own jobs and cron tasks can write to the same log

The add-on has no options and adds nothing to the admin control panel. It does not change which jobs run or when.

Requirements

  • XenForo 2.2 or later
  • PHP 7.1 or later
  • shell access to the server

Tested on XenForo 2.2 and 2.3.

Installation

Install as per normal add-on installation. Installing it changes nothing about how jobs run.

Running jobs from cron

This is XenForo's own setup. The add-on does not need it, but is most useful on a forum that runs it.

1. In the admin control panel, open the System and performance options, turn on Show advanced options, and set Job run trigger to Server based trigger.

1596700351453.png


2. Add a cron entry for the user your web server runs PHP as, running XenForo's job runner every minute. For example, in /etc/cron.d/xenforo on Ubuntu, with a web server user of www-data:

Bash:
* * * * * www-data php /path/to/your/forum/root/cmd.php --quiet xf:run-jobs

Under server-based triggering nothing runs from page views, so if the cron entry stops, so do XenForo's scheduled tasks. job-monitor:validate is how you find out.

Checking that jobs and cron are running

job-monitor:validate reports one line per check:

Bash:
$ php cmd.php job-monitor:validate
OK      Job trigger         Server based: jobs run only from cron, which should run xf:run-jobs every minute.
OK      Cron job queued     The cron job is in the queue.
OK      Overdue jobs        The oldest due job has waited 48 seconds.
OK      Overdue cron tasks  No cron task is more than an hour late.
OK      Admin warning       The admin control panel is not reporting stopped jobs.
OK      Manual jobs         No manually started job is stuck.
OK      Failed jobs         No job has failed in the last day.
OK      Crontab             hg:run-jobs has not been used recently.

What it checks:

  • Job trigger - a warning when the Job run trigger is activity based, so jobs wait for page views instead of running from cron
  • Cron job queued - a problem when XenForo's own cron job is missing from the queue, in which case no scheduled task runs until a cron entry is next saved
  • Overdue jobs - a warning when the oldest due job has waited more than 5 minutes, a problem after an hour
  • Overdue cron tasks - a problem when a cron task is more than an hour past its next run
  • Admin warning - a warning when the admin control panel is showing "There are scheduled jobs outstanding which have not run"
  • Manual jobs - a warning when a manually started job has stopped, such as a rebuild left unfinished in a browser
  • Failed jobs - a warning when any job has failed in the last day
  • Crontab - a warning when hg:run-jobs has run in the last two hours, so a crontab still needs changing

Each line starts OK, WARNING or PROBLEM. A check that did not apply starts with a blank instead: failed jobs on XenForo 2.2, which does not record them, and the cron job on a forum with no active cron tasks. It was not checked, so it is not a pass, and it never changes the exit code.

It changes nothing unless you pass --fix, which does the one repair it knows: queue a missing cron job again, as XenForo does when a cron entry is saved.

Exit codes

It exits 1 if any check found a problem and 0 otherwise, warnings included, so a deploy script or a cron job can depend on it. --strict makes a warning exit 2, for a monitor that should hear of one.

all finewarnings onlyany problem
job-monitor:validate001
job-monitor:validate --strict021

Bash:
$ php cmd.php job-monitor:validate || echo "jobs or cron are not running"

This is not the monitoring-plugin numbering, where 1 is a warning and 2 is critical: here 1 always means a problem. Map the values explicitly in whatever reads them.

--json prints the same result as one JSON document, for a monitoring system that reads more than the exit code: the overall status (ok, warning or problem), the exit_code, and each check's name, label, status and detail.

Running jobs by hand

job-monitor:run-jobs runs outstanding jobs and due cron tasks, and shows each as it starts and finishes with its execution time, without being asked:

Bash:
$ php cmd.php job-monitor:run-jobs
[2026-10-13 00:03:57] Hampel\JobRunner\Cli\Command\RunJobs: Run Jobs starting

[2026-10-13 00:03:57] Hampel\JobRunner\XF\Job\Manager: Starting Job [XF\Job\Cron]

[2026-10-13 00:03:57] Hampel\JobRunner\XF\Service\CronEntry\CalculateNextRun: Executing cron entry [forumStatistics]

[2026-10-13 00:03:57] Hampel\JobRunner\XF\Service\CronEntry\CalculateNextRun: Cron entry [forumStatistics] executed in 0.02 seconds

[2026-10-13 00:03:57] Hampel\JobRunner\XF\Job\Manager: Job [XF\Job\Cron] executed in 0.04 seconds

Total execution time: 0.05 seconds
All outstanding jobs have run.

It is safe to run while cron is running xf:run-jobs. XenForo claims each job before running it, so the two never run the same job, and there is no need to disable your cron entry first.

Options:

  • -t or --max-execution-time - how long to keep running jobs, in seconds. The default is 55; 0 runs until the queue is empty
  • --manual-only - run only manually triggered jobs, such as rebuilds started from the admin control panel
  • -v - adds the context of each job and cron task as JSON (job data, cron entry, next run time) and the queue loop itself
  • -vv - adds every internal step
  • -q - prints nothing at all

For example, with -v:

Bash:
$ php cmd.php job-monitor:run-jobs -v
[2026-10-13 00:03:57] Hampel\JobRunner\Cli\Command\RunJobs: Run Jobs starting {"maxRunTime":8,"maxExecutionTime":55,"manualOnly":false}

[2026-10-13 00:03:57] Hampel\JobRunner\Cli\Command\RunJobs: Remaining time: 55.00 seconds {}

[2026-10-13 00:03:57] Hampel\JobRunner\XF\Job\Manager: Running job queue {"maxRunTime":8}

[2026-10-13 00:03:57] Hampel\JobRunner\XF\Job\Manager: getRunnable: No runnable jobs found {}

Total execution time: 0.00 seconds
All outstanding jobs have run.

Failed jobs

On XenForo 2.3, a job that throws and cannot be retried is removed from the queue and, in debug mode only, recorded. With the add-on installed every failure is recorded, debug mode or not, so you can see what failed, when, with what data, and why:

Bash:
$ php cmd.php job-monitor:show-failed-jobs

2 failed jobs recorded, showing the newest 2

+----+----------------------+------------------------------------------------+------------------------------------------------------------------------+
| ID | Failed (UTC+11:00)   | Job                                            | Exception                                                              |
+----+----------------------+------------------------------------------------+------------------------------------------------------------------------+
| 23 | 21-Sep-2026 17:51:03 | Hampel\SparkPostMail\Job\FetchMessageEventsJob | Hampel\SparkPost\Exception\ClientException: SparkPost rejected GET ... |
| 22 | 21-Sep-2026 17:21:03 | Hampel\SparkPostMail\Job\FetchMessageEventsJob | Hampel\SparkPost\Exception\ClientException: SparkPost rejected GET ... |
+----+----------------------+------------------------------------------------+------------------------------------------------------------------------+

Use --id to see one in full. Records older than 30 days are pruned daily.

--id=23 shows one in full: the job class, when it failed, the data it ran with, the exception and its stack trace. --limit sets how many to list; the default is 20.

A daily cron task deletes records older than 30 days, including those XenForo wrote in debug mode, which it otherwise keeps forever.

XenForo 2.2 has no record of failed jobs: a job that throws is logged to the server error log and treated as complete.

Statistics

Every job and cron task that runs is timed, whichever command runs it - cron's xf:run-jobs included, and on page views under activity-based triggering - and kept hour by hour for 90 days.

Bash:
$ php cmd.php job-monitor:show-stats --limit=8

The last 24 hours, by total time

+------+--------------------------------------------+------+--------+-------+--------+--------+
| Type | Name                                       | Runs | Total  | Avg   | Max    | Failed |
+------+--------------------------------------------+------+--------+-------+--------+--------+
| job  | XF\Job\Cron                                | 1428 | 2.1m   | 89ms  | 50.05s |        |
| cron | cleanUpHourly                              | 24   | 51.32s | 2.14s | 50.04s |        |
| cron | logdigestSendLogs                          | 289  | 9.49s  | 33ms  | 333ms  |        |
| job  | XF\Job\PermissionRebuild                   | 54   | 4.73s  | 88ms  | 264ms  |        |
| job  | XF\Job\Sitemap                             | 1    | 2.68s  | 2.68s | 2.68s  |        |
| cron | forumStatistics                            | 144  | 2.56s  | 18ms  | 57ms   |        |
| job  | XF\Job\FileCheck                           | 46   | 2.45s  | 53ms  | 2.19s  |        |
| job  | XF\Job\SearchUserChange                    | 302  | 1.99s  | 7ms   | 31ms   |        |
+------+--------------------------------------------+------+--------+-------+--------+--------+

Use --name for one hour by hour. Statistics are kept for 90 days.

Options:

  • --hours - how far back to look. The default is 24
  • --type - only job or only cron
  • -s or --sort - time (total time, the default), runs, max or avg. --sort=max finds the slowest single runs
  • -l or --limit - how many rows. The default is 20
  • --name - one job class or cron entry id, hour by hour

Bash:
$ php cmd.php job-monitor:show-stats --name='XF\Job\Cron' --hours=3

job [XF\Job\Cron], the last 3 hours

+-------------------+------+-------+------+-------+--------+
| Hour (UTC+11:00)  | Runs | Total | Avg  | Max   | Failed |
+-------------------+------+-------+------+-------+--------+
| 13-Oct-2026 08:00 | 60   | 2.93s | 49ms | 152ms |        |
| 13-Oct-2026 09:00 | 59   | 3.64s | 62ms | 911ms |        |
| 13-Oct-2026 10:00 | 58   | 2.69s | 46ms | 166ms |        |
| 13-Oct-2026 11:00 | 6    | 304ms | 51ms | 148ms |        |
+-------------------+------+-------+------+-------+--------+

Failures are counted alongside runs on XenForo 2.3.

The job queue

job-monitor:show-jobs lists the pending jobs, so you can see how full the queue is. It shows the next 100; --all shows every one.

There should always be at least two: the main Cron job and the Upgrade Check job.

Bash:
$ php cmd.php job-monitor:show-jobs

2 pending jobs found

+----------------+---------------------+----------------------+----------------------+
| Key            | Class               | Next Run             | Last Run             |
+----------------+---------------------+----------------------+----------------------+
| cron           | XF\Job\Cron         | 13-Oct-2026 11:03:02 | 13-Oct-2026 11:03:01 |
| xfUpgradeCheck | XF\Job\UpgradeCheck | 13-Oct-2026 12:26:27 | 12-Oct-2026 10:02:03 |
+----------------+---------------------+----------------------+----------------------+

The current time is: 13-Oct-2026 11:03:50 (UTC+11:00)

Cron tasks

Show Crons

job-monitor:show-crons lists your active XenForo cron tasks and when each is next due:

Bash:
$ php cmd.php job-monitor:show-crons

19 active cron entries found

+---------------------------+----------------------+--------------------------+
| ID                        | Next Run (UTC+10:00) | Addon                    |
+---------------------------+----------------------+--------------------------+
| forumStatistics           | 29-Sep-2020 06:53    | XF                       |
| emailBounce               | 29-Sep-2020 06:53    | XF                       |
| warningExpiry             | 29-Sep-2020 06:55    | XF                       |
| rebuildSearchForumCache   | 29-Sep-2020 07:00    | XF                       |
| memberStatsCache          | 29-Sep-2020 07:00    | XF                       |
| feeder                    | 29-Sep-2020 07:02    | XF                       |
| cleanUpHourly             | 29-Sep-2020 07:10    | XF                       |
| emailUnsubscribe          | 29-Sep-2020 07:13    | XF                       |
| userGroupPromotions       | 29-Sep-2020 07:20    | XF                       |
| views                     | 29-Sep-2020 07:30    | XF                       |
| trophy                    | 29-Sep-2020 07:40    | XF                       |
| expireTempUserChanges     | 29-Sep-2020 07:42    | XF                       |
| deleteExpiredBans         | 29-Sep-2020 07:45    | XF                       |
| downgradeExpired          | 29-Sep-2020 07:50    | XF                       |
| fileCheck                 | 29-Sep-2020 10:10    | XF                       |
| dailyStats                | 29-Sep-2020 10:30    | XF                       |
| cleanUpDaily              | 29-Sep-2020 13:00    | XF                       |
| activitySummaryEmail      | 30-Sep-2020 00:20    | XF                       |
| sitemap                   | 30-Sep-2020 15:37    | XF                       |
+---------------------------+----------------------+--------------------------+

The current time is: 29-Sep-2020 06:52:05 (UTC+10:00)

Options:

  • -a or --all includes disabled cron tasks
  • -m or --method includes the class::method for each cron task
  • -s or --sort sets the sort column: date, id or addon

Use it to find the ID of the cron task you want to run.

Run Cron

job-monitor:run-cron runs one cron task now, exactly as the scheduler would - give it the cron ID.

For example: php cmd.php job-monitor:run-cron activitySummaryEmail runs the "Send activity summary email" cron task.

A disabled cron task is not run unless you pass -f or --force. The command exits 1 when the task is not found, has no callback, or throws, and an error it throws is reported and written to the server error log.

Testing the job queue

job-monitor:queue-test-job queues a job that sends one test email to the address you give, the next time the queue runs:

Bash:
$ php cmd.php job-monitor:queue-test-job you@example.com

If the email arrives, jobs are being queued, run and can send mail. It is a real email, sent through your forum's mail settings.

Logging from your own jobs and cron tasks

Your own code can write to the same log while job-monitor:run-jobs is running it. Nothing is printed under any other command, cron's xf:run-jobs included, and nothing breaks if this add-on is disabled or removed, so the logging can stay in your add-on when you deploy it.

In a job

Add this method to your job class:

PHP:
    protected function log($message, array $context = [])
    {
        // check to see if we actually have a logger available and abort if not
        if (!isset($this->app['cli.logger'])) return;

        /** @var \Hampel\JobRunner\Cli\Logger $logger */
        $logger = $this->app['cli.logger'];
        $logger->logJobProgress($this, $message, $context);
    }

Then call it from your job. These lines are debug output, so they show when you run job-monitor:run-jobs -vv. The test job included in this add-on, Hampel\JobRunner\Job\TestJob, is the example:

PHP:
    public function run($maxRunTime)
    {
        $this->log("About to start test job", $this->data);

        $mail = $this->app->mailer()->newMail();
        $mail->setTo($this->data['email']);
        $mail->setContent(
            "Test job",
            "This is an email sent from a test job"
        );
        $sent = $mail->send();

        $this->log("Sent mail", ['sent' => $sent]);

        return $this->complete();
    }

The important part is the isset($this->app['cli.logger']) check, which returns when the logger is not available.

Note: the argument order of logJobProgress() is the job first, then the message. Earlier versions of this page showed it the other way round.

In a cron task

Cron callbacks are static, so the method is slightly different:

PHP:
    protected static function log($message, array $context = [])
    {
        // check to see if we actually have a logger available and abort if not
        $app = \XF::app();
        if (!isset($app['cli.logger'])) return;

        /** @var \Hampel\JobRunner\Cli\Logger $logger */
        $logger = $app['cli.logger'];
        $logger->log(static::class, $message, $context);
    }

Then call something like self::log("some message about something happening", ['key' => 'value']); in your cron code.

Logger trait

There is also a trait, Hampel\JobRunner\Cli\LoggerTrait, for classes that are not jobs. It checks for the logger itself, and provides:

  • log, logQuiet, logNormal and logVerbose - shown by job-monitor:run-jobs with no verbosity option
  • logVeryVerbose - shown with -v or higher
  • debug - shown with -vv

Its methods are private and two of them are named log() and debug(), so a class that already has methods of those names cannot use it.

Upgrading from 2.x

Version 3.0.0 renamed the add-on from CLI Job Runner and changed what it is for: it no longer runs your cron jobs, XenForo does. Upgrade as per a normal add-on upgrade. Nothing stops working when you do.

Change your crontab from hg:run-jobs to xf:run-jobs. hg:run-jobs still works - it now runs xf:run-jobs with the same options - but it will be removed in a later major version. job-monitor:validate warns for as long as a crontab is still calling it.

The other commands have new names. Their old hg: names still work in 3.x.

wasnow
hg:run-jobs in cronxf:run-jobs
hg:run-jobs by handjob-monitor:run-jobs
hg:show-jobsjob-monitor:show-jobs
hg:show-cronsjob-monitor:show-crons
hg:run-cronjob-monitor:run-cron
hg:test-jobsjob-monitor:queue-test-job

What else changed:

  • There is no lock file any more, and no ten-minute run. XenForo claims each job before running it, so overlapping runs are safe without one. --reset is still accepted by hg:run-jobs and does nothing
  • The maximum execution time is XenForo's own: xf:run-jobs --max-execution-time
  • PHP 7.1 is now the minimum, up from 7.0
  • With the add-on installed, xf:run-jobs runs scheduled cron tasks. Versions before 3.0.0 held them back for hg:run-jobs
Related resources
Author
Sim
Downloads
170
Views
3,856
First release
Last update

Ratings

5.00 star(s) 2 ratings

More resources from Sim

Latest updates

  1. v3.1.2 - code cleanup, no new functionality, installation optional

    3.1.2 (2026-10-14) job-monitor:validate accepts --unattended. Other add-ons use the option to...
  2. v3.1.1 - major update

    Major update: renamed from CLI Job Runner to Job & Cron Monitor - this tool no longer runs your...
  3. v2.1.1 XF 2.3 compatibility update

    XF 2.3 compatibility

Latest reviews

A useful tool I've used to force batch image conversions via command line. It saved me a lot of time. Recommended.
Most people will only notice how urgently needed and useful this add-on is when they are surprised that some of the functions of XF do not seem to work. With this add-on you can change/fix that and generate additional information. Thanks for that, and that there is also a free add-on.
Back
Top Bottom