# Run scheduled jobs

**Goal:** run recurring background work (Drupal cron, imports, cleanups) on a Cloud Platform [environment](/start-here/glossary/#environment), reliably and with output you can find later.

## What you'll have when you're done

- Drupal cron running on a schedule you chose, not on page-request luck.
- Job output landing in a log file instead of silently filling the disk.

## Prerequisites

- A Cloud Platform application, and either [acli](/start-here/glossary/#acli) authenticated ([CLI quickstart](/cloud-platform/cli/quickstart/)) or access to the Cloud Platform user interface
- [SSH access](/cloud-platform/cli/everyday-workflows/#run-drush-remotely-or-open-a-shell) for testing commands before scheduling them

## Steps

<Steps>

1. ### Understand how scheduled jobs work here

   Cloud Platform environments have a `Scheduled Jobs` feature: a command line plus a schedule, held per environment. Creating one is a single command:

   ```bash
   acli api:environments:cron-create myapp.dev \
     '/var/www/html/${AH_SITE_NAME}/scripts/my-script.sh' \
     '25 7 * * *' 'Nightly cleanup'
   ```

   The three arguments after the environment are positional, in this order: the command to run, the cron frequency string, and the label the job is listed under. Single-quote the command so your own shell leaves `${AH_SITE_NAME}` intact for the platform to expand at run time.

   The rest of the lifecycle is the same shape. `acli api:environments:cron-job-list myapp.dev` prints the environment's jobs, each with the `id` that `api:environments:cron-delete`, `api:environments:cron-enable`, and `api:environments:cron-disable` take as their second argument. The environment's `Scheduled Jobs` page in the Cloud UI has the same actions; see [Using scheduled jobs](https://docs.acquia.com/acquia-cloud-platform/using-scheduled-jobs-support-your-application) on docs.acquia.com.

   Jobs are environment-specific by design; there is no "run on all environments" job. Once dev's set is right, copy it rather than retyping it:

   ```bash
   acli env:cron-copy myapp.dev myapp.prod
   ```

   That copies **all** of the source environment's cron tasks to the destination, so tune dev to what production should run before you point it anywhere that matters.

   The platform's constraints on the command line itself:

   - Maximum 255 characters. Anything longer goes into a shell script committed to your repository, and the job runs the script.
   - Use absolute paths everywhere; the job's environment is minimal. The platform provides `${AH_SITE_NAME}` to build them: `/var/www/html/${AH_SITE_NAME}/docroot`.
   - `%` is special in cron syntax; escape it as `\%`.

   For anything non-trivial, the script-in-repo pattern is the recommended one anyway: it's version-controlled and its logging is controllable. Commit `scripts/my-script.sh` and schedule:

   ```
   /var/www/html/${AH_SITE_NAME}/scripts/my-script.sh
   ```

2. ### Disable Drupal's built-in automated cron

   Uninstall the Automated Cron module, or set `Configuration > System > Cron > Run cron every` to `Never`. Request-triggered cron runs on whatever traffic happens to arrive and ties up PHP processes mid-request; a scheduled job runs when you said it should.

3. ### Schedule drush cron as a job

   The command needs three things: the right docroot, the site URI, and a logging redirect:

   ```bash
   drush --root=/var/www/html/${AH_SITE_NAME}/docroot --uri=https://www.example.com -d -v cron &>> /shared/logs/drush-cron.log
   ```

   - `--uri` matters: cron runs per site, and on a [multisite](/start-here/glossary/#site-multi-experience-operations) codebase you schedule one job per site URI.
   - The `&>> /shared/logs/...` redirect is not optional politeness. Job output that isn't redirected gets mailed to the application user on the box; unread mail accumulates until it fills the disk and takes the application down. Always redirect to a log file under `/shared/logs`.
   - `/shared/logs` files are not auto-rotated and must stay under 1 GB, so prune old logs periodically (that can be its own scheduled job).

   As written, the command is about 125 characters, comfortably inside the 255-character limit; committing it as a script (step 1) still pays, keeping it version-controlled with its logging adjustable in the script itself. Schedule the script:

   ```bash
   acli api:environments:cron-create myapp.prod \
     '/var/www/html/${AH_SITE_NAME}/scripts/drush-cron.sh' \
     '25 7 * * *' 'Drupal cron'
   ```

4. ### Confirm it runs

   `acli api:environments:cron-job-list myapp.prod` confirms the job registered: each entry lists the stored `command`, the schedule split across `minute`, `hour`, `day_month`, `month`, and `day_week`, the `label`, and whether it is enabled. Scheduled-job STDOUT is captured in `cronjob.log`, downloadable from the environment's `Logs` page; your own `&>>` redirect target has the rest. Before trusting a job to the scheduler, test the exact command over [SSH](/cloud-platform/cli/everyday-workflows/#run-drush-remotely-or-open-a-shell) first, from `$HOME`, so you're testing in the same minimal environment the job gets.

</Steps>

## When something goes wrong

**The job didn't run, or ran and failed**: the triage steps are in [diagnose a slow or erroring site](/cloud-platform/observability/diagnose/#a-background-job-didnt-run).

**The job dies partway through on memory**: jobs created through the interface using Acquia's cron wrapper run with your environment's configured PHP memory limit (default 128 MB), while drush-triggered commands get the CLI limit of 512 MB. Running the work via drush instead of the wrapper is the platform's own first suggestion.

**The disk is filling up**: look for un-redirected job output (mail spool) or unrotated logs in `/shared/logs`.

## Next steps

- [Per-environment settings](/cloud-platform/configure/settings/): the environment variables (like `AH_SITE_NAME`) jobs build paths from.
- [What the platform logs](/cloud-platform/observability/logs/): where `cronjob.log` fits in the catalog.
- Long-running web requests that should be jobs: see the timeout discussion in [diagnose problems](/cloud-platform/observability/diagnose/#users-are-seeing-errors).
- [acli command reference](/cloud-platform/reference/acli/#the-cloud-platform-api): the generated `api:*` commands the steps above use, and how `--task-wait` turns an async operation into a scriptable one.
