> ## Documentation Index
> Fetch the complete documentation index at: https://developer.upsun.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Crons

> A cron dictionary that defines scheduled tasks for the app.

export const DynamicCodeBlock = ({language = 'yaml', filename, icon, lines, wrap, expandable, highlight, focus, children}) => {
  const STORAGE_KEY = 'upsun_versions_cache';
  const COMPOSABLE_STORAGE_KEY = 'upsun_composable_cache';
  const CACHE_TTL = 5 * 60 * 1000;
  const API_URL = 'https://meta.upsun.com/images';
  const COMPOSABLE_API_URL = 'https://meta.upsun.com/composable';
  const DEBUG_PREFIX = '[DynamicCodeBlock cache]';
  const [versionData, setVersionData] = useState(null);
  const [versionError, setVersionError] = useState(false);
  const [composableData, setComposableData] = useState(null);
  const [composableError, setComposableError] = useState(false);
  useEffect(() => {
    const fetchData = async () => {
      let cachedData = null;
      let cachedEtag = null;
      if (typeof localStorage !== 'undefined') {
        try {
          const cached = localStorage.getItem(STORAGE_KEY);
          if (cached) {
            const parsed = JSON.parse(cached);
            cachedData = parsed?.data || null;
            cachedEtag = parsed?.etag || null;
            if (cachedData && Date.now() - parsed.timestamp < CACHE_TTL) {
              return cachedData;
            }
          }
        } catch (err) {
          console.error('Failed to load from cache:', err);
        }
      }
      const requestHeaders = cachedEtag ? {
        'If-None-Match': cachedEtag
      } : {};
      console.debug(`${DEBUG_PREFIX} revalidating`, {
        storageKey: STORAGE_KEY,
        hasCachedData: Boolean(cachedData),
        hasCachedEtag: Boolean(cachedEtag)
      });
      const response = await fetch(API_URL, {
        headers: requestHeaders
      });
      if (response.status === 304 && cachedData) {
        console.debug(`${DEBUG_PREFIX} revalidated (304)`, {
          storageKey: STORAGE_KEY
        });
        if (typeof localStorage !== 'undefined') {
          try {
            const etag = response.headers.get('etag') || cachedEtag;
            localStorage.setItem(STORAGE_KEY, JSON.stringify({
              data: cachedData,
              etag,
              timestamp: Date.now()
            }));
          } catch (err) {
            console.error('Failed to refresh cache metadata:', err);
          }
        }
        return cachedData;
      }
      if (!response.ok) throw new Error(`API request failed: ${response.statusText}`);
      const data = await response.json();
      const etag = response.headers.get('etag');
      console.debug(`${DEBUG_PREFIX} refreshed (200)`, {
        storageKey: STORAGE_KEY,
        etag
      });
      if (typeof localStorage !== 'undefined') {
        try {
          localStorage.setItem(STORAGE_KEY, JSON.stringify({
            data,
            etag,
            timestamp: Date.now()
          }));
        } catch (err) {
          console.error('Failed to cache data:', err);
        }
      }
      return data;
    };
    fetchData().then(data => setVersionData(data)).catch(err => console.error('Failed to fetch version data:', err));
  }, []);
  const findHighestVersion = versionsMap => {
    if (!versionsMap || Object.keys(versionsMap).length === 0) return null;
    const entries = Object.entries(versionsMap);
    const active = entries.filter(([, v]) => v.upsun && v.upsun.status === 'supported' || v.upsun && v.upsun.status === 'deprecated');
    const candidates = active.length > 0 ? active : entries;
    let [highestName] = candidates[0];
    for (let i = 1; i < candidates.length; i++) {
      const [currentName] = candidates[i];
      const cp = currentName.split('.').map(Number);
      const hp = highestName.split('.').map(Number);
      for (let j = 0; j < Math.max(cp.length, hp.length); j++) {
        if ((cp[j] || 0) > (hp[j] || 0)) {
          highestName = currentName;
          break;
        } else if ((cp[j] || 0) < (hp[j] || 0)) {
          break;
        }
      }
    }
    return highestName;
  };
  const getVersion = (lang, requestedVersion = 'latest') => {
    if (lang === 'composable') {
      if (!composableData || !composableData.versions || Object.keys(composableData.versions).length === 0) return null;
      if (requestedVersion && requestedVersion !== 'latest') {
        return (requestedVersion in composableData.versions) ? requestedVersion : null;
      }
      return findHighestVersion(composableData.versions);
    }
    if (!versionData) return null;
    const imageData = versionData[lang];
    if (!imageData || !imageData.versions || Object.keys(imageData.versions).length === 0) {
      return null;
    }
    if (requestedVersion && requestedVersion !== 'latest') {
      return (requestedVersion in imageData.versions) ? requestedVersion : null;
    }
    return findHighestVersion(imageData.versions);
  };
  let code = typeof children === 'string' ? children : String(children || '');
  const codeLines = code.split('\n');
  while (codeLines.length > 0 && codeLines[0].trim() === '') codeLines.shift();
  while (codeLines.length > 0 && codeLines[codeLines.length - 1].trim() === '') codeLines.pop();
  if (codeLines.length > 0) {
    const indents = codeLines.filter(line => line.trim().length > 0).map(line => line.match(/^[ \t]*/)[0].length);
    const minIndent = Math.min(...indents);
    code = codeLines.map(line => line.slice(minIndent)).join('\n');
  }
  code = code.replace(/\{\{version:(.*?)\}\}/g, (match, params) => {
    const parts = params.split(':');
    const lang = parts[0];
    const ver = parts[1] || 'latest';
    const isComposable = lang === 'composable';
    const hasError = isComposable ? composableError : versionError;
    const dataReady = isComposable ? composableData !== null : versionData !== null;
    if (hasError) return '(unavailable)';
    if (dataReady) {
      const resolvedVersion = getVersion(lang, ver);
      return resolvedVersion || match;
    }
    return '...';
  });
  const codeBlockProps = {
    language,
    ...filename && ({
      filename
    }),
    ...icon && ({
      icon
    }),
    ...lines !== undefined && ({
      lines
    }),
    ...wrap !== undefined && ({
      wrap
    }),
    ...expandable !== undefined && ({
      expandable
    }),
    ...highlight && ({
      highlight
    }),
    ...focus && ({
      focus
    })
  };
  return <CodeBlock {...codeBlockProps}>{code}</CodeBlock>;
};

Optional in [single-runtime](/docs/configure-apps/app-reference/single-runtime-image#primary-application-properties) and [composable](/docs/configure-apps/app-reference/composable-image#primary-application-properties) images.

The keys of the `crons` definition are the names of the cron jobs.
The names must be unique.

If an application defines both a `web` instance and `worker` instances, cron jobs run only on the `web` instance.

See how to [get cron logs](/docs/observability/logs/access-logs#container-logs).

The following table shows the properties for each job:

| Name               | Type                                         | Required | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| ------------------ | -------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `spec`             | `string`                                     | Yes      | The [cron specification](https://en.wikipedia.org/wiki/Cron#Cron_expression). To prevent competition for resources that might hurt performance, use `H` in definitions to indicate an unspecified but invariant time. For example, instead of using `0 * * * *` to indicate the cron job runs at the start of every hour, you can use `H * * * *` to indicate it runs every hour, but not necessarily at the start. This prevents multiple cron jobs from trying to start at the same time. |
| `commands`         | A [cron commands dictionary](#cron-commands) | Yes      | A definition of what commands to run when starting and stopping the cron job.                                                                                                                                                                                                                                                                                                                                                                                                               |
| `shutdown_timeout` | `integer`                                    | No       | When a cron is canceled, this represents the number of seconds after which a `SIGKILL` signal is sent to the process to force terminate it. The default is `10` seconds.                                                                                                                                                                                                                                                                                                                    |
| `timeout`          | `integer`                                    | No       | The maximum amount of time a cron can run before it's terminated. Defaults to the maximum allowed value of `86400` seconds (24 hours).                                                                                                                                                                                                                                                                                                                                                      |

Note that you can [cancel pending or running crons](/docs/environments/cancel-activity).

### Cron commands

| Name    | Type     | Required | Description                                                                                                                                                                                                                                                                        |
| ------- | -------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `start` | `string` | Yes      | The command that's run. It's run in [Dash](https://en.wikipedia.org/wiki/Almquist_shell).                                                                                                                                                                                          |
| `stop`  | `string` | No       | The command that's issued to give the cron command a chance to shutdown gracefully, such as to finish an active item in a list of tasks. Issued when a cron task is interrupted by a user through the CLI or Console. If not specified, a `SIGTERM` signal is sent to the process. |

<Tabs>
  <Tab title="Single-runtime image">
    <DynamicCodeBlock language="yaml" filename=".upsun/config.yaml">
      {`
              applications:
                <APP_NAME>:
                  type: 'nodejs:{{version:nodejs:latest}}'
                  source:
                    root: "/"
                  crons:
                    mycommand:
                      spec: 'H * * * *'
                      commands:
                        start: sleep 60 && echo sleep-60-finished && date
                        stop: killall sleep
                      shutdown_timeout: 18`
          }
    </DynamicCodeBlock>
  </Tab>

  <Tab title="Composable image">
    <DynamicCodeBlock language="yaml" filename=".upsun/config.yaml">
      {`
              applications:
                <APP_NAME>:
                  type: "composable:{{version:composable:latest}}"
                  stack:
                    runtimes: [ 'nodejs:{{version:nodejs:latest}}' ]
                  source:
                    root: "/"
                  crons:
                    mycommand:
                      spec: 'H * * * *'
                      commands:
                        start: sleep 60 && echo sleep-60-finished && date
                        stop: killall sleep
                      shutdown_timeout: 18`
          }
    </DynamicCodeBlock>
  </Tab>
</Tabs>

In this example configuration, the crons `spec` key uses the `H` syntax.

### Single-runtime image: Example cron jobs

<Tabs>
  <Tab title="Drupal">
    <DynamicCodeBlock language="yaml" filename=".upsun/config.yaml">
      {`
              applications:
                # The name of the app container. Must be unique within a project.
                myapp:
                  # The location of the application's code.
                  type: 'php:{{version:php:latest}}'
                  crons:
                    # Run Drupal's cron tasks every 19 minutes.
                    drupal:
                      spec: '*/19 * * * *'
                      commands:
                        start: 'cd web ; drush core-cron'
                    # But also run pending queue tasks every 7 minutes.
                    # Use an odd number to avoid running at the same time as the \`drupal\` cron.
                    drush-queue:
                      spec: '*/7 * * * *'
                      commands:
                        start: 'cd web ; drush queue-run aggregator_feeds'
            `
          }
    </DynamicCodeBlock>
  </Tab>

  <Tab title="Ruby on Rails">
    <DynamicCodeBlock language="yaml" filename=".upsun/config.yaml">
      {`
              applications:
                # The name of the app container. Must be unique within a project.
                myapp:
                  # The location of the application's code.
                  type: 'ruby:{{version:ruby:latest}}'
                  crons:
                    # Execute a rake script every 19 minutes.
                    ruby:
                      spec: '*/19 * * * *'
                      commands:
                        start: 'bundle exec rake some:task'
            `
          }
    </DynamicCodeBlock>
  </Tab>

  <Tab title="Laravel">
    <DynamicCodeBlock language="yaml" filename=".upsun/config.yaml">
      {`
              applications:
                # The name of the app container. Must be unique within a project.
                myapp:
                  # The location of the application's code.
                  type: 'php:{{version:php:latest}}'
                  crons:
                    # Run Laravel's scheduler every 5 minutes.
                    scheduler:
                      spec: '*/5 * * * *'
                      commands:
                        start: 'php artisan schedule:run'
            `
          }
    </DynamicCodeBlock>
  </Tab>

  <Tab title="Symfony">
    <DynamicCodeBlock language="yaml" filename=".upsun/config.yaml">
      {`
              applications:
                # The name of the app container. Must be unique within a project.
                myapp:
                  # The location of the application's code.
                  type: 'php:{{version:php:latest}}'
                  crons:
                    # Take a backup of the environment every day at 5:00 AM.
                    snapshot:
                      spec: 0 5 * * *
                      commands:
                        start: |
                          # Only run for the production environment, aka main branch
                          if [ "$PLATFORM_ENVIRONMENT_TYPE" = "production" ]; then
                              croncape symfony ...
                          fi
            `
          }
    </DynamicCodeBlock>
  </Tab>
</Tabs>

### Composable image: Example cron jobs

<Tabs>
  <Tab title="Drupal">
    <DynamicCodeBlock language="yaml" filename=".upsun/config.yaml">
      {`
              applications:
                # The name of the app container. Must be unique within a project.
                myapp:
                  # The location of the application's code.
                  type: "composable:{{version:composable:latest}}"
                  stack:
                    runtimes: [ "php@8.4" ]
                  crons:
                    # Run Drupal's cron tasks every 19 minutes.
                    drupal:
                      spec: '*/19 * * * *'
                      commands:
                        start: 'cd web ; drush core-cron'
                    # But also run pending queue tasks every 7 minutes.
                    # Use an odd number to avoid running at the same time as the \`drupal\` cron.
                    drush-queue:
                      spec: '*/7 * * * *'
                      commands:
                        start: 'cd web ; drush queue-run aggregator_feeds'`
          }
    </DynamicCodeBlock>
  </Tab>

  <Tab title="Ruby on Rails">
    <DynamicCodeBlock language="yaml" filename=".upsun/config.yaml">
      {`
              applications:
                # The name of the app container. Must be unique within a project.
                myapp:
                  # The location of the application's code.
                  type: "composable:{{version:composable:latest}}"
                  stack:
                    runtimes: [ "ruby@{{version:ruby:latest}}" ]
                  crons:
                    # Execute a rake script every 19 minutes.
                    ruby:
                      spec: '*/19 * * * *'
                      commands:
                        start: 'bundle exec rake some:task'`
          }
    </DynamicCodeBlock>
  </Tab>

  <Tab title="Laravel">
    <DynamicCodeBlock language="yaml" filename=".upsun/config.yaml">
      {`
              applications:
                # The name of the app container. Must be unique within a project.
                myapp:
                  # The location of the application's code.
                  type: "composable:{{version:composable:latest}}"
                  stack:
                    runtimes: [ "php@8.4" ]
                  crons:
                    # Run Laravel's scheduler every 5 minutes.
                    scheduler:
                      spec: '*/5 * * * *'
                      commands:
                        start: 'php artisan schedule:run'`
          }
    </DynamicCodeBlock>
  </Tab>

  <Tab title="Symfony">
    <DynamicCodeBlock language="yaml" filename=".upsun/config.yaml">
      {`
              applications:
                # The name of the app container. Must be unique within a project.
                myapp:
                  # The location of the application's code.
                  type: "composable:{{version:composable:latest}}"
                  stack:
                    runtimes: [ "php@8.4" ]
                  crons:
                    # Take a backup of the environment every day at 5:00 AM.
                    snapshot:
                      spec: 0 5 * * *
                      commands:
                        start: |
                          # Only run for the production environment, aka main branch
                          if [ "$PLATFORM_ENVIRONMENT_TYPE" = "production" ]; then
                              croncape symfony ...
                          fi`
          }
    </DynamicCodeBlock>
  </Tab>
</Tabs>

### Conditional crons

If you want to set up customized cron schedules depending on the environment type,
define conditional crons.
To do so, use a configuration similar to the following:

<Tabs>
  <Tab title="Single-runtime image">
    <DynamicCodeBlock language="yaml" filename=".upsun/config.yaml">
      {`
              applications:
                myapp:
                  type: 'php:{{version:php:latest}}'
                  source:
                    root: "/"
                  crons:
                    update:
                      spec: '0 0 * * *'
                      commands:
                        start: |
                          if [ "$PLATFORM_ENVIRONMENT_TYPE" = production ]; then
                            upsun backup:create --yes --no-wait
                            upsun source-operation:run update --no-wait --yes
                          fi`
          }
    </DynamicCodeBlock>
  </Tab>

  <Tab title="Composable image">
    <DynamicCodeBlock language="yaml" filename=".upsun/config.yaml">
      {`
              applications:
                myapp:
                  type: "composable:{{version:composable:latest}}"
                  source:
                    root: "/"
                  stack:
                    runtimes: [ "php@8.4" ]
                  crons:
                    update:
                      spec: '0 0 * * *'
                      commands:
                        start: |
                          if [ "$PLATFORM_ENVIRONMENT_TYPE" = production ]; then
                            upsun backup:create --yes --no-wait
                            upsun source-operation:run update --no-wait --yes
                          fi`
          }
    </DynamicCodeBlock>
  </Tab>
</Tabs>

### Cron job timing

The minimum time between cron jobs being triggered is 5 minutes.

For each app container, only one cron job can run at a time.
If a new job is triggered while another is running, the new job is paused until the other completes.

To minimize conflicts, a random offset is applied to all triggers.
The offset is a random number of seconds up to 20 minutes or the cron frequency, whichever is smaller.

Crons are also paused while activities such as [backups](/docs/environments/backup) are running.
The crons are queued to run after the other activity finishes.

To run cron jobs in a timezone other than UTC, set the `timezone` property property as described for the image type ([single-runtime image](/docs/configure-apps/app-reference/single-runtime-image#primary-application-properties) or
[composable image](/docs/configure-apps/app-reference/composable-image#primary-application-properties)).

To run cron jobs in a timezone other than UTC, set the [timezone property](/docs/configure-apps/timezone).

### Paused crons

[Preview environments](/docs/glossary#preview-environment) are often used for a limited time and then abandoned.
While it's useful for environments under active development to have scheduled tasks,
unused environments don't need to run cron jobs.
To minimize unnecessary resource use,
crons on environments with no deployments are paused.

This affects all preview environments *and* production environments that do not yet have a domain attached to them.

Such environments with deployments within 14 days have crons with the status `running`.
If there haven't been any deployments within 14 days, the status is `paused`.

You can see the status in the Console
or using the CLI by running `upsun environment:info` and looking under `deployment_state`.

#### Restarting paused crons

If the crons on your preview environment are paused but you're still using them,
you can push changes to the environment or redeploy it.

To restart crons without changing anything:

<Tabs>
  <Tab title="In the Console">
    1. In the Console, navigate to your project.
    2. Open the environment where you'd like the crons to run.
    3. Click `Redeploy` next to the cron status of `Paused`.
  </Tab>

  <Tab title="Using the CLI">
    Run the following command:

    ```bash theme={null}
    upsun redeploy
    ```
  </Tab>
</Tabs>
