Scheduled jobs#
Everything the system does on its own. The list is generated from the code, so it names exactly the jobs the repository really contains.
How a job is called#
| Thing | How it works |
|---|---|
| Authorisation | the request must carry a ?token= matching the cron.token setting (group 1) |
| Response | always JSON — {"status":"ok",…}, {"status":"error",…} or {"status":"skipped"} |
| Concurrency | a second instance of the same job ends as skipped, it does not wait |
Without a token it returns 403 — and an empty token lets NOBODY in
The check compares the expected and the supplied value; when cron.token is
missing from the settings, not even an empty request gets through. On a fresh
server it is the first thing to fill in — otherwise nothing runs at all and
nothing anywhere complains.
The concurrency lock is fail-open
Until the lock table exists, skipped is never returned and jobs may overlap.
That is deliberate — a missing table must not stop operations — but it means
"I have never seen skipped" is no proof that the lock works.
How to tell a job actually ran
By its response, not by the fact that "nothing crashed". {"status":"ok"} is
also returned by a job that did nothing at all — the counts are in the other
keys of the response.
The scheduler — one address instead of fifty#
There are around seventy jobs. Entering them one by one at the hosting is forgotten with every new job, so the scheduler calls them: the hosting calls a single address
and the scheduler uses the cms_system_cron_jobs table to decide which jobs are due, then calls
them one by one over HTTP on its own address with the same token — no job is rewritten.
The administrator manages it in the administration (Settings → Cron, see Task scheduler).
| Thing | How it works |
|---|---|
| Catalogue | the #[CronJob(frequency: …, outbound: …, query: […])] attribute on a public action*() of a cron presenter — the source of truth; the docblock (URL:, Frekvence:) stays for this overview |
| A new job | without the attribute the scheduler does not know it — CronJobCatalogTest fails for every action* lacking it. It is added to the table by itself on the scheduler's next call (no migration) |
| Frequency | text every:N · daily:HH:MM · weekly:D@HH:MM · monthly:D@HH:MM · manual (CronFrequency); "once after deployment" and repair jobs = manual |
outbound |
a required argument (no default): true = sends e-mails/SMS, issues documents, deletes/anonymises user data, calls payment gateways or notifies watchdogs. An outbound job starts disabled. When in doubt, true |
query |
fixed call parameters (e.g. sitemaps ['save' => '1'] — without save they only return a preview) |
| Modules | cms_system_cron_jobs.module_id from the path /cron/<module>/… (cms_system_modules); jobs of a module with activated = 0 are neither shown nor run, the system module is always on |
| Run | 45 s budget, the longest-waiting first; an error or timeout = error state and the next try only at the next term; "skipped" (the job's lock) is retried on the next call. The response is stored shortened (≤ 500 characters) and the token never reaches it or any log |
| Concurrency | two schedulers: the second ends skipped on the lock (cron.system.scheduler.run) like any cron |
| The scheduler is not a job | SchedulerPresenter is left out of the catalogue (CronJobCatalog::EXCLUDED_PRESENTERS) |
The scheduler calls jobs over HTTP on its own address
On a single-threaded php -S (the local dev server) such a call hangs and ends with a timeout —
the server cannot serve a second request until the first one finishes. Verify with several workers:
PHP_CLI_SERVER_WORKERS=4 php -S ….
Adding a new cron action?
- Add
#[CronJob(frequency: '…', outbound: true|false)]aboveaction*()(andqueryif needed). - Add the job name to the translations
admin.system.presenters.cronJob.jobs.<module>.<presenter>.<action>(cs/en/sk); without a translation the administration shows only the address. - The rest (table, list, scheduling) happens by itself.
Deployment#
The Suggested interval column is the recommendation from the source code — build your crontab from it. For example:
*/1 * * * * curl -s "https://site.com/cron/system/email-queue/send?token=TOKEN"
*/5 * * * * curl -s "https://site.com/cron/store/reservation/expire?token=TOKEN"
15 3 * * * curl -s "https://site.com/cron/blog/sitemap/article?token=TOKEN"
An unscheduled job NEVER runs — and nothing breaks
It raises no error. It merely stops doing what it was meant to: carts do not expire, invoices are not generated, data is not cleaned up. It usually surfaces only when something "has somehow grown". So walk the whole list below after an installation, not just the parts you happen to use.
Not everything is periodic
A few jobs are repair or one-off tasks — their interval says "by hand". They do not belong in the crontab; run them when needed.
The sitemap index goes last, after the other sitemaps
It assembles a directory of already generated files. Run first, it builds one from whatever the previous run left behind.
The jobs#
Shared#
| Endpoint | What it does | Suggested interval |
|---|---|---|
/cron/base/newsletter/dispatch |
Dispatches executing newsletters into the e-mail queue in batches ; the queue cron sends them | every 5 minutes |
/cron/base/rating/recalc |
Recalculates the rating thread aggregates as a safety net against drift | periodically, e.g. once a day |
Classifieds#
| Endpoint | What it does | Suggested interval |
|---|---|---|
/cron/bazaar/advert/notice-expiration |
Sends BZ_ADVERT_EXPIRATION_NOTICE to adverts expiring within N days |
daily, at night |
/cron/bazaar/advert/delete-expired |
Silently marks deleted=1 on adverts expired for more than M days; no e-mail is sent |
daily |
/cron/bazaar/advert/recount-prices |
Recalculates the denormalized price column of all adverts from the price parameter |
every 10-15 minutes |
/cron/bazaar/category/recount |
Recalculates the denormalized advert_count of all categories |
every 10-15 minutes |
/cron/bazaar/elastic/reindex |
Full reindex of adverts into a new index version with an atomic alias swap | once a day, at night |
/cron/bazaar/elastic/sync |
Incremental sync: indexes adverts inserted since the watermark and cleans up orphans | every 10 minutes |
/cron/bazaar/sitemap/category |
Sitemap of all active Bazaar categories with hreflang variants | daily, at night |
/cron/bazaar/sitemap/advert |
Sitemap of active adverts, split into files by ID ranges (size depends on the number of languages) | daily, at night |
/cron/bazaar/watchdog-email/send |
Modes 1 (immediate) and 3 (after the advert becomes active) | continuously, e.g. every 5-10 minutes |
/cron/bazaar/watchdog-email/digest |
Mode 2: daily digest | once a day, e.g. at 23:00 |
/cron/bazaar/watchdog-email/purge-old |
Queue retention of the watchdog: deletes sent and canceled _watchdog_email_rel rows older than N days, plus e-mails (_watchdog_emails and texts) no row points to anymore |
daily |
Blog#
| Endpoint | What it does | Suggested interval |
|---|---|---|
/cron/blog/elastic/reindex |
Full reindex of articles into a new index version with an atomic alias swap | once a day, at night |
/cron/blog/elastic/sync |
Incremental sync: indexes articles changed since the watermark and cleans up orphans | every 10 minutes |
/cron/blog/sitemap/article |
— | daily, at night |
/cron/blog/sitemap/event |
— | daily, at night |
/cron/blog/sitemap/news |
— | daily, at night |
/cron/blog/sitemap/article-category |
— | daily, at night |
/cron/blog/sitemap/event-category |
— | daily, at night |
/cron/blog/sitemap/news-category |
— | daily, at night |
Company catalogue#
| Endpoint | What it does | Suggested interval |
|---|---|---|
/cron/comcat/advert/notice-expiration |
Sends CC_ADVERT_EXPIRATION_NOTICE to company profiles expiring within N days |
daily, at night |
/cron/comcat/advert/delete-expired |
Silently marks deleted=1 on company profiles expired for more than M days; no e-mail is sent |
daily |
/cron/comcat/category/recount |
Recalculates the denormalized advert_count of all categories |
every 10-15 minutes |
/cron/comcat/elastic/reindex |
Full reindex of company profiles (Comcat Advert) into a new index version with an atomic alias swap |
once a day, at night |
/cron/comcat/elastic/sync |
Incremental sync: indexes company profiles inserted since the watermark and cleans up orphans | every 10 minutes |
/cron/comcat/sitemap/category |
Sitemap of all active Comcat categories with hreflang variants | daily, at night |
/cron/comcat/sitemap/advert |
Sitemap of active company profiles, split into files by ID ranges (size depends on the number of languages) | daily, at night |
/cron/comcat/watchdog-email/send |
Modes 1 (immediate) and 3 (after the advert becomes active) | continuously, e.g. every 5-10 minutes |
/cron/comcat/watchdog-email/digest |
Mode 2: daily digest | once a day, e.g. at 23:00 |
/cron/comcat/watchdog-email/purge-old |
Queue retention of the watchdog: deletes sent and canceled _watchdog_email_rel rows older than N days, plus e-mails (_watchdog_emails and texts) no row points to anymore |
daily |
Company#
| Endpoint | What it does | Suggested interval |
|---|---|---|
/cron/company/sitemap/service |
Sitemap of the company service detail pages (Front:Company:Service:detail) |
daily, at night (before the sitemap index at 04:30) |
Discussion#
| Endpoint | What it does | Suggested interval |
|---|---|---|
/cron/discussion/elastic/reindex |
Full reindex of thread roots (level=1) into a new index version with an atomic alias swap |
once a day, at night |
/cron/discussion/elastic/sync |
Incremental sync: indexes new threads since the watermark and cleans up orphans | every 10 minutes |
/cron/discussion/sitemap/message |
Sitemap of top-level (level=1) active messages, split by ID ranges (size depends on the number of languages) |
daily, at night |
/cron/discussion/sitemap/category |
Sitemap of all Discussion categories | daily, at night |
E-shop#
| Endpoint | What it does | Suggested interval |
|---|---|---|
/cron/eshop/cart/cleanup |
Deletes abandoned carts whose retention period has expired | daily, at night |
/cron/eshop/complaint/expire |
Closes complaints whose resolution deadline has passed | daily (deadlines are counted in days) |
/cron/eshop/elastic/reindex |
Full reindex of sale cards into a new index version with an atomic alias swap | once a day, at night |
/cron/eshop/elastic/sync |
Incremental sync: indexes cards changed since the watermark and cleans up orphans | every 10 minutes |
/cron/eshop/feed/generate |
Generates XML feeds for product comparison sites | hourly (or as the comparison site requires) |
/cron/eshop/order/expire |
Cancels unpaid orders and generates missing invoices | hourly |
/cron/eshop/order/cancel-unpaid |
Cancels unpaid orders and generates missing invoices | daily (the period is counted in days) |
/cron/eshop/order/invoice |
Cancels unpaid orders and generates missing invoices | every 15 minutes |
/cron/eshop/price/recalc |
Activates planned prices and repairs the denormalized price column | every 15 minutes (a longer interval delays activation of planned prices) |
/cron/eshop/product-import/run |
Runs the oldest queued job | every minute |
/cron/eshop/sitemap/category |
Sitemap of all e-shop categories with hreflang variants | daily, at night |
/cron/eshop/sitemap/product |
Sitemap of visible products (sale cards), split into files by ID ranges (size depends on the number of languages) | daily, at night |
Invoicing#
| Endpoint | What it does | Suggested interval |
|---|---|---|
/cron/invoicer/payment/generate |
Issues invoices for paid payments according to the module rules | often, e.g. every 15 minutes: invoices appear shortly after a payment status change, but outside |
/cron/invoicer/schedule/generate |
Issues recurring invoices for schedules that are due | daily |
Warehouse#
| Endpoint | What it does | Suggested interval |
|---|---|---|
/cron/store/batch/expiry-alert |
Alerts about batches nearing expiration | daily, in the morning |
/cron/store/reservation/expire |
Releases stock reservations that have expired | every 5 minutes |
/cron/store/stock/recount |
Nightly stock recount: repairs reserved quantities, batches and card prices | daily, at night |
/cron/store/stock/low-stock-alert |
Alerts about cards below the minimum stock: one summary e-mail to stock.lowStock.email |
daily, in the morning |
System#
| Endpoint | What it does | Suggested interval |
|---|---|---|
/cron/system/asset-thumbnail-catchup/generate |
Generates missing image variants (poster from the first frame) for video assets whose variants were never physically generated, typically because ffmpeg was missing or failed at upload time |
daily, at night |
/cron/system/asset-thumbnail-catchup/backfill-meta |
Backfills Asset.meta (real variant sizes) for assets where it is missing (meta IS NULL) |
once after deployment, no longer needed afterwards |
/cron/system/asset-variant-regenerate/default |
Regenerates missing variants of stored images, for all usages at once | repeatedly after deployment until the response says remaining: 0; then daily at night is |
/cron/system/auth-log/purge-old |
Periodic cleanup of the persistent audit log (cms_system_auth_log) |
daily; otherwise the table grows |
/cron/system/dropzone-temp/clean |
Cleans dropzone temp folders (temp/dropzone): files and empty folders older than 24 h |
daily |
/cron/system/elastic/status |
Elasticsearch cluster health plus document counts per index; a monitoring smoke check | manually (state check) |
/cron/system/elastic/watch |
Watches search availability and reports problems by e-mail | every 15 minutes |
/cron/system/email-queue/send |
Sends e-mails from the queue gradually | every minute |
/cron/system/email-queue/purge-old |
Cleans processed (sent or stopped) e-mails older than N days; newsletter rows stay | daily |
/cron/system/file-system/sanitize |
Sanitizes file and folder names in the uploads directory | manually, after bulk file uploads (not periodic) |
/cron/system/jwt-deny-list/cleanup-expired |
Cleans expired rows of cms_system_jwt_denylist (the DB-backed JtiDenyList) |
daily, at night |
/cron/system/log/purge-old |
Retention of the log/ directory |
daily |
/cron/system/notification/clean-orphans |
Detaches orphaned admin notifications from their target (the target record, such as an order, user or payment, no longer exists) | daily |
/cron/system/notification/purge-read |
Detaches orphaned admin notifications from their target (the target record, such as an order, user or payment, no longer exists) | daily |
/cron/system/page-tree/rebuild |
Regenerates the nested-set indexes (lft, rgt, level) of the page tree in cms_system_pages from the adjacency list (parent_id) |
manually, after a bulk change of the page tree (not periodic) |
/cron/system/payment/activation-audit |
Payment maintenance jobs: the activation audit and the retention of the gateway notification history | every 15 minutes |
/cron/system/payment/purge-notifications |
Payment maintenance jobs: the activation audit and the retention of the gateway notification history | every 15 minutes |
/cron/system/rate-limit/purge-old |
Cleans expired rows of cms_system_rate_limit (the DB-backed RateLimit) |
daily, at night (a no-op with useRedis: true) |
/cron/system/scheduler/run |
Cron scheduler: the only address the hosting has to call | every 5 minutes |
/cron/system/sitemap/generate |
Generates the XML sitemap of System pages with all language variants (hreflang) | daily, at night |
/cron/system/sitemap/index |
Generates the sitemap index linking all generated sitemap- .xml files in the directory |
daily, at night, after the other sitemaps |
/cron/system/user/anonymize-deleted |
Account deletion: anonymizes the accounts whose grace period (account.deletion.graceDays, default 7 days) has passed |
daily |
/cron/system/user-refresh-token/purge-old |
Periodic cleanup of expired refresh tokens (cms_system_user_refresh_tokens) |
daily |