- Compatible XF 2.x versions
- 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
Features
The add-on has no options and adds nothing to the admin control panel. It does not change which jobs run or when.
Requirements
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.
2. Add a cron entry for the user your web server runs PHP as, running XenForo's job runner every minute. For example, in
Under server-based triggering nothing runs from page views, so if the cron entry stops, so do XenForo's scheduled tasks.
Checking that jobs and cron are running
What it checks:
Each line starts
It changes nothing unless you pass
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.
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.
Running jobs by hand
It is safe to run while cron is running
Options:
For example, with
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:
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
Options:
Failures are counted alongside runs on XenForo 2.3.
The job queue
There should always be at least two: the main Cron job and the Upgrade Check job.
Cron tasks
Show Crons
Options:
Use it to find the ID of the cron task you want to run.
Run Cron
For example:
A disabled cron task is not run unless you pass
Testing the job queue
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
In a job
Add this method to your job class:
Then call it from your job. These lines are debug output, so they show when you run
The important part is the
Note: the argument order of
In a cron task
Cron callbacks are static, so the method is slightly different:
Then call something like
Logger trait
There is also a trait,
Its methods are private and two of them are named
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
The other commands have new names. Their old
What else changed:
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:validatechecks that the job queue and cron are running, with an exit code a deploy script or monitoring system can read, and optional JSON outputjob-monitor:run-jobsruns outstanding jobs and due cron tasks by hand, logging each one as it runsjob-monitor:show-jobslists the job queuejob-monitor:show-failed-jobslists jobs that failed, with the exception and the data each ran with (XenForo 2.3)job-monitor:show-statsshows how often each job and cron task ran and how long it took, kept hour by hour for 90 daysjob-monitor:show-cronslists cron tasks and when each is next duejob-monitor:run-cronruns one cron task now, as the scheduler wouldjob-monitor:queue-test-jobqueues 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.
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
cronjob 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-jobshas 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 fine | warnings only | any problem | |
|---|---|---|---|
job-monitor:validate | 0 | 0 | 1 |
job-monitor:validate --strict | 0 | 2 | 1 |
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:
-tor--max-execution-time- how long to keep running jobs, in seconds. The default is 55;0runs 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- onlyjobor onlycron-sor--sort-time(total time, the default),runs,maxoravg.--sort=maxfinds the slowest single runs-lor--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:
-aor--allincludes disabled cron tasks-mor--methodincludes theclass::methodfor each cron task-sor--sortsets the sort column:date,idoraddon
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,logNormalandlogVerbose- shown byjob-monitor:run-jobswith no verbosity optionlogVeryVerbose- shown with-vor higherdebug- 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.| was | now |
|---|---|
hg:run-jobs in cron | xf:run-jobs |
hg:run-jobs by hand | job-monitor:run-jobs |
hg:show-jobs | job-monitor:show-jobs |
hg:show-crons | job-monitor:show-crons |
hg:run-cron | job-monitor:run-cron |
hg:test-jobs | job-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.
--resetis still accepted byhg:run-jobsand 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-jobsruns scheduled cron tasks. Versions before 3.0.0 held them back forhg:run-jobs
- Related resources
- Version compatible with XF2.1: https://xenforo.com/community/resources/cli-job-runner.6478/