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.phpsets, orTZon 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-runlists 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.phpand 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:
- Add the one scheduled task for
cron/run.php(above). - 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.
- 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 incron/orscripts/- except thecron/run.phpline. - 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.
- 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:
- 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.phplives with the other command-line scripts, so listing thecron/folder will not show it. It is a scheduled job in every other respect. The directory import moved tocron/directory_sync.phpin the release after 4.11.0 (PR #240); a task that still runsscripts/directory_sync.phpkeeps 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.
--allmeans every enabled source of either kind. It skips anything switched off, and skips OIDC providers entirely โ they sign people in and import nobody. Add--previewto 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
.batfile and point the task at that instead โ far easier than fightingschtasksquoting. /ru SYSTEMtasks are invisible to a normal user. Task Scheduler run without elevation will not list them, and neither will PowerShell'sGet-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 Resultof0x41303means "has not run yet" โ normal for a task you have just created. It should become0x0after 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
TZindocker-compose.yml, for exampleTZ=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=0to stop the container doing it too - then runcron/run.phpfrom 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.phpandintune_app_worker.phplook 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