AppSignal

Monitoring WooCommerce Background Jobs with AppSignal

Monitoring WooCommerce Background Jobs with AppSignal

WooCommerce relies on a lot of work happening in the background. Some of that is handled by WordPress cron, while other tasks are queued through Action Scheduler.

This can include things like sending scheduled emails, processing subscription renewals, delivering webhooks, running scheduled WooCommerce actions, and other maintenance tasks that do not happen during a normal page request.

The problem is that when one of these background jobs stops running, it is not always obvious straight away. A scheduled action can fail, a queue can become backed up, or a cron task can simply stop firing. In many cases, the first sign of a problem is a customer reporting that something did not happen when expected.

That Is Where Monitoring Becomes Useful

With AppSignal, we can monitor these background processes and get visibility into whether they are running successfully, failing, taking too long, or missing their expected schedule altogether.

In this article, we will build a small WooCommerce background job, run it through Action Scheduler, and use AppSignal to detect successful executions, failures and missed runs. We will then go deeper by checking queue-health conditions for a monitored Action Scheduler group from PHP and sending Process Monitor events directly from WordPress. The examples were tested locally with Lando, WooCommerce 10.8.1, Action Scheduler 3.9.3 and PHP 8.4.15.

What You'll Need

  • A WooCommerce store you can safely test on (local or staging), with shell access and WP-CLI.
  • A free AppSignal account with an app for the store, and that app's Push API key.
  • AppSignal Wrap installed wherever the monitored commands run.

How WooCommerce Runs Background Jobs

WooCommerce background work commonly involves two important systems: WP-Cron and Action Scheduler. They solve slightly different problems, but both are important for keeping scheduled and queued tasks moving.

WP-Cron

WP-Cron is WordPress’s built-in scheduling system. Plugins and themes can use it to schedule tasks that should run later or on a recurring basis.

Unlike a traditional system cron, WP-Cron is normally triggered when someone visits the site. On a busy store, that can work reasonably well because requests happen regularly. On a low-traffic or irregular-traffic site, however, scheduled tasks may run later than expected because there may be no request available to trigger them.

For WooCommerce, that matters because some background work can be time-sensitive. If cron is delayed or stops being triggered altogether, scheduled tasks can begin to build up without there being an obvious problem on the storefront.

Action Scheduler

WooCommerce also uses Action Scheduler, a job queue designed for scheduled and background processing in WordPress.

It is used extensively by WooCommerce and many of its extensions for tasks such as subscription renewals, webhook delivery, scheduled emails, database updates, and other work that should happen outside the normal page request.

Each scheduled action has its own status, such as pending, complete or failed. This makes Action Scheduler much easier to inspect than a simple cron event, but it does not mean problems cannot go unnoticed.

By default, Action Scheduler is itself kicked off through WordPress. Its queue runner is scheduled through WP-Cron, and it can also initiate an asynchronous loopback request from the shutdown hook on WordPress admin requests when pending actions are available. This means Action Scheduler gives WooCommerce a more capable queue, but the default runner can still depend on WordPress scheduling and HTTP loopback behaviour.

Actions can fail because of an exception or application error, become overdue because the runner is not processing the queue, or remain in progress longer than expected. Over time, this can lead to a backlog of scheduled work even while the main WooCommerce store continues to appear healthy.

That is why it is useful to monitor both whether the background runner is executing and whether the jobs inside it are actually completing successfully.

Creating a Safe Test Job

For this tutorial, we will use a small custom WooCommerce background job built with Action Scheduler.

The goal is to use something realistic enough to behave like a normal WooCommerce background task, while avoiding anything that could affect orders, payments, customers, webhooks, or other important store data.

The job reads a maximum of 25 published WooCommerce products, checks their stock status, and stores only an aggregate summary. It records how many products are in stock, out of stock, or on backorder.

It also includes a deliberate failure mode, which gives us a safe way to test how monitoring behaves when a background job fails.

Registering the Action Scheduler Hook and Building the Product Summary

For this tutorial, save the demo job as a must-use plugin at wp-content/mu-plugins/appsignal-woocommerce-demo.php.

Start the file with the PHP opening tag, then register the callback that Action Scheduler will run. When Action Scheduler runs an action with the appsignal_demo_catalog_summary hook, WordPress will call the matching function.

The callback accepts a mode argument so we can safely test both successful and failed executions:

wp-content/mu-plugins/appsignal-woocommerce-demo.php

The query is deliberately limited to 25 published products so the test remains predictable and lightweight.

Nothing is changed on the products themselves. The job only reads their stock status and stores the resulting counts in a single WordPress option.

In our test store, a successful run produced:

JSON
{
  "products_checked": 25,
  "instock": 25,
  "outofstock": 0,
  "onbackorder": 0,
  "other": 0
}

By using a small custom Action Scheduler job like this, we can safely repeat the same test while we add process monitoring and compare successful and failed executions.

Running the Job Manually

Before adding AppSignal monitoring, it is worth running the job manually and confirming that the Action Scheduler behaves as expected.

We can create a fresh action using WP-CLI:

Shell
lando wp action-scheduler action create \
  appsignal_demo_catalog_summary '+1 day' \
  --group=appsignal-demo \
  --args='["success"]'

This schedules our appsignal_demo_catalog_summary action in the dedicated appsignal-demo group and passes success as its mode.

The command returns the ID of the newly created action:

text
Success: Single action (ACTION_ID) scheduled.

We deliberately schedule the action for the following day. This prevents the normal Action Scheduler queue from picking it up before we are ready to run our controlled test.

Making Exit Codes Reliable

Before we run the action, there's one detail that becomes important once we monitor the job from outside WordPress: the process exit code.

Action Scheduler can correctly mark an individual action as failed without the parent CLI command necessarily behaving like a failed system process. That creates a problem for external process monitoring.

AppSignal Wrap monitors the command being executed. If that command exits successfully, the process can appear healthy even when the WooCommerce action inside it has failed.

For reliable monitoring, we therefore want the exit code of the CLI process to reflect the final state of the Action Scheduler job itself.

A conventional command-line process uses:

  • 0 for success
  • a non-zero value for failure

To make that behaviour explicit, we'll run the demo action through a small strict runner.

The runner also refuses to touch anything outside our demo hook and group, so a tutorial command can't accidentally execute an unrelated WooCommerce action.

Creating the Strict Runner

Save the strict runner as tools/appsignal/run-action.php. It validates the supplied action ID, makes sure it belongs to our tutorial hook and group, executes only that action, then checks the final Action Scheduler status so the process exit code reflects the real result:

tools/appsignal/run-action.php

If the action completes successfully, the runner reaches WP_CLI::success() and the process exits successfully.

If the action ends in any other state, such as failed, the runner throws an exception. The surrounding error handler passes that message to WP_CLI::error(), which terminates the command with a non-zero exit code.

This gives us the behaviour we want:

  • Action Scheduler status complete → process exits 0
  • Action Scheduler status failed → process exits non-zero

That gives AppSignal a much more trustworthy signal. A successful process now means that the WooCommerce background job was actually completed successfully, rather than simply meaning that the CLI command finished executing.

Running the Action

Run the action through the strict runner, replacing ACTION_ID with the ID returned when you created it:

Shell
lando wp eval-file tools/appsignal/run-action.php ACTION_ID

A successful execution produces output similar to:

text
Running demo action ACTION_ID.
Success: Action ACTION_ID completed.

We can also check the exit code returned by the runner:

Shell
echo $?

A successful action should return:

text
0

Checking the Action Scheduler Status

Finally, we can inspect the action directly using WP-CLI:

Shell
lando wp action-scheduler action get ACTION_ID --format=json

For a successful run, the important value is:

JSON
"status": "complete"

You can also confirm the result in WooCommerce → Status → Scheduled Actions, where the action should appear with a status of Complete.

WooCommerce Scheduled Actions screen showing the appsignal_demo_catalog_summary action with status Complete, run via the AppSignal tutorial CLI

At this point, the job runs and the runner's exit code matches the result. Next, let's test both outcomes side by side.

Testing Success and Failure

With the demo job and strict runner in place, we can now test both outcomes and confirm that the Action Scheduler status matches the process exit code.

This is important because AppSignal will monitor the process from outside WordPress. If the exit code says the command succeeded when the underlying WooCommerce action actually failed, the monitor could report a false success.

Successful Run

First, create a fresh action in success mode:

Shell
lando wp action-scheduler action create \
  appsignal_demo_catalog_summary '+1 day' \
  --group=appsignal-demo \
  --args='["success"]'

In this test, Action Scheduler created action 239530.

Running it through the strict runner:

Shell
lando wp eval-file tools/appsignal/run-action.php 239530
echo $?

The output was:

text
Running demo action 239530.
Success: Action 239530 completed.
0

The final 0 confirms that the CLI process exited successfully.

Checking the action in WooCommerce also showed the final Action Scheduler status as complete.

Scheduled Actions entry for action 239530 with status Complete

Failed Run

Next, create a fresh action in failure mode:

Shell
lando wp action-scheduler action create \
  appsignal_demo_catalog_summary '+1 day' \
  --group=appsignal-demo \
  --args='["fail"]'

For this test, Action Scheduler created action 239531.

Running it through the same strict runner:

Shell
lando wp eval-file tools/appsignal/run-action.php 239531
echo $?

This time the output was:

text
Running demo action 239531.
Error: Action 239531 ended with status failed. Deliberate AppSignal catalog-summary failure.
1

The runner returned exit code 1, and WooCommerce recorded the action with a status of failed.

Scheduled Actions entry for the fail-mode action with status Failed and the message "Deliberate AppSignal catalog-summary failure."

These two tests give us the behaviour we need:

  • successful action → complete → exit code 0
  • failed action → failed → exit code 1

That means the process result now accurately reflects the WooCommerce job result, giving AppSignal a reliable signal to monitor in the next stage.

Monitoring the Runner with AppSignal Wrap

Now that the runner returns a trustworthy exit code, we can start monitoring it with AppSignal Wrap.

AppSignal Wrap sits around a command-line process and reports its execution to AppSignal. This makes it useful for jobs that run outside a normal web request, such as cron commands, queue workers, maintenance scripts, or in this case, our WooCommerce Action Scheduler runner.

For this tutorial, Wrap is installed inside the same Lando appserver that runs WordPress. The AppSignal push key is provided through an environment variable rather than being included directly in the command or committed to the project.

Before running the command, create a Cron Process Monitor in AppSignal (how to create one) using the identifier:

text
woo-catalog-summary

For the initial manual tests, the monitor was configured to expect a run every minute with a maximum duration of two minutes. This short schedule is only useful while testing because it lets us see successful and missed occurrences quickly. In production, the schedule should match how often the real background process is expected to run.

It is worth noting that the Process Monitor does not schedule the job itself. It defines what AppSignal expects to happen. The command still needs to be triggered separately by cron, a scheduler, or manually as we are doing here.

Once connected, AppSignal can tell us several useful things about the runner:

  • whether the process actually started
  • whether it completed successfully
  • how long the execution took
  • whether an expected execution never completed

That gives us visibility beyond simply knowing that an Action Scheduler entry exists inside WordPress.

Installing and Configuring AppSignal Wrap

AppSignal Wrap needs to be installed in the environment where the monitored command runs. AppSignal provides installation instructions for Linux and macOS in the Wrap documentation.

Wrap also needs the app-level Push API key for the AppSignal application and environment being monitored. You can find this under Settings → Push & Deploy in AppSignal.

Rather than placing the key in the command itself, expose it as the APPSIGNAL_APP_PUSH_API_KEY environment variable.

In this Lando setup, the variable is defined in a private, ignored local configuration and exposed to the appserver container. Because Wrap runs inside that container, it inherits the same environment variable.

The docker exec and Lando-specific paths used below are specific to this local environment. On a normal VPS or SSH host, you would run appsignal-wrap and WP-CLI directly from the WordPress installation.

Running the Strict Runner Through Wrap

We can run our existing strict runner through AppSignal Wrap without changing the PHP code itself.

The tested command is:

Shell
docker exec --user www-data --workdir /app \
  your-wordpress-appserver \
  appsignal-wrap woo-catalog-summary --cron -- \
  wp eval-file tools/appsignal/run-action.php ACTION_ID

Replace ACTION_ID with the ID of the Action Scheduler job you want to execute, and your-wordpress-appserver with the name of the container running WordPress in your environment.

appsignal-wrap starts the monitored process, while woo-catalog-summary matches the identifier of the Process Monitor we created in AppSignal.

The --cron option tells Wrap that this is a scheduled process. Everything after the final -- is the command being monitored, which in our case is the same strict WP-CLI runner we tested earlier.

For a successful action, the runner returns exit code 0. Wrap records the successful execution and sends the completed occurrence to AppSignal.

For a failed action, the runner returns exit code 1. Wrap reports the non-zero exit as an error and does not send a successful finish event. AppSignal can then show both the command failure and the missing successful completion of that scheduled occurrence.

This is where the strict exit handling from earlier becomes important: Wrap does not need to understand Action Scheduler itself. It only needs the command to communicate success or failure accurately.

Running the Monitor on a Real Schedule

So far, we have executed individual actions manually so each test remains controlled. The strict runner is deliberately built around a single Action Scheduler ID, so it is useful for testing but is not suitable as a recurring production runner.

To test a more realistic setup, register the same demo hook as a recurring Action Scheduler action every five minutes. Only register it once: re-running the command creates another recurrence.

Shell
lando wp action-scheduler action create \
  appsignal_demo_catalog_summary \
  '2026-10-08 11:50:00 UTC' \
  --cron='*/5 * * * *' \
  --group=appsignal-demo \
  --args='["success"]'

The start time above came from our test environment. When reproducing this, choose a future five-minute boundary rather than copying that timestamp.

Each execution creates a new Action Scheduler action, so the recurring job is not tied to a single action ID.

Something still has to process the queue every five minutes. For that, we'll use a host-level cron entry that calls a small shell script, plus a WP-CLI helper that keeps the queue runner scoped to our demo.

Scoping the CLI Queue Runner

Save the WP-CLI helper as tools/appsignal/demo-queue-cli.php. It keeps this tutorial run isolated to the modern Action Scheduler store and disables the runner’s global housekeeping for this CLI invocation:

tools/appsignal/demo-queue-cli.php

The --group and --hooks options we'll pass to the runner restrict which actions it processes, but they don't scope the runner's housekeeping. The helper prevents the standard runner from performing unrelated cleanup or timeout/failure housekeeping during this tutorial command.

It is deliberately scoped to this test environment rather than being a general Action Scheduler configuration change.

Running the Recurring Queue Command

Save the cron entry point as tools/appsignal/run-recurring-demo.sh:

tools/appsignal/run-recurring-demo.sh
#!/bin/sh
# Host cron entry point.
# The private key is inherited inside the Lando container.
 
set -eu
 
exec /usr/local/bin/docker \
  --context desktop-linux \
  exec \
  --user www-data \
  --workdir /app \
  your-wordpress-appserver \
  /usr/bin/flock -n /tmp/appsignal-demo-queue.lock \
  /usr/local/bin/appsignal-wrap \
  woo-catalog-summary \
  --cron \
  -- \
  /usr/local/bin/wp \
  --require=/app/tools/appsignal/demo-queue-cli.php \
  action-scheduler run \
  --group=appsignal-demo \
  --hooks=appsignal_demo_catalog_summary \
  --batch-size=10 \
  --batches=1

Replace your-wordpress-appserver with the name of the container running WordPress in your environment. A few details are worth calling out:

  • flock prevents overlapping executions if one run is still active when the next five-minute window arrives.
  • --group and --hooks restrict processing to the appsignal-demo group and our appsignal_demo_catalog_summary hook, so the command does not process unrelated WooCommerce jobs.
  • --require loads the demo-queue-cli.php helper from the previous section.
  • The script contains no AppSignal API key. APPSIGNAL_APP_PUSH_API_KEY is supplied to the Lando appserver through the private .lando.local.yml configuration, and because docker exec runs the command inside that container, Wrap inherits it there.

Next, trigger the script from cron. On macOS, open the user crontab with:

Shell
crontab -e

Then add:

Shell
*/5 * * * * /bin/sh /Users/your-user/path/to/project/tools/appsignal/run-recurring-demo.sh >> /tmp/appsignal-recurring-demo.log 2>&1

Replace /Users/your-user/path/to/project with the path to your own project. On a production server, the same script would normally be invoked by the server’s own cron or scheduled-task service using that environment’s paths. Because this example runs locally, the Mac must remain awake and Docker and Lando must be running for the scheduled command to execute.

Finally, update the woo-catalog-summary Process Monitor in AppSignal to the matching schedule, */5 * * * *, with a maximum duration of two minutes.

During testing, consecutive recurring actions completed successfully five minutes apart. The WooCommerce and AppSignal screenshots below come from separate verified recurring runs, but both show the same five-minute setup: Action Scheduler creates new demo actions on the configured recurrence, while AppSignal records the wrapped runner executions against the matching monitor schedule.

Two recurring appsignal-demo actions on a */5 schedule, both completed via WP-CLI five minutes apart
AppSignal Process Monitoring occurrence for woo-catalog-summary on a */5 schedule, status Success, with start and finish events

The AppSignal schedule and the actual cron trigger now match, giving the monitor a predictable execution window instead of relying on manual commands.

A successful queue-runner process does not necessarily prove that every individual action inside a larger queue succeeded. That is why we add a separate queue-health check later in the article rather than relying on the runner exit code alone.

Cleaning Up the Recurring Test

After testing, remove the host cron entry with:

Shell
crontab -e

Delete the run-recurring-demo.sh line so the local runner is no longer triggered every five minutes.

The recurring Action Scheduler entry should also be removed or cancelled so it does not continue creating demo actions after the tutorial is complete.

Finally, remove or adjust the woo-catalog-summary Process Monitor if the five-minute test schedule is no longer required.

For the deliberate missed-run test later in the article, we temporarily disabled the host cron trigger for one scheduled window, then restored it afterwards.

Viewing the Process in AppSignal

Once the wrapped command has run, the result appears under Process Monitoring in AppSignal.

Open the woo-catalog-summary monitor to view its individual occurrences. Each occurrence represents one expected execution of the scheduled process and shows whether AppSignal received the events it expected within the configured time window.

For our successful test, action 239535 completed normally and the strict runner returned exit code 0. AppSignal recorded the occurrence as Success, with a duration of around one second.

AppSignal Process Monitoring occurrence for woo-catalog-summary with status Success and an actual duration of 1 second

This gives us confirmation from outside WordPress that the runner did not just start, but also completed successfully.

Viewing the Runner Logs

AppSignal Wrap also captures the wrapped command’s standard output and standard error.

In this test, the Action Scheduler runner output appeared in AppSignal Logging under the application source and the woo-catalog-summary log group.

The logs show the scheduled task being found, processed and completed, giving us the command output alongside the Process Monitor occurrence.

AppSignal Logging view showing the Action Scheduler runner output in the woo-catalog-summary log group

Viewing a Failed Execution

The deliberate failure behaves differently.

Action 239534 threw our test exception, Action Scheduler marked it as failed, and the strict runner returned exit code 1.

Because Wrap received a non-zero exit code, AppSignal created a NonZeroExit error for the woo-catalog-summary process.

You can find this under Errors in AppSignal, where the failure provides a separate signal that the command itself exited unsuccessfully.

AppSignal NonZeroExit error for the woo-catalog-summary process, showing the deliberate catalog-summary failure and "Process exited with code 1"

The Process Monitor also tells us something slightly different about the same failure.

Wrap sends a start event when the process begins, but it only sends a successful finish event when the wrapped command completes successfully. Because our deliberately failed command exited with 1, no successful finish event was sent.

After the configured two-minute deadline passed, AppSignal therefore marked that Process Monitor occurrence as Missed.

AppSignal occurrence marked Missed: a start event was received but no finish event

This gives us two useful views of the failure:

  • Errors → NonZeroExit tells us that the command exited unsuccessfully.
  • Process Monitoring → Missed tells us that the expected process never reported a successful completion.

Together, these make it much easier to distinguish between a job that completed normally and one that started but failed before it could finish successfully.

Timing Matters

During testing, we also ran a successful action outside the Process Monitor’s configured schedule window, which AppSignal recorded as Unexpected.

That is useful behaviour to understand. A Process Monitor does not simply record whether a command has run at some point. It compares executions against the schedule you configured.

For this tutorial, we used an every-minute schedule purely to make testing faster. In a production store, the AppSignal monitor schedule should match the schedule of the background job itself. Otherwise, valid executions could appear as unexpected, or expected executions could be reported as missed.

Breaking It on Purpose

So far, we have seen what happens when a background job starts and then fails. Another important failure mode is when the job never starts at all.

To test this, we temporarily configured the woo-catalog-summary Process Monitor to expect a run every five minutes, with a maximum duration of two minutes.

For one scheduled window, we temporarily disabled the host cron entry that runs run-recurring-demo.sh. The recurring Action Scheduler setup remained in place, but the wrapped queue runner was not triggered for that window.

AppSignal therefore expected the process to start at 08:45 and finish by 08:47, but no events were received.

After the deadline passed, the occurrence was marked as Missed.

AppSignal occurrence for woo-catalog-summary marked Missed with "No events received"

This is different from the deliberate failure test earlier.

In that case, the process started and then exited unsuccessfully. In this controlled test, we know the process never started because we deliberately disabled the host cron trigger for that execution window. In production, a Missed occurrence tells us that AppSignal did not receive the expected events. That could mean the scheduler or runner never executed, but reporting or connectivity problems can produce the same symptom.

That distinction is useful in production because the two situations can point to different problems:

  • a failed process may indicate an application or code error
  • a missed process may indicate that cron or the runner did not execute, or that AppSignal never received the expected monitoring events

By monitoring the expected schedule as well as the process exit status, AppSignal can help distinguish between a job that ran and failed and one that never ran in the first place.

Receiving the Alert

For this test, we also attached AppSignal’s default email notifier to the woo-catalog-summary Process Monitor.

After the expected 08:55 execution was deliberately skipped, AppSignal marked the occurrence as Missed once the two-minute maximum duration had passed.

The configured notifier then delivered an email alert shortly afterwards, confirming that the failure was not only visible in the AppSignal dashboard but was also sent to the configured notification channel.

AppSignal email alert: check-in woo-catalog-summary did not finish in time

Going Deeper from PHP

AppSignal Wrap tells us whether the background runner itself started, finished, and returned a successful exit code.

That is useful, but it does not necessarily tell us whether the Action Scheduler queue is healthy.

A runner can execute successfully while individual actions are failing, pending work is becoming overdue, or jobs are remaining in progress for too long.

To get that extra visibility, we can add a small PHP health check around the Action Scheduler itself.

For this tutorial, the health check is read-only and scoped to the appsignal-demo group. It does not retry, cancel, delete, or modify any actions.

The health check looks for three unhealthy conditions and also records the age of the oldest overdue action to show how far behind the queue has become.

This gives us a different type of monitoring.

The woo-catalog-summary monitor answers: Did the background runner complete successfully?

The woo-queue-health monitor answers: Does our monitored Action Scheduler group pass the queue-health checks?

Inspecting the Queue from PHP

Save the queue-health check as tools/appsignal/queue-health.php. The script is read-only and scopes every query to the appsignal-demo group:

tools/appsignal/queue-health.php

This checks three unhealthy conditions within the appsignal-demo group: recent failures, overdue pending actions, and actions that have remained in progress beyond the configured threshold.

It also records the age of the oldest overdue action so we can see how far behind the monitored group has become.

The count helper deliberately checks that the Action Scheduler returned a numeric result. If the query itself fails, the script therefore reports an inspection error rather than silently converting the result to zero.

Checking Queue Health from PHP

The PHP health check runs through WP-CLI:

Shell
lando wp eval-file tools/appsignal/queue-health.php 1 5
echo $?

A healthy result looks like this:

text
Action Scheduler health | group: appsignal-demo
Failed actions (last attempted within 1m): 0
Overdue pending actions: 0
Oldest overdue pending age: none
In-progress actions older than 5m: 0
HEALTHY
0
Terminal output of queue-health.php reporting HEALTHY and exit code 0

If one of the queue-health conditions is breached, the script returns a non-zero exit code instead.

For the unhealthy test, we ran the script with its default 15-minute failure window. That window still included the deliberately failed demo action:

text
Action Scheduler health | group: appsignal-demo
Failed actions (last attempted within 15m): 1
Overdue pending actions: 0
Oldest overdue pending age: none
In-progress actions older than 5m: 0
UNHEALTHY
1
Terminal output of queue-health.php reporting one recent failed action, UNHEALTHY, and exit code 1

That gives us a simple signal that can be monitored externally without changing any queue data.

Sending Queue Health to AppSignal

We can wrap the same PHP health check with AppSignal Wrap:

Shell
docker exec --user www-data --workdir /app \
  your-wordpress-appserver \
  appsignal-wrap woo-queue-health --cron -- \
  wp eval-file tools/appsignal/queue-health.php
echo $?

The woo-queue-health identifier is configured as a separate Process Monitor so it remains distinct from the background runner itself.

In testing, a healthy queue returned exit code 0 and AppSignal recorded the occurrence as Success.

AppSignal occurrence for woo-queue-health with status Success

The unhealthy check returned exit code 1. AppSignal recorded a NonZeroExit error, while the Process Monitor occurrence later became Missed because no successful finish event was received.

AppSignal occurrence for woo-queue-health marked Missed after the unhealthy check

This means we now have two complementary layers of monitoring:

  • woo-catalog-summary monitors whether the runner executes successfully
  • woo-queue-health monitors whether the queue passes our PHP health rules

Together, they provide a much clearer picture than either one alone.

A process can be running while the queue is unhealthy, and a healthy queue check can confirm that recent failures, overdue work, and long-running actions are not building up silently.

A Note on the Failure Window

The health check uses the action's last-attempt time to identify recent failures rather than an exact failure timestamp.

For the tutorial, the default observation window is 15 minutes. On a real store, this should be tuned to the workload and how frequently the queue is expected to process actions.

The check is also scoped to the modern Action Scheduler tables and the appsignal-demo group so historical failures elsewhere in the store do not distort the result.

Sending Process Monitor Events Directly from PHP

So far, PHP has evaluated the health rules for our monitored appsignal-demo group, while AppSignal Wrap has been responsible for reporting the result.

For jobs that already run inside WordPress, you can also send Process Monitor events directly from PHP using AppSignal’s HTTP API.

Before running this example, create a separate Cron Process Monitor in AppSignal using the identifier:

text
woo-php-health

For this test, save the script as tools/appsignal/php-process-monitor.php.

The important part is that the start and finish events use the same unique digest, allowing AppSignal to correlate them as one occurrence.

tools/appsignal/php-process-monitor.php

The first request sends the start event. After the simulated work completes, the second request sends the successful finish event by omitting the kind.

For this test, the two-second delay is only a stand-in for real PHP work so the occurrence has a visible duration.

Run the script directly through WP-CLI, without AppSignal Wrap:

Shell
lando wp eval-file tools/appsignal/php-process-monitor.php

Both requests returned HTTP 200, and AppSignal correlated them into a single woo-php-health occurrence.

The resulting occurrence showed:

  • Status: Success
  • Actual duration: 2 seconds
  • matching start and finish events
  • a shared Remote ID for the occurrence

This demonstrates the difference between the two approaches used in this tutorial.

AppSignal Wrap observes the lifecycle of an external command.

Direct PHP events let WordPress code decide when a monitored operation starts and finishes.

Wrap is a good fit for cron commands, queue runners and other external processes, while direct PHP events are useful when the application has more precise knowledge of the work being performed.

Production Considerations

The examples in this article use a deliberately small test job and short monitoring windows so the behaviour is easy to see. On a production WooCommerce store, there are a few extra things worth considering.

Keep Credentials Out of Source Control

AppSignal credentials should not be hardcoded into PHP files, shell scripts, or committed configuration.

For this setup, the AppSignal push key is stored in a private environment configuration and exposed to the appserver as an environment variable.

That keeps the key out of the repository while still making it available to AppSignal Wrap at runtime.

Match the Monitor to the Real Schedule

The one-minute and five-minute schedules used in this tutorial were chosen to make testing quick.

In production, there is no universally correct interval. The Process Monitor schedule should match how often the real job is expected to run, whether that is every minute, every hour, or once a day.

The maximum duration should likewise reflect the normal runtime of that process while remaining tight enough to expose genuinely slow or stuck executions.

Avoid Noisy Monitoring

Not every WooCommerce background action needs its own monitor.

A busy store can have a large number of scheduled actions, and monitoring all of them individually would quickly create noise.

It is usually more useful to focus on processes that are important to the customer experience or business operation, such as:

  • subscription renewals
  • order-related emails
  • webhook delivery
  • imports or exports
  • payment or fulfilment integrations
  • other business-critical scheduled work

Queue-level health checks can then provide broader visibility without creating a separate alert for every action.

Choose Thresholds Carefully

A failed job does not always mean there is an incident.

Some actions may retry automatically, and a small number of temporary failures may be acceptable depending on the store.

The same applies to overdue or long-running actions. Thresholds should reflect the normal workload of the site rather than using arbitrary values.

A useful starting point is to observe the queue under normal conditions first, then set thresholds around what would genuinely require investigation.

Test Safely

Avoid deliberately breaking real orders, payments, subscriptions, webhooks, or customer-facing processes just to test monitoring.

A dedicated test action, like the product-summary job used in this tutorial, gives you a controlled way to trigger success and failure without affecting real store data.

Where possible, validate the monitoring setup on a staging or local environment before applying it to production.

Hosts Without Shell Access

The examples in this article assume access to WP-CLI, the command line, and a system scheduler.

Some managed WordPress hosts do not provide that level of access.

In those environments, you may need to rely on the host's own cron configuration, scheduled task interface, or trigger monitoring from PHP instead.

The underlying principle is the same: create a reliable signal for whether the background process ran and whether the queue itself remains healthy.

Keep the Two Layers Separate

It is useful to think of the monitoring setup as two separate layers:

  • process monitoring tells you whether the background runner executed successfully
  • queue-health monitoring tells you whether the monitored queue scope passes the configured health checks

Keeping those signals separate makes incidents easier to diagnose.

A healthy runner does not necessarily mean a healthy queue, and an unhealthy queue does not necessarily mean the scheduler itself has stopped running.

That distinction is what makes the combined approach useful on a real WooCommerce store.

Conclusion

WooCommerce background jobs can fail in several different ways. A job may start and throw an error, a runner may never execute at all, or the Action Scheduler queue may gradually become unhealthy while the storefront still appears to be working normally.

In this tutorial, we built a small WooCommerce background job and used AppSignal to monitor it at several levels.

AppSignal Wrap gave us visibility into whether the CLI process started, completed successfully, or returned a non-zero exit code. Process Monitoring then allowed us to detect missed runs when an expected job never started.

We also went a step further from PHP by checking queue-health conditions for our monitored Action Scheduler group and sending Process Monitor events directly from WordPress.

The important distinction is that these signals answer different questions:

  • Did the runner execute successfully?
  • Did the scheduled process run when expected?
  • Does the monitored Action Scheduler group pass our health checks?
  • Does the application itself know that an important piece of work started and finished?

Using those signals together makes silent failures much easier to spot before they turn into customer-facing problems.

For a production store, the exact jobs, schedules and thresholds will vary, but the principle stays the same: monitor both the process that runs the work and the state of the queue behind it.

Ready to monitor your WooCommerce background jobs? Create a free AppSignal account.

Published

Wondering what you can do next?

Sam Noble

Sam Noble

Our guest author Sam is a WordPress and WooCommerce developer.

All articles by Sam Noble

Become our next author!

Find out more
$appsignal install

AppSignal monitors your apps

AppSignal provides insights for Ruby, Rails, Elixir, Phoenix, Node.js, Express and many other frameworks and libraries. We are located in beautiful Amsterdam. We love stroopwafels. If you do too, let us know. We might send you some!

Discover AppSignal