Scheduled Tasks - edmozley/freeitsm GitHub Wiki

Scheduled tasks (cron jobs)

Some things in FreeITSM cannot happen while somebody is looking at a screen. An SLA breaches at 3am. A contract expires in 30 days. A developer comments on a Jira issue and nobody is watching. For those, FreeITSM needs a small job that wakes up on a timer.

On a server you run yourself, you set up one scheduled task, once. It runs every minute and starts each job when it is due. Jobs added by later releases are picked up with nothing to change. The Docker image needs nothing at all - it runs them itself. Without a scheduled task the features that depend on these jobs fail silently: nothing errors, nothing appears in a log, the feature simply never happens.

โš ๏ธ Silent is the important word. A tracker whose status never changes looks exactly like a tracker where nothing has happened. An SLA that never breaches looks like good service. If a time-based feature "doesn't seem to work", check this page first.

โœ… And now check Background jobs. Reporting โ†’ System logs โ†’ Background jobs says whether the scheduler is running, shows every job below with its schedule, whether this install needs it and whether it is overdue, failing or stuck - and Watchtower shows a card while any of them needs attention. That page tells you whether a job is running and when it runs; this one tells you how to make it run.


One scheduled task for everything

The built-in scheduler (from 4.8.0, proposed by vsc55 on #233) is one script, cron/run.php, run every minute. Each time, it starts the jobs that are due, each on its own, and returns in well under a second. How often each job runs is set in FreeITSM, not in the scheduled task: Reporting โ†’ System logs โ†’ Background jobs โ†’ Edit - every so many minutes, once a day at a set time, or off.

Windows - in a Command Prompt opened as Administrator, adjusting the two paths:

schtasks /create /tn "FreeITSM scheduler" /tr "C:\wamp64\bin\php\php8.4.0\php.exe C:\wamp64\www\freeitsm-app\cron\run.php --quiet" /sc minute /mo 1 /ru SYSTEM

Linux - in the web server user's crontab (sudo crontab -u www-data -e):

* * * * * /usr/bin/php /var/www/freeitsm-app/cron/run.php --quiet

Use the same PHP the web server uses - WAMP keeps several under bin\php\, and an older one cannot run FreeITSM. The Background jobs tab shows the exact line for your server when no scheduler is running yet.

Docker - nothing to do. The container runs the scheduler itself. Set TZ in docker-compose.yml (for example TZ=Europe/London), or a job set to run daily at 06:00 runs at 06:00 UTC; the tab warns when it is not set. See Docker below.

  • Daily times are in the install's timezone - the one config.php sets, or TZ on Docker - because a job has nobody signed in, so nobody's own timezone applies. The tab shows the zone next to the time.
  • Already have one task per job? See the next section - nothing breaks, and there is no rush.
  • A job that is not needed is not started - one for a module that is switched off, or a feature nobody has set up. It starts the minute it is needed.
  • php cron/run.php --dry-run lists what is due and starts nothing.

Already have one task per job?

If you set FreeITSM up before 4.8.0, you probably have a scheduled task for each job. You do not have to remove them, and nothing breaks whatever you do:

  • Do nothing, and nothing changes. Your tasks keep running the jobs exactly as before. The built-in scheduler only runs if you add it.
  • Add cron/run.php and keep the old tasks, and that is safe too. A job is never run twice at once: when one start finds the job already running, it stands down, and the run history shows it as Skipped. Skipped runs are cleared away after 30 days.

The one catch: your schedules in FreeITSM only take full control once the old tasks are gone. An old task keeps starting its job on its own timing, whatever Background jobs says. Set the people import to once a day at 06:00 while an old task still runs it every two hours, and it still runs every two hours. A schedule in FreeITSM can make a job run more often than its old task does, but never less often.

To move over:

  1. Add the one scheduled task for cron/run.php (above).
  2. Open Reporting โ†’ System logs โ†’ Background jobs and check the box at the top is green - The built-in scheduler is running. Give it a couple of minutes.
  3. Remove the old per-job tasks, whenever suits you:
    • Windows: open Task Scheduler as administrator (tasks set to run as SYSTEM are hidden otherwise), and delete the ones named FreeITSM ... - but not the new FreeITSM scheduler. Or from an administrator Command Prompt, one at a time: schtasks /delete /tn "FreeITSM SLA Breach Check" /f.
    • Linux: sudo crontab -u www-data -e (or the user that holds them), and delete every line naming a script in cron/ or scripts/ - except the cron/run.php line.
    • Docker: delete the docker compose exec ... cron/... lines from the host's crontab. From 4.8.0 the container runs the scheduler itself, so you need nothing on the host at all.
  4. Back on Background jobs, set any job whose old task ran at a particular time - a nightly people import, say - to once a day at a set time.

A monitoring tool or remote scheduler calling a job's web address (?token=) works the same way: safe to leave alongside, and it keeps that job running on its own timing until you stop it.

No scheduled task at all?

Some hosts allow no scheduled tasks. For those, Background jobs has a switch, off by default: Start due jobs while somebody has FreeITSM open. Each open analyst page checks once a minute and starts the scheduler if nothing else has. It is a fallback, not a schedule:

  • nothing runs while nobody has FreeITSM open - a job due at 06:00 runs at the first page load after it;
  • PHP must be allowed to start a process, as the Intune sync needs (if FreeITSM cannot find PHP, set it under Assets โ†’ Settings โ†’ Intune);
  • its runs are recorded as page runs, so Background jobs still says no scheduled task runs these jobs - which is true, and worth fixing when you can.

The thirteen jobs

Job What it does What breaks without it Suggested interval
cron/integration_poll.php Asks Jira (and other issue trackers) what has changed Issue status never updates and comments never come back โ€” see Issue trackers every 5 minutes
cron/sla_breach_check.php Checks open tickets against their SLA targets SLA warning and breach notifications are never sent every 5 minutes
cron/webhook_deliveries.php Delivers queued outbound webhooks, with retries Webhooks fired by workflows sit in the queue and never send every 1 minute
cron/workflow_scheduled.php Fires time-based workflow triggers Rules like "this contract expires in 30 days" never run every 5 minutes
cron/task_recurrence.php Creates the next occurrence of tasks that repeat on a fixed schedule, once work on it can start Fixed-schedule repeats never appear. Repeats set to after completion are unaffected โ€” see the note below once a day
cron/calendar_sync_pull.php Reads changes back out of analysts' calendars - Microsoft 365 and CalDAV servers alike Moving or deleting a synced appointment in a calendar never reaches FreeITSM. The outbound half still works โ€” see Ticket calendar sync every 5 minutes
cron/attachment_extract.php Reads the text out of PDFs and scanned documents so they can be searched Nothing immediately โ€” see the note below every 5 minutes
scripts/documents_maintenance.php Reads the text out of files attached through Documents, and collects documents whose record has been deleted Nothing immediately โ€” see the note below every 5 minutes
cron/directory_sync.php --all Imports people from every enabled directory (LDAP / Active Directory) and address book (CardDAV) โ€” see Directory sync and Contact details Nothing immediately, and nothing loudly. People are imported only when somebody presses Run on the source's own page, so a new starter added to your directory on Monday does not exist in FreeITSM until a human remembers once or twice a day
cron/domains.php Domain registry lookups, DNS and certificate checks, expiry alerts - see Domains Lookups and alerts happen only when somebody opens Domains (at most hourly), so an expiry alert can be days late every hour
cron/lms_reminders.php Training reminder emails, when LMS โ†’ Settings โ†’ Reminders is switched on Reminders go only when a manager opens the LMS console once a day
cron/proxmox_sync.php Syncs Proxmox VE servers into Assets, each on its own interval Proxmox virtual machines are only updated when somebody presses Sync now every 15 minutes
cron/vcloud_sync.php Syncs VMware Cloud Director servers into Assets, each on its own interval The same, for vCloud every 15 minutes

Four of these were missing from this page until #182 (Domains, LMS reminders, Proxmox and vCloud), and the page said there were nine. The Background jobs tab lists them from one registry in the code (includes/job_registry.php), so when the two disagree, the tab is right.

One of these is not in cron/. scripts/documents_maintenance.php lives with the other command-line scripts, so listing the cron/ folder will not show it. It is a scheduled job in every other respect. The directory import moved to cron/directory_sync.php in the release after 4.11.0 (PR #240); a task that still runs scripts/directory_sync.php keeps working, because the old file is a one-line pointer to the new one.

The people import was missing from this page until 1.8.0, which is worth saying plainly: it has been schedulable since directory sync shipped, and anyone running a directory had no way to find that out from the documentation. If you use LDAP or Active Directory today and have been pressing Run by hand, this is the job you wanted.

--all means every enabled source of either kind. It skips anything switched off, and skips OIDC providers entirely โ€” they sign people in and import nobody. Add --preview to have it report what it would do and write nothing, which is the sensible way to see what a schedule is about to start doing on its own. A single source can be run on its own with --provider=<id>.

The first four each have a detailed setup document in the docs/ folder of the application: integration-poll-cron-setup.md, sla-cron-setup.md, webhook-cron-setup.md and workflow-scheduled-cron-setup.md.

An occurrence appears when work on it can START, not when it falls due. A repeat keeps the gap between starting and being due, so a task that needs a fortnight appears a fortnight before its due date and a task with no start date appears on the day. This is why the job runs daily rather than hourly, and why the board only ever holds work you could actually be doing: the lead time is the task's own duration, which you have already given it, rather than a number to configure.

Only half of recurring tasks needs this job. A task set to repeat after completion makes its next occurrence inside the request that completed the last one, so it works on an installation with no scheduled tasks at all. Only fixed schedule repeats โ€” the ones that arrive on their dates whether or not the previous one was finished โ€” need this job running. The calendar draws future fixed-schedule occurrences faintly before they exist, so you can see the plan either way, but the actual tasks are created by this job. See Tasks.

The attachment job is the odd one out, deliberately. It is the only one whose absence is not silent failure. Without it, waiting documents are still read โ€” a few at a time, whenever somebody is using FreeITSM โ€” so attachment searching works on an installation with no scheduled tasks at all. The job just keeps up with a busy service desk rather than trickling. Both behaviours can be turned off in Tickets โ†’ Settings โ†’ Indexing, and neither does anything until a document reader is set up under System โ†’ Integrations โ†’ Apache Tika. See Attachment text extraction.

The documents job is the other one whose absence is not silent failure. Attaching a file reads it there and then, with a few seconds to do it in โ€” right for one file, useless for somebody who has just attached fifty warranty PDFs, which then sit unread until the next upload nudges the queue. The job clears that backlog. Its second half matters more quietly: a document attached to a contract survives the contract being deleted, because the link can point at any kind of record and so no database rule can clean it up. The document becomes invisible immediately โ€” every permission check asks whether its parent still exists โ€” but the file stays on the disk forever. Nothing leaks and nothing is lost without this job; you get prompt searching and disk that does not slowly fill with files nobody can reach. See Attached documents.

Which do I actually need?

With the one scheduled task above, you need not decide: the scheduler starts only the jobs this install needs. If you schedule jobs one by one instead, only the ones matching the features you use. If you have no issue tracker connected, the tracker poll does nothing useful. If you have never written a workflow, the scheduled-workflow job has nothing to fire. If in doubt, set them all up โ€” an unused job costs a second of CPU and prints one line saying it had nothing to do.


One task per job (the older way)

Before 4.8.0 every job needed its own scheduled task, and this still works. A job added by a later release then needs a new task, which is why the one task above is easier. The lists below are kept for anybody who stays on this way, and to help you recognise the old tasks when you move over.

Windows (Task Scheduler)

Run these in a Command Prompt opened as Administrator. Adjust the two paths if your PHP or FreeITSM lives elsewhere.

schtasks /create /tn "FreeITSM Tracker Poll" /tr "C:\wamp64\bin\php\php8.4.0\php.exe C:\wamp64\www\freeitsm-app\cron\integration_poll.php" /sc minute /mo 5 /ru SYSTEM

schtasks /create /tn "FreeITSM SLA Breach Check" /tr "C:\wamp64\bin\php\php8.4.0\php.exe C:\wamp64\www\freeitsm-app\cron\sla_breach_check.php" /sc minute /mo 5 /ru SYSTEM

schtasks /create /tn "FreeITSM Webhooks" /tr "C:\wamp64\bin\php\php8.4.0\php.exe C:\wamp64\www\freeitsm-app\cron\webhook_deliveries.php" /sc minute /mo 1 /ru SYSTEM

schtasks /create /tn "FreeITSM Scheduled Workflows" /tr "C:\wamp64\bin\php\php8.4.0\php.exe C:\wamp64\www\freeitsm-app\cron\workflow_scheduled.php" /sc minute /mo 5 /ru SYSTEM

schtasks /create /tn "FreeITSM Recurring Tasks" /tr "C:\wamp64\bin\php\php8.4.0\php.exe C:\wamp64\www\freeitsm-app\cron\task_recurrence.php" /sc daily /st 01:00 /ru SYSTEM

schtasks /create /tn "FreeITSM Calendar Sync Pull" /tr "C:\wamp64\bin\php\php8.4.0\php.exe C:\wamp64\www\freeitsm-app\cron\calendar_sync_pull.php" /sc minute /mo 5 /ru SYSTEM

schtasks /create /tn "FreeITSM Documents Maintenance" /tr "C:\wamp64\bin\php\php8.4.0\php.exe C:\wamp64\www\freeitsm-app\scripts\documents_maintenance.php --quiet" /sc minute /mo 5 /ru SYSTEM

schtasks /create /tn "FreeITSM Attachment Text" /tr "C:\wamp64\bin\php\php8.4.0\php.exe C:\wamp64\www\freeitsm-app\cron\attachment_extract.php" /sc minute /mo 5 /ru SYSTEM

schtasks /create /tn "FreeITSM People Import" /tr "C:\wamp64\bin\php\php8.4.0\php.exe C:\wamp64\www\freeitsm-app\scripts\directory_sync.php --all --quiet" /sc daily /st 02:00 /ru SYSTEM

schtasks /create /tn "FreeITSM Domains" /tr "C:\wamp64\bin\php\php8.4.0\php.exe C:\wamp64\www\freeitsm-app\cron\domains.php" /sc hourly /ru SYSTEM

schtasks /create /tn "FreeITSM Training Reminders" /tr "C:\wamp64\bin\php\php8.4.0\php.exe C:\wamp64\www\freeitsm-app\cron\lms_reminders.php" /sc daily /st 07:00 /ru SYSTEM

schtasks /create /tn "FreeITSM Proxmox Sync" /tr "C:\wamp64\bin\php\php8.4.0\php.exe C:\wamp64\www\freeitsm-app\cron\proxmox_sync.php" /sc minute /mo 15 /ru SYSTEM

schtasks /create /tn "FreeITSM vCloud Sync" /tr "C:\wamp64\bin\php\php8.4.0\php.exe C:\wamp64\www\freeitsm-app\cron\vcloud_sync.php" /sc minute /mo 15 /ru SYSTEM

Only schedule the last four if you use Domains, LMS reminders, Proxmox or vCloud - the Background jobs tab shows each one as Not needed until you do.

Things that catch people out on Windows

  • If either path contains a space, the command above will fail. Put the whole thing in a .bat file and point the task at that instead โ€” far easier than fighting schtasks quoting.
  • /ru SYSTEM tasks are invisible to a normal user. Task Scheduler run without elevation will not list them, and neither will PowerShell's Get-ScheduledTask. Open Task Scheduler as administrator to see them. A task you cannot see is not necessarily a task that does not exist.
  • Last Run Result of 0x41303 means "has not run yet" โ€” normal for a task you have just created. It should become 0x0 after its first run.
  • Check the task is Enabled, not merely present. A disabled task sits in the list looking perfectly healthy.

Linux (cron)

*/5 * * * * /usr/bin/php /var/www/freeitsm-app/cron/integration_poll.php    >/dev/null 2>&1
*/5 * * * * /usr/bin/php /var/www/freeitsm-app/cron/sla_breach_check.php    >/dev/null 2>&1
*   * * * * /usr/bin/php /var/www/freeitsm-app/cron/webhook_deliveries.php  >/dev/null 2>&1
*/5 * * * * /usr/bin/php /var/www/freeitsm-app/cron/workflow_scheduled.php  >/dev/null 2>&1
0   1 * * * /usr/bin/php /var/www/freeitsm-app/cron/task_recurrence.php     >/dev/null 2>&1
*/5 * * * * /usr/bin/php /var/www/freeitsm-app/cron/calendar_sync_pull.php  >/dev/null 2>&1
*/5 * * * * /usr/bin/php /var/www/freeitsm-app/cron/attachment_extract.php  >/dev/null 2>&1
*/5 * * * * /usr/bin/php /var/www/freeitsm-app/scripts/documents_maintenance.php --quiet >/dev/null 2>&1
0   2 * * * /usr/bin/php /var/www/freeitsm-app/cron/directory_sync.php --all --quiet >/dev/null 2>&1
0   * * * * /usr/bin/php /var/www/freeitsm-app/cron/domains.php             >/dev/null 2>&1
0   7 * * * /usr/bin/php /var/www/freeitsm-app/cron/lms_reminders.php       >/dev/null 2>&1
*/15 * * * * /usr/bin/php /var/www/freeitsm-app/cron/proxmox_sync.php       >/dev/null 2>&1
*/15 * * * * /usr/bin/php /var/www/freeitsm-app/cron/vcloud_sync.php        >/dev/null 2>&1

Drop the >/dev/null while you are testing, so cron emails you the output. Every job exits with a non-zero code when it fails, so a cron wrapper or monitoring of exit codes can notice too.

Docker

From 4.8.0 the image runs the scheduler itself. When the container starts, it runs cron/run.php once a minute as the web server user, so every needed job runs with nothing set up on the host. Its messages, if any, are in docker logs.

  • Set TZ in docker-compose.yml, for example TZ=Europe/London, and recreate the container. Without it the container's timezone is UTC, so a job set to run daily at 06:00 runs at 06:00 UTC. Background jobs warns while it is unset.
  • Already scheduling jobs from the host's crontab? They still work alongside it (a job started twice runs once), so remove them when it suits you - see Already have one task per job?, including the catch about timing. Or keep driving everything from the host and set FREEITSM_SCHEDULER=0 to stop the container doing it too - then run cron/run.php from the host:
* * * * * cd /opt/freeitsm && docker compose exec -T app php /var/www/html/cron/run.php --quiet

(The -T stops it asking for a terminal, which cron does not have.)

Before 4.8.0 the image ran no scheduler, and each job had to be scheduled from the host like the line above, one per job.

Calling a job by its web address

Most jobs can also be run by calling their web address with a token, for a scheduler or monitoring tool that can make a web request but cannot run a command. The tokens are stored encrypted; print the full addresses with:

php scripts/cron_token.php --url

A run through the web address counts as scheduled, exactly like the command line. Proxmox, vCloud, documents maintenance and directory sync are command-line only. (The training reminders address could never work before #182 - its token was never created. Database Verification now creates it.)

Running one by hand

Useful for testing, or before you have set the schedule up. From the application folder:

php cron/integration_poll.php

Each job prints what it did. The tracker poll, for example:

Jira                         checked   1, changed  0
    comments: 1 seen, 1 imported

Checking they are working

Start with Background jobs (Reporting โ†’ System logs). Every job records every run, and the tab says for each one whether its schedule last ran it successfully within its interval - and if not, whether it is overdue, failing, stuck or never seen. A page load or Run now does not count: only the schedule proves the schedule.

To be sure beyond that, the most reliable test is not "does the task exist" but "did something happen that only the job could have caused".

  • Tracker poll โ€” change an issue's status in Jira, wait for two intervals, and see whether the pill on the linked ticket follows.
  • Webhooks โ€” fire a workflow that sends one, then check the delivery queue empties.
  • SLA โ€” the notification arrives, or it does not.
  • Documents โ€” attach a file to anything, then search for a word that is only inside it. Before the job has run the file is found by its name only; afterwards, by its contents.
  • Recurring tasks โ€” set a task to repeat on a fixed schedule with a date that has already passed, run the job by hand, and the next occurrence should appear. Running it twice must not produce two: creating an occurrence is idempotent on the series and the date. To check the start-date rule, give the task a start date a week before its due date and a repeat due a week from now โ€” it should be created straight away, dated in the future.

๐Ÿ’ก Each job refuses to run more often than a minimum interval, and each connection has its own interval too. So running a job twice in quick succession may print "No connections due" or "Rate limited". That is the job protecting itself, not a fault โ€” wait and run it again. A job also never runs twice at once: started while it is still running, it prints "Skipped: another run of this job is still going" and the run history shows it as skipped.


What is not a scheduled task

Worth knowing, so you do not go looking for a job that does not exist โ€” or create one that should not.

  • Incoming email. Mailboxes are checked by the browser while an analyst has the ticket list open, not by a scheduled job. Mail therefore arrives when somebody is working, which is usually what you want, but it does mean nothing is collected overnight with every browser closed.
  • Snoozed tickets waking up. There is no job. A snoozed ticket reappears because the snooze time has passed โ€” it is read from the clock whenever the folder is drawn.
  • InTune synchronisation. scripts/intune_worker.php and intune_app_worker.php look like cron jobs but are not. The application starts them itself when you run a sync. Do not schedule them.

See also: Background jobs ยท Installation ยท Issue trackers (Jira) ยท Workflows ยท Webhooks