Skip to content

Background cron tasks

Greg Bowler edited this page Jun 25, 2026 · 3 revisions

When WebEngine projects need work to happen outside the normal request cycle, use background cron tasks.

A browser request should render a page, return an API response, or accept user input. It should not wait while the application refreshes a remote cache, sends a digest email, imports a feed, or cleans old generated files. Those jobs belong in the project, but they should run on their own schedule.

WebEngine uses the PHP.GT/Cron component for this. The schedule lives in a project-level crontab file, and the task entry points usually live in the project's cron/ directory.

Project layout

Here's an example project with two background cron tasks:

.
├── class/
│   └── ServiceLoader.php
├── cron/
├───├── cleanup/
│   │   └── cache.php 
│   └── refresh-news.php
├── page/
├── www/
└── crontab

The crontab file decides when jobs are due. The cron/ directory contains the project code that runs when those jobs fire.

The crontab file

Create a file named crontab in the project root. It follows the standard unix crontab format. Example:

# Refresh remote content every 15 minutes.
*/15 * * * * refresh-news?source=php

# Clear generated cache files every night.
30 2 * * * cleanup/cache

# Run a normal shell command at the top of every hour.
0 * * * * bash bin/report-status.sh

The first five fields use familiar crontab syntax:

* * * * * command
| | | | |
| | | | +-- day of week
| | | +---- month
| | +------ day of month
| +-------- hour
+---------- minute

The command after the schedule is interpreted relative to the project. It can be a cron/ script, a go() cron script with query-string input, a static PHP method, or a shell command.

PHP.GT/Cron also provides a few helper extensions that are not part of every Unix cron implementation. For sub-minute jobs, add an s suffix to the first field, such as */10s * * * * refresh-news, to run every 10 seconds. It also supports special @ schedule names such as @hourly, @daily, @weekly, @monthly, @yearly, and @annually. These helpers are convenient for WebEngine projects, but keep in mind that they are interpreted by PHP.GT/Cron rather than by the system crontab command.

Cron go() scripts

The most WebEngine-friendly style is a PHP file in cron/ that defines a go() function. It feels similar to page logic: declare the services you need as parameters and let the container provide them.

crontab:

*/15 * * * * refresh-news?source=php

cron/refresh-news.php:

<?php
use GT\Input\Input;
use App\NewsImporter;

function go(Input $input, NewsImporter $newsImporter):void {
	$source = $input->getString("source");
	$newsImporter->refresh($source);
}

The ?source=php part is parsed as query-string input and is available through Input. Other parameters are resolved through the same service-loading conventions used by WebEngine page logic.

This makes go() scripts a good fit for application tasks that need repositories, API clients, configuration, logging, or other project services.

Nested tasks

Cron scripts can be organised into subdirectories:

30 2 * * * cleanup/cache

This can point at:

cron/cleanup/cache.php

Nested paths are for go() cron scripts. In this example, cron/cleanup/cache.php should define function go(...).

Ordinary PHP scripts

For simple jobs that do not need service injection, create a standalone script:

crontab:

0 * * * * build-index --quiet

cron/build-index.php:

<?php
[$script, $mode] = $argv + [null, null];

// Build generated files, warm a cache, or call application code here.

The runner resolves build-index to cron/build-index.php and passes any trailing arguments through to the script. Ordinary script aliases are direct children of cron/; use a go() script when you want nested paths such as cleanup/cache.

Static methods and shell commands

The command can also call a static PHP method, loaded using the project's autoloader:

0 9 * * MON-FRI App\Task\Digest::send("team@example.com")

Or it can run a shell command exactly as written:

30 2 * * * php bin/nightly-maintenance.php

Use a go() script for application-integrated work, a normal cron/ script for standalone PHP scripts, and a shell command when the job naturally belongs to another executable.

Local development

When running the application locally, gt run starts the WebEngine development server and can run the cron watcher alongside it.

gt run

If a project has cron tasks but you only want the web server and other development processes, start without cron:

gt run --no-cron

If you only want the scheduler, run:

gt cron

With no flags, gt cron reads ./crontab, runs jobs that are due now, prints the current time, prints the next scheduled job, and exits.

Example output:

Current time: 16:49:52 (15:49:52 UTC)
Just ran 0 jobs
Next job at: 17:00:00 (16:00:00 UTC) [build-index]

Use watch mode to keep the scheduler running:

gt cron --watch

Useful local checks:

gt cron --validate
gt cron --now
gt cron --now --watch

--validate checks the crontab file without running jobs. --now runs every task once before normal scheduling begins, which is useful when testing new task code.

Production use

gt run is for local development. In production, the web server handles requests and the cron runner should be managed as a separate long-running process.

Use the process manager provided by your hosting environment, such as systemd, Supervisor, a container process, or a platform-specific worker service. The important part is that the process starts from the project root and runs the scheduler in watch mode:

gt cron --watch

The process manager should restart it after deploys, reboots, or crashes. Keep only one active scheduler process per environment unless a task is explicitly designed to run concurrently.


Move back to Run an application, or continue to the request-response lifecycle.

Clone this wiki locally