Scheduler
Deployment options are split into Development and Production. Development offers an Auto-Run option
that runs jobs entirely in the browser, with a countdown ring showing the next interval.
Production shows an example /cron request using a
service token. Each job displays its run
count and duration metrics, and the edit and
run now actions open in a popup.
The scheduler is responsible for planning and running jobs.
A job is an automated background task: checking your mailboxes for new messages, search indexing new records, performing nightly maintenance, draining the parallel queue, triggering automation timers, etc. There are several built-in jobs (listed below), and plugins can add new ones.
Each job is repeated at a specific interval – a number of minutes, hours, or days. A job can be disabled to prevent it from running.
Different jobs can run at the same time. A job is locked while running to prevent multiple copies of itself from starting.
A job's extension manifest can flag it as parallel. Parallel jobs limit concurrency through reserved queue slots rather than a hard lock, so multiple invocations can overlap when capacity is available. The built-in Background Queue runs this way – it can fan out across slots to drain work in near real time rather than once per minute. Traditional locked jobs remain available for tasks that must not overlap (e.g. mailbox polling).
A parallel job draws from the slow lane, since it holds a slot for as long as its work takes. There's no per-job concurrency setting: a parallel job takes a slot like any other long-running drain and runs as fast as it can. A job that's currently running concurrently shows a countdown ring of its own rather than a fixed count, but it's excluded from the page's next to fire chip – a job that drains as fast as it can has no cadence to be next on.
Each job has a "run now" link that will immediately run the job with logging enabled from inside your web browser. This is useful for troubleshooting and development, but the scheduler should be automated in production environments so that the jobs run without human intervention. Run now on a parallel job takes a real slot, so it reports that every slot is busy rather than starting work that has nowhere to run.
Automating /cron
For Cerb's scheduled jobs to automatically run in the background, you need to configure a third-party tool to request the /cron page every minute. On Unix-based systems this is accomplished with a cronjob1. On Windows Server you can add a Scheduled Task2.
If you're using Cerb Cloud, we handle this for you.
We recommend using curl or wget to request your scheduler URL every minute.
The /cron page doesn't require a worker login. It is authenticated with service tokens – manage them at Setup » Configure » Security. The legacy AUTHORIZED_IPS_DEFAULTS IP allowlist has been deprecated.
Create a token scoped to cron:* (or narrower, like cron:maint) so the cronjob can't authenticate against /debug or /update. Then use one of the examples below.
Using curl
curl --silent --show-error --fail --max-time 60 \
--header "Authorization: Bearer ${CERB_SERVICE_TOKEN}" \
--output /dev/null \
https://cerb.example.com/cronThe flags do the following:
| Flag | Purpose |
|---|---|
--silent --show-error |
Suppress progress output, but still print errors to stderr so cron can email you |
--fail |
Treat non-2xx HTTP responses as failures (returns a non-zero exit code) |
--max-time 60 |
Abort after 60 seconds so a stuck request doesn't pile up minute-after-minute |
--header "Authorization: Bearer …" |
Authenticate with a service token |
--output /dev/null |
Discard the response body – cron only cares about the exit code |
Using wget
wget --quiet --tries=1 --timeout=60 \
--header="Authorization: Bearer ${CERB_SERVICE_TOKEN}" \
--output-document=/dev/null \
https://cerb.example.com/cron--quiet silences successful runs, --tries=1 prevents automatic retries (Cerb will pick the work back up on the next minute anyway), and --timeout=60 caps the request.
Adding to crontab
Edit the crontab with crontab -e and add a single line that runs every minute:
MAILTO=admin@example.com
CERB_SERVICE_TOKEN=cerb_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# Cerb scheduler
* * * * * curl -sSf --max-time 60 -H "Authorization: Bearer $CERB_SERVICE_TOKEN" -o /dev/null https://cerb.example.com/cronBy not redirecting stderr (no trailing 2>&1 > /dev/null), cron will email MAILTO whenever the request fails. If you'd rather log to a file instead, append:
* * * * * curl -sSf --max-time 60 -H "Authorization: Bearer $CERB_SERVICE_TOKEN" -o /dev/null https://cerb.example.com/cron >> /var/log/cerb-cron.log 2>&1…and rotate the log with logrotate.
Best practices
- Don't use the master
APP_SERVICE_TOKENfor cron. Create a dedicated service token per host or cronjob with a narrow scope likecron:*. You can revoke individual tokens without disrupting other automation, and thecerb.service.token.usesmetric lets you audit each token's traffic. - Always use HTTPS. A service token is a credential – it shouldn't traverse the network in cleartext.
- Cap the request with a timeout. A stuck PHP-FPM worker can cause minute-by-minute pile-up otherwise; both
curl --max-timeandwget --timeoutprevent this. - Let cron mail you on failure. Set
MAILTO=at the top of the crontab and resist the urge to2>&1everything into/dev/null– silent failures are the worst kind. - Don't add a lock file. Cerb's scheduler locks each job internally, so overlapping
/cronrequests are safe – the second request will just skip any in-flight job. - Behind a load balancer, point cron at a specific internal node (e.g.
https://cerb-internal-01.example.com/cron) rather than the public hostname. This avoids unnecessary edge/SSL termination cost for traffic you control.
Built-in jobs
| Job | Default interval | Description |
|---|---|---|
cron.automations |
1 minute | Runs cron.maint and cron.heartbeat automation events, and dispatches due automation timers. |
cron.background_queue |
1 minute | Drains the parallel background queue – record.changed events, queue jobs, metrics, and worker-initiated jobs whose monitors are no longer connected. |
cron.heartbeat |
1 minute | Fires the cron.heartbeat event for automations that need to run on a regular cadence. |
cron.mail_queue |
1 minute | Processes outbound email in the mail queue. |
cron.mailbox |
1 minute | Connects to configured mailboxes and downloads new messages. |
cron.maint |
daily | Nightly maintenance – pruning expired records, clearing watcher links on deactivated workers, etc. Also fires the cron.maint event. |
cron.packages |
1 minute | Imports queued packages (e.g. plugin-provided or admin-installed templates). |
cron.parser |
1 minute | Parses inbound email messages into tickets, running mail.filter automations. |
cron.reminders |
1 minute | Dispatches due reminders. |
cron.search |
5 minutes | Updates search indexes with newly created or modified records. |
cron.storage |
daily | Moves attachments between configured storage profiles based on age. |
Disabled jobs are skipped until re-enabled.